2024-07-01 13:05:15 +00:00
|
|
|
package ast
|
|
|
|
|
|
|
|
import (
|
2024-07-03 09:39:24 +00:00
|
|
|
"git.akyoto.dev/cli/q/src/build/errors"
|
2024-07-01 13:05:15 +00:00
|
|
|
"git.akyoto.dev/cli/q/src/build/expression"
|
|
|
|
"git.akyoto.dev/cli/q/src/build/keyword"
|
|
|
|
"git.akyoto.dev/cli/q/src/build/token"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Parse generates an AST from a list of tokens.
|
|
|
|
func Parse(tokens token.List) (AST, error) {
|
|
|
|
tree := make(AST, 0, len(tokens)/64)
|
|
|
|
|
|
|
|
err := EachInstruction(tokens, func(instruction token.List) error {
|
|
|
|
node, err := toASTNode(instruction)
|
|
|
|
|
|
|
|
if err == nil && node != nil {
|
|
|
|
tree = append(tree, node)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
|
|
|
return tree, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// toASTNode generates an AST node from an instruction.
|
|
|
|
func toASTNode(tokens token.List) (Node, error) {
|
|
|
|
if tokens[0].Kind == token.Keyword {
|
|
|
|
switch tokens[0].Text() {
|
|
|
|
case keyword.Return:
|
|
|
|
value := expression.Parse(tokens[1:])
|
|
|
|
return &Return{Value: value}, nil
|
|
|
|
|
|
|
|
case keyword.Loop:
|
|
|
|
blockStart := tokens.IndexKind(token.BlockStart) + 1
|
|
|
|
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())
|
|
|
|
}
|
|
|
|
|
|
|
|
tree, err := Parse(tokens[blockStart:blockEnd])
|
|
|
|
return &Loop{Body: tree}, err
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
expr := expression.Parse(tokens)
|
|
|
|
|
|
|
|
if expr == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case IsVariableDefinition(expr):
|
|
|
|
if len(expr.Children) < 2 {
|
2024-07-05 07:50:46 +00:00
|
|
|
return nil, errors.New(errors.MissingAssignValue, nil, expr.Token.End())
|
2024-07-01 13:05:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
name := expr.Children[0].Token
|
|
|
|
value := expr.Children[1]
|
|
|
|
return &Define{Name: name, Value: value}, nil
|
|
|
|
|
|
|
|
case IsAssignment(expr):
|
|
|
|
if len(expr.Children) < 2 {
|
2024-07-05 07:50:46 +00:00
|
|
|
return nil, errors.New(errors.MissingAssignValue, nil, expr.Token.End())
|
2024-07-01 13:05:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
name := expr.Children[0].Token
|
|
|
|
value := expr.Children[1]
|
2024-07-03 09:39:24 +00:00
|
|
|
return &Assign{Name: name, Value: value, Operator: expr.Token}, nil
|
2024-07-01 13:05:15 +00:00
|
|
|
|
|
|
|
case IsFunctionCall(expr):
|
|
|
|
return &Call{Expression: expr}, nil
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, nil, expr.Token.Position)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsAssignment returns true if the expression is an assignment.
|
|
|
|
func IsAssignment(expr *expression.Expression) bool {
|
|
|
|
return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '='
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsFunctionCall returns true if the expression is a function call.
|
|
|
|
func IsFunctionCall(expr *expression.Expression) bool {
|
|
|
|
return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ"
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsVariableDefinition returns true if the expression is a variable definition.
|
|
|
|
func IsVariableDefinition(expr *expression.Expression) bool {
|
|
|
|
return expr.Token.Kind == token.Operator && expr.Token.Text() == ":="
|
|
|
|
}
|