// Primary expressions: literals, parens, lists, map literals, CASE, // EXISTS/COUNT subqueries, list-predicate quantifiers, function calls, and // variables. Ambiguity ordering mirrors the Rust grammar: COUNT/EXISTS // followed by '{' are subqueries (count(...) stays the aggregate); the // quantifiers all/any/none/single with a 'var IN' head are list // predicates, otherwise plain function calls. Excluded surface // (comprehensions, reduce, map projections, Cypher path-search functions) // gets targeted errors.
package parser import ( ) // parsePrimary parses one primary expression (no prefix/infix/postfix). func ( *parser) () (ast.Expr, error) { // VALUE { ... } is the ISO scalar subquery -- not yet supported, but // it must say so rather than misparse into the map-projection error. if .peekKw("value") && .peekAt(1).Kind == TokLBrace { return nil, errf(.peek().Pos, "VALUE { ... } scalar subqueries are not supported") } := .peek() switch .Kind { case TokLParen: .i++ , := .parseExpr() if != nil { return nil, } if , := .expect(TokRParen, "')'"); != nil { return nil, } return , nil case TokLBracket: return .parseListLit() case TokLBrace: return .parseMapLit() case TokInt, TokFloat, TokStr, TokParam: , := .parseLiteralTok() if != nil { return nil, } return &ast.Lit{Value: }, nil case TokIdent: return .parseIdentPrimary() } return nil, errf(.Pos, "expected an expression, found %q", .Text) } // parseIdentPrimary handles every identifier-led primary. func ( *parser) () (ast.Expr, error) { := .peek() // Fold the identifier into a stack buffer for the keyword dispatch: a // plain variable or column reference (the common case) matches no keyword // and is not reserved, so it never needs a heap-allocated lowercased copy // -- the switch and reserved probe both run over the non-copying // string(folded) view. An identifier longer than any keyword folds to // ok=false and falls straight through to the variable/function path. var [24]byte , := foldLower(.Text, [:]) if { switch string() { case "true", "false", "null": , := .parseLiteralTok() if != nil { return nil, } return &ast.Lit{Value: }, nil case "case": return .parseCase() case "exists": .i++ , , := .parseBracedMatch("EXISTS") if != nil { return nil, } return &ast.Exists{Pattern: , Where: }, nil case "count": if .peekAt(1).Kind == TokLBrace { .i++ , , := .parseBracedMatch("COUNT") if != nil { return nil, } return &ast.CountSub{Pattern: , Where: }, nil } case "collect": // COLLECT { ... } is the list subquery (the brace form); collect(...) // is the aggregate function call and falls through to parseFuncCall. if .peekAt(1).Kind == TokLBrace { .i++ , , , := .parseBracedCollect() if != nil { return nil, } return &ast.PatternComp{Pattern: , Where: , Proj: }, nil } case "date", "datetime", "zoned_datetime", "localdatetime", "timestamp", "duration": // A temporal keyword directly followed by a string literal is the // GQL temporal-literal form (DATE '2024-01-01'); it lowers to the // matching constructor function. The function-call spelling has a // '(' here instead, so the forms never collide. if .peekAt(1).Kind == TokStr { := string() if == "datetime" || == "timestamp" { = "zoned_datetime" } .i++ , := .parseLiteralTok() if != nil { return nil, } return &ast.Func{Name: , Args: []ast.Expr{&ast.Lit{Value: }}}, nil } case "all", "any", "none", "single": // A quantifier with a `var IN` head is a list predicate; anything // else falls through to a plain function call. if .peekAt(1).Kind == TokLParen && .peekAt(2).Kind == TokIdent && kwIs(.peekAt(3), "in") { return .parseListPred(string()) } case "cast": if .peekAt(1).Kind == TokLParen { return .parseCast() } case "reduce": return nil, errf(.Pos, "reduce(...) is not in the GQL subset") case "shortestpath", "allshortestpaths", "weightedshortestpath": return nil, errf(.Pos, "%s(...) is not GQL: write MATCH p = ANY SHORTEST / ALL SHORTEST <pattern> [COST <expr>]", .Text) case "cost": if .peekAt(1).Kind == TokLParen { return nil, errf(.Pos, "cost(shortestPath(...)) is not in the GQL subset: write MATCH p = ANY SHORTEST <pattern> COST <expr>") } } if reserved[string()] { return nil, errf(.Pos, "reserved word %q cannot start an expression", .Text) } } if .peekAt(1).Kind == TokLParen { return .parseFuncCall() } .i++ return &ast.Var{Name: .Text}, nil } // parseListLit parses a list literal, or a list comprehension // [x IN xs [WHERE pred] [| map]] -- an extension with no ISO GQL // spelling, matching the Rust grammar's ordered choice: a leading // `ident IN` always opens a comprehension (parenthesize a membership // test to keep it a literal element). func ( *parser) () (ast.Expr, error) { .i++ // '[' if := .peek(); .Kind == TokIdent && !isReserved(.Text) && kwIs(.peekAt(1), "in") { return .parseListComp() } := &ast.ListExpr{} // A '(' opening the list is how a pattern comprehension presents when // its pattern cannot parse as an expression (e.g. [(a)-[:R]-(b) | x]), // so an element parse failure there gets the targeted rejection. := .peek().Kind == TokLParen for .peek().Kind != TokRBracket { , := .parseExpr() if != nil { if { return nil, patternCompErr(.peek().Pos) } return nil, } if .peekKw("where") || .peek().Kind == TokPipe { return nil, patternCompErr(.peek().Pos) } .Elems = append(.Elems, ) if !.acceptTok(TokComma) { break } } if , := .expect(TokRBracket, "']' closing a list"); != nil { return nil, } return , nil } // patternCompErr is the targeted pattern-comprehension rejection with its // GQL-conformant rewrites. func patternCompErr( int) *Error { return errf(, "pattern comprehensions are not in the GQL subset: rewrite size([pat | x]) as COUNT { MATCH pat } and value collection as MATCH pat + collect(...)") } // parseListComp parses the comprehension body after '[': var IN list // [WHERE pred] ['|' map] ']'. func ( *parser) () (ast.Expr, error) { , := .identName("a comprehension variable") if != nil { return nil, } if := .expectKw("in"); != nil { return nil, } , := .parseExpr() if != nil { return nil, } := &ast.ListComp{Var: , List: } if .acceptKw("where") { , := .parseExpr() if != nil { return nil, } .Filter = } if .acceptTok(TokPipe) { , := .parseExpr() if != nil { return nil, } .Map = } if , := .expect(TokRBracket, "']' closing a comprehension"); != nil { return nil, } return , nil } // parseCast parses CAST(expr AS TYPE) -- the GQL cast spelling, lowered // to the matching conversion function. func ( *parser) () (ast.Expr, error) { .i += 2 // 'cast' '(' , := .parseExpr() if != nil { return nil, } if := .expectKw("as"); != nil { return nil, } , := .expect(TokIdent, "a CAST target type") if != nil { return nil, } var [16]byte , := foldLower(.Text, [:]) var string switch string() { case "float", "double": = "tofloat" case "int", "integer", "bigint": = "tointeger" case "string", "varchar": = "tostring" case "bool", "boolean": = "toboolean" case "date": = "date" case "datetime", "zoned_datetime", "timestamp": = "zoned_datetime" case "localdatetime": = "localdatetime" case "duration": = "duration" default: return nil, errf(.Pos, "CAST target %q is not supported (FLOAT, INTEGER, STRING, BOOLEAN, DATE, DATETIME, DURATION)", .Text) } if , := .expect(TokRParen, "')' closing the CAST"); != nil { return nil, } return &ast.Func{Name: , Args: []ast.Expr{}}, nil } // parseMapLit parses {key: expr, ...} in expression position. func ( *parser) () (ast.Expr, error) { .i++ // '{' := &ast.MapLit{} for .peek().Kind != TokRBrace { , := .identName("a map key") if != nil { return nil, } if , := .expect(TokColon, "':'"); != nil { return nil, } , := .parseExpr() if != nil { return nil, } .Fields = append(.Fields, ast.MapField{Key: , Val: }) if !.acceptTok(TokComma) { break } } if , := .expect(TokRBrace, "'}' closing a map"); != nil { return nil, } return , nil } // parseCase parses CASE [operand] (WHEN cond THEN result)+ [ELSE default] // END; without an operand it is the searched form. func ( *parser) () (ast.Expr, error) { .i++ // CASE := &ast.Case{} if !.peekKw("when") { , := .parseExpr() if != nil { return nil, } .Operand = } for .acceptKw("when") { , := .parseExpr() if != nil { return nil, } if := .expectKw("then"); != nil { return nil, } , := .parseExpr() if != nil { return nil, } .Whens = append(.Whens, ast.CaseWhen{Cond: , Result: }) } if len(.Whens) == 0 { return nil, errf(.peek().Pos, "CASE needs at least one WHEN") } if .acceptKw("else") { , := .parseExpr() if != nil { return nil, } .Else = } if := .expectKw("end"); != nil { return nil, } return , nil } // parseBracedMatch parses { [MATCH] pattern [WHERE expr] } -- the body of // EXISTS / COUNT subqueries. The MATCH keyword is optional (GQL's bare // EXISTS { <pattern> } spelling). func ( *parser) ( string) (*ast.Pattern, ast.Expr, error) { if , := .expect(TokLBrace, "'{' after "+); != nil { return nil, nil, } .acceptKw("match") , := .parsePattern() if != nil { return nil, nil, } , := .parseOptionalWhere() if != nil { return nil, nil, } if , := .expect(TokRBrace, "'}' closing "+); != nil { return nil, nil, } return , , nil } // parseBracedCollect parses { [MATCH] pattern [WHERE expr] RETURN proj [AS // alias] } -- the body of a COLLECT subquery. Unlike EXISTS/COUNT, RETURN and // a single projection are required (COLLECT must be told what to gather); the // MATCH keyword is optional and an AS alias is accepted but ignored (a list // value carries no column name). func ( *parser) () (*ast.Pattern, ast.Expr, ast.Expr, error) { if , := .expect(TokLBrace, "'{' after COLLECT"); != nil { return nil, nil, nil, } .acceptKw("match") , := .parsePattern() if != nil { return nil, nil, nil, } , := .parseOptionalWhere() if != nil { return nil, nil, nil, } if := .expectKw("return"); != nil { return nil, nil, nil, } , := .parseExpr() if != nil { return nil, nil, nil, } if .acceptKw("as") { if , := .identName("a COLLECT alias"); != nil { return nil, nil, nil, } } if , := .expect(TokRBrace, "'}' closing COLLECT"); != nil { return nil, nil, nil, } return , , , nil } // parseListPred parses quant(var IN list WHERE pred). func ( *parser) ( string) (ast.Expr, error) { var ast.Quant switch { case "all": = ast.QuantAll case "any": = ast.QuantAny case "none": = ast.QuantNone default: = ast.QuantSingle } .i += 2 // quant '(' , := .identName("a quantifier variable") if != nil { return nil, } if := .expectKw("in"); != nil { return nil, } , := .parseExpr() if != nil { return nil, } if := .expectKw("where"); != nil { return nil, } , := .parseExpr() if != nil { return nil, } if , := .expect(TokRParen, "')' closing the quantifier"); != nil { return nil, } return &ast.ListPred{Quant: , Var: , List: , Pred: }, nil } // parseFuncCall parses name([DISTINCT] * | args...). func ( *parser) () (ast.Expr, error) { , := .identName("a function name") if != nil { return nil, } .i++ // '(' := &ast.Func{Name: } .Distinct = .acceptKw("distinct") if .acceptTok(TokStar) { .Star = true } else { for .peek().Kind != TokRParen { , := .parseExpr() if != nil { return nil, } .Args = append(.Args, ) if !.acceptTok(TokComma) { break } } } if , := .expect(TokRParen, "')' closing the call"); != nil { return nil, } // The ISO surface passes NORMALIZE's form as a bare keyword // (NORMALIZE(s, NFC)); it parses as a variable reference, so lower it // to the string literal the evaluator reads (a quoted form works too). if (strings.EqualFold(, "normalize") || strings.EqualFold(, "is_normalized")) && len(.Args) == 2 { if , := .Args[1].(*ast.Var); && isNormForm(.Name) { .Args[1] = &ast.Lit{Value: ast.StrLit(strings.ToUpper(.Name))} } } return , nil } // isNormForm reports whether name is a Unicode normalization form keyword. func isNormForm( string) bool { switch strings.ToUpper() { case "NFC", "NFD", "NFKC", "NFKD": return true } return false }