Improved AST parser
This commit is contained in:
parent
265ab988d9
commit
1e7a1399d3
@ -1,17 +1,16 @@
|
|||||||
package ast
|
package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.akyoto.dev/cli/q/src/build/errors"
|
|
||||||
"git.akyoto.dev/cli/q/src/build/expression"
|
"git.akyoto.dev/cli/q/src/build/expression"
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parse generates an AST from a list of tokens.
|
// Parse generates an AST from a list of tokens.
|
||||||
func Parse(tokens []token.Token, buffer []byte) (AST, error) {
|
func Parse(tokens []token.Token, source []byte) (AST, error) {
|
||||||
tree := make(AST, 0, len(tokens)/64)
|
tree := make(AST, 0, len(tokens)/64)
|
||||||
|
|
||||||
err := EachInstruction(tokens, func(instruction token.List) error {
|
err := EachInstruction(tokens, func(instruction token.List) error {
|
||||||
node, err := toASTNode(instruction, buffer)
|
node, err := parseNode(instruction, source)
|
||||||
|
|
||||||
if err == nil && node != nil {
|
if err == nil && node != nil {
|
||||||
tree = append(tree, node)
|
tree = append(tree, node)
|
||||||
@ -23,83 +22,6 @@ func Parse(tokens []token.Token, buffer []byte) (AST, error) {
|
|||||||
return tree, err
|
return tree, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// toASTNode generates an AST node from an instruction.
|
|
||||||
func toASTNode(tokens token.List, buffer []byte) (Node, error) {
|
|
||||||
if tokens[0].IsKeyword() {
|
|
||||||
if tokens[0].Kind == token.Return {
|
|
||||||
if len(tokens) == 1 {
|
|
||||||
return &Return{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
value := expression.Parse(tokens[1:])
|
|
||||||
return &Return{Value: value}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokens[0].Kind == token.Assert {
|
|
||||||
if len(tokens) == 1 {
|
|
||||||
return nil, errors.New(errors.MissingExpression, nil, tokens[0].End())
|
|
||||||
}
|
|
||||||
|
|
||||||
condition := expression.Parse(tokens[1:])
|
|
||||||
return &Assert{Condition: condition}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if keywordHasBlock(tokens[0].Kind) {
|
|
||||||
blockStart := tokens.IndexKind(token.BlockStart)
|
|
||||||
blockEnd := tokens.LastIndexKind(token.BlockEnd)
|
|
||||||
|
|
||||||
if blockStart == -1 {
|
|
||||||
return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End())
|
|
||||||
}
|
|
||||||
|
|
||||||
if blockEnd == -1 {
|
|
||||||
return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End())
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := Parse(tokens[blockStart+1:blockEnd], buffer)
|
|
||||||
|
|
||||||
switch tokens[0].Kind {
|
|
||||||
case token.If:
|
|
||||||
condition := expression.Parse(tokens[1:blockStart])
|
|
||||||
return &If{Condition: condition, Body: body}, err
|
|
||||||
|
|
||||||
case token.Loop:
|
|
||||||
return &Loop{Body: body}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(buffer)}, nil, tokens[0].Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
expr := expression.Parse(tokens)
|
|
||||||
|
|
||||||
if expr == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case IsVariableDefinition(expr):
|
|
||||||
if len(expr.Children) < 2 {
|
|
||||||
return nil, errors.New(errors.MissingOperand, nil, expr.Token.End())
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Define{Expression: expr}, nil
|
|
||||||
|
|
||||||
case IsAssignment(expr):
|
|
||||||
if len(expr.Children) < 2 {
|
|
||||||
return nil, errors.New(errors.MissingOperand, nil, expr.Token.End())
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Assign{Expression: expr}, nil
|
|
||||||
|
|
||||||
case IsFunctionCall(expr):
|
|
||||||
return &Call{Expression: expr}, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text(buffer)}, nil, expr.Token.Position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAssignment returns true if the expression is an assignment.
|
// IsAssignment returns true if the expression is an assignment.
|
||||||
func IsAssignment(expr *expression.Expression) bool {
|
func IsAssignment(expr *expression.Expression) bool {
|
||||||
return expr.Token.IsAssignment()
|
return expr.Token.IsAssignment()
|
||||||
@ -114,8 +36,3 @@ func IsFunctionCall(expr *expression.Expression) bool {
|
|||||||
func IsVariableDefinition(expr *expression.Expression) bool {
|
func IsVariableDefinition(expr *expression.Expression) bool {
|
||||||
return expr.Token.Kind == token.Define
|
return expr.Token.Kind == token.Define
|
||||||
}
|
}
|
||||||
|
|
||||||
// keywordHasBlock returns true if the keyword requires a block.
|
|
||||||
func keywordHasBlock(kind token.Kind) bool {
|
|
||||||
return kind == token.If || kind == token.Loop
|
|
||||||
}
|
|
||||||
|
59
src/build/ast/parseKeyword.go
Normal file
59
src/build/ast/parseKeyword.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.akyoto.dev/cli/q/src/build/errors"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/expression"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseKeyword generates a keyword node from an instruction.
|
||||||
|
func parseKeyword(tokens token.List, source []byte) (Node, error) {
|
||||||
|
switch tokens[0].Kind {
|
||||||
|
case token.Assert:
|
||||||
|
if len(tokens) == 1 {
|
||||||
|
return nil, errors.New(errors.MissingExpression, nil, tokens[0].End())
|
||||||
|
}
|
||||||
|
|
||||||
|
condition := expression.Parse(tokens[1:])
|
||||||
|
return &Assert{Condition: condition}, nil
|
||||||
|
|
||||||
|
case token.If:
|
||||||
|
blockStart, _, body, err := block(tokens, source)
|
||||||
|
condition := expression.Parse(tokens[1:blockStart])
|
||||||
|
return &If{Condition: condition, Body: body}, err
|
||||||
|
|
||||||
|
case token.Loop:
|
||||||
|
_, _, body, err := block(tokens, source)
|
||||||
|
return &Loop{Body: body}, err
|
||||||
|
|
||||||
|
case token.Return:
|
||||||
|
if len(tokens) == 1 {
|
||||||
|
return &Return{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value := expression.Parse(tokens[1:])
|
||||||
|
return &Return{Value: value}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(source)}, nil, tokens[0].Position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// block retrieves the start and end position of a block.
|
||||||
|
func block(tokens token.List, source []byte) (blockStart int, blockEnd int, body AST, err error) {
|
||||||
|
blockStart = tokens.IndexKind(token.BlockStart)
|
||||||
|
blockEnd = tokens.LastIndexKind(token.BlockEnd)
|
||||||
|
|
||||||
|
if blockStart == -1 {
|
||||||
|
err = errors.New(errors.MissingBlockStart, nil, tokens[0].End())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if blockEnd == -1 {
|
||||||
|
err = errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = Parse(tokens[blockStart+1:blockEnd], source)
|
||||||
|
return
|
||||||
|
}
|
42
src/build/ast/parseNode.go
Normal file
42
src/build/ast/parseNode.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.akyoto.dev/cli/q/src/build/errors"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/expression"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseNode generates an AST node from an instruction.
|
||||||
|
func parseNode(tokens token.List, source []byte) (Node, error) {
|
||||||
|
if tokens[0].IsKeyword() {
|
||||||
|
return parseKeyword(tokens, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
expr := expression.Parse(tokens)
|
||||||
|
|
||||||
|
if expr == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case IsVariableDefinition(expr):
|
||||||
|
if len(expr.Children) < 2 {
|
||||||
|
return nil, errors.New(errors.MissingOperand, nil, expr.Token.End())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Define{Expression: expr}, nil
|
||||||
|
|
||||||
|
case IsAssignment(expr):
|
||||||
|
if len(expr.Children) < 2 {
|
||||||
|
return nil, errors.New(errors.MissingOperand, nil, expr.Token.End())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Assign{Expression: expr}, nil
|
||||||
|
|
||||||
|
case IsFunctionCall(expr):
|
||||||
|
return &Call{Expression: expr}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text(source)}, nil, expr.Token.Position)
|
||||||
|
}
|
||||||
|
}
|
@ -114,16 +114,16 @@ func (expr *Expression) LastChild() *Expression {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// String generates a textual representation of the expression.
|
// String generates a textual representation of the expression.
|
||||||
func (expr *Expression) String(data []byte) string {
|
func (expr *Expression) String(source []byte) string {
|
||||||
builder := strings.Builder{}
|
builder := strings.Builder{}
|
||||||
expr.write(&builder, data)
|
expr.write(&builder, source)
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// write generates a textual representation of the expression.
|
// write generates a textual representation of the expression.
|
||||||
func (expr *Expression) write(builder *strings.Builder, data []byte) {
|
func (expr *Expression) write(builder *strings.Builder, source []byte) {
|
||||||
if expr.IsLeaf() {
|
if expr.IsLeaf() {
|
||||||
builder.WriteString(expr.Token.Text(data))
|
builder.WriteString(expr.Token.Text(source))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,12 +135,12 @@ func (expr *Expression) write(builder *strings.Builder, data []byte) {
|
|||||||
case token.Array:
|
case token.Array:
|
||||||
builder.WriteString("@")
|
builder.WriteString("@")
|
||||||
default:
|
default:
|
||||||
builder.WriteString(expr.Token.Text(data))
|
builder.WriteString(expr.Token.Text(source))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, child := range expr.Children {
|
for _, child := range expr.Children {
|
||||||
builder.WriteByte(' ')
|
builder.WriteByte(' ')
|
||||||
child.write(builder, data)
|
child.write(builder, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.WriteByte(')')
|
builder.WriteByte(')')
|
||||||
|
Loading…
Reference in New Issue
Block a user