118 lines
3.0 KiB
Go
Raw Normal View History

package ast
import (
2024-07-03 09:39:24 +00:00
"git.akyoto.dev/cli/q/src/build/errors"
"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 {
2024-07-16 10:01:38 +00:00
word := tokens[0].Text()
if word == keyword.Return {
2024-07-16 18:22:28 +00:00
if len(tokens) == 1 {
return &Return{}, nil
}
value := expression.Parse(tokens[1:])
return &Return{Value: value}, nil
2024-07-16 10:01:38 +00:00
}
2024-07-16 10:01:38 +00:00
if keywordHasBlock(word) {
2024-07-07 12:07:34 +00:00
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())
}
2024-07-07 12:07:34 +00:00
body, err := Parse(tokens[blockStart+1 : blockEnd])
2024-07-07 10:30:57 +00:00
2024-07-16 10:01:38 +00:00
switch word {
case keyword.If:
condition := expression.Parse(tokens[1:blockStart])
return &If{Condition: condition, Body: body}, err
2024-07-07 10:30:57 +00:00
2024-07-16 10:01:38 +00:00
case keyword.Loop:
return &Loop{Body: body}, err
2024-07-07 10:30:57 +00:00
}
}
2024-07-16 10:01:38 +00:00
return nil, errors.New(&errors.KeywordNotImplemented{Keyword: word}, 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 15:11:30 +00:00
return nil, errors.New(errors.MissingOperand, nil, expr.Token.End())
}
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 15:11:30 +00:00
return nil, errors.New(errors.MissingOperand, nil, expr.Token.End())
}
2024-07-20 15:35:26 +00:00
return &Assign{Expression: expr}, nil
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() == ":="
}
2024-07-16 10:01:38 +00:00
// keywordHasBlock returns true if the keyword requires a block.
func keywordHasBlock(word string) bool {
return word == keyword.If || word == keyword.Loop
}