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" ) // Parse generates an AST from a list of tokens. func Parse(tokens []token.Token, buffer []byte) (AST, error) { tree := make(AST, 0, len(tokens)/64) err := EachInstruction(tokens, func(instruction token.List) error { node, err := toASTNode(instruction, buffer) 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, 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()) } name := expr.Children[0].Token value := expr.Children[1] return &Define{Name: name, Value: value}, 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. func IsAssignment(expr *expression.Expression) bool { return expr.Token.IsAssignment() } // IsFunctionCall returns true if the expression is a function call. func IsFunctionCall(expr *expression.Expression) bool { return expr.Token.Kind == token.Call } // IsVariableDefinition returns true if the expression is a variable definition. func IsVariableDefinition(expr *expression.Expression) bool { 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 }