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/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 { word := tokens[0].Text() if word == keyword.Return { if len(tokens) == 1 { return &Return{}, nil } value := expression.Parse(tokens[1:]) return &Return{Value: value}, nil } if keywordHasBlock(word) { 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]) switch word { case keyword.If: condition := expression.Parse(tokens[1:blockStart]) return &If{Condition: condition, Body: body}, err case keyword.Loop: return &Loop{Body: body}, err } } 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 { 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()) } name := expr.Children[0].Token value := expr.Children[1] return &Assign{Name: name, Value: value, Operator: expr.Token}, 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() == ":=" } // keywordHasBlock returns true if the keyword requires a block. func keywordHasBlock(word string) bool { return word == keyword.If || word == keyword.Loop }