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 { 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 { 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() == ":=" }