// graphdblite Cypher subset grammar (PEG)
WHITESPACE = _{ " " | "\t" | "\r" | "\n" }
COMMENT = _{ "//" ~ (!"\n" ~ ANY)* }
// === Top-level ===
statement = { SOI ~ union_stmt ~ ";"? ~ EOI }
union_stmt = { single_stmt ~ (union_op ~ single_stmt)* }
union_op = { ^"UNION" ~ ^"ALL"? }
single_stmt = { explain_stmt | create_index_stmt | drop_index_stmt | multi_clause_stmt | match_stmt | unwind_stmt | with_stmt | match_create_stmt | match_merge_stmt | delete_stmt | set_stmt | remove_stmt | create_stmt | merge_stmt | call_stmt | return_stmt }
explain_stmt = { ^"EXPLAIN" ~ (multi_clause_stmt | match_create_stmt | with_stmt | match_stmt | unwind_stmt) }
// === Multi-clause statement (arbitrary sequences of updating + reading clauses) ===
// Handles query forms that specialized rules cannot parse:
// CREATE...MERGE, MERGE...MERGE, MATCH...WITH...CREATE, etc.
//
// The rule requires at least one CREATE or MERGE, preceded or followed by
// at least one other clause. This prevents it from stealing simple patterns
// like MATCH...DELETE or MATCH...SET that have their own rules.
multi_clause_stmt = {
// Pattern A: at least one non-write clause before CREATE/MERGE (handles MATCH...CREATE, MATCH...WITH...CREATE, etc.)
(multi_clause_non_write+ ~
(multi_create_clause | multi_merge_clause) ~
multi_clause_any* ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?)
|
// Pattern B: CREATE/MERGE followed by at least one more clause (handles CREATE...MERGE, MERGE...MERGE, etc.)
((multi_create_clause | multi_merge_clause) ~
multi_clause_any+ ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?)
|
// Pattern C: 2+ non-write clauses followed by DELETE/SET/REMOVE (+ optional more clauses)
// Handles: MATCH...WITH...DELETE...RETURN, MATCH...WITH...SET...RETURN, etc.
// Requires 2+ leading clauses to avoid stealing simple MATCH...DELETE from delete_stmt.
(multi_clause_non_write ~ multi_clause_non_write+ ~
(multi_delete_clause | multi_set_clause | multi_remove_clause) ~
multi_clause_any* ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?)
|
// Pattern D: 1 non-write clause + DELETE/SET/REMOVE + at least one more clause
// Handles: MATCH...REMOVE...WITH...RETURN, MATCH...SET...WITH...RETURN, etc.
// The trailing clause(s) make it unambiguously multi-clause (not a simple set/remove/delete stmt).
(multi_clause_non_write ~
(multi_delete_clause | multi_set_clause | multi_remove_clause) ~
multi_clause_any+ ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?)
|
// Pattern E: CALL followed by at least one more clause (CALL...WITH, CALL...CALL, etc.)
(multi_call_clause ~
multi_clause_non_write_or_call+ ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?)
|
// Pattern F: Non-write clause(s) followed by CALL (MATCH...CALL...RETURN)
(multi_clause_non_write+ ~
multi_call_clause ~
multi_clause_non_write_or_call* ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?)
}
// Non-write clause (MATCH, WITH, UNWIND) — used for leading clauses.
multi_clause_non_write = _{
with_clause | multi_match_clause | multi_unwind_clause
}
// Non-write clause including CALL — used in Pattern E.
multi_clause_non_write_or_call = _{
with_clause | multi_match_clause | multi_unwind_clause | multi_call_clause
}
// Any clause type — used for trailing clauses after the first CREATE/MERGE.
multi_clause_any = _{
with_clause |
multi_match_clause |
multi_create_clause |
multi_merge_clause |
multi_unwind_clause |
multi_set_clause |
multi_remove_clause |
multi_delete_clause |
multi_call_clause
}
multi_match_clause = { (^"MATCH" ~ pattern_list ~ where_clause? ~ optional_match_clause*) | optional_match_clause+ }
multi_create_clause = { ^"CREATE" ~ create_pattern_list }
multi_merge_clause = { ^"MERGE" ~ pattern_item ~ (on_create_clause ~ on_match_clause? | on_match_clause ~ on_create_clause?)? }
multi_unwind_clause = { ^"UNWIND" ~ expr ~ ^"AS" ~ ident }
multi_call_clause = { ^"CALL" ~ procedure_name ~ ("(" ~ (expr ~ ("," ~ expr)*)? ~ ")")? ~ yield_clause? }
multi_set_clause = { ^"SET" ~ assignment_list }
multi_remove_clause = { ^"REMOVE" ~ remove_item_list }
multi_delete_clause = { detach_keyword? ~ ^"DELETE" ~ delete_expr_list }
delete_expr_list = { expr ~ ("," ~ expr)* }
// === Standalone RETURN (no preceding MATCH) ===
return_stmt = {
return_clause ~
order_by_clause? ~
skip_clause? ~
limit_clause?
}
// === CALL procedure ===
call_stmt = {
^"CALL" ~ procedure_name ~ ("(" ~ (expr ~ ("," ~ expr)*)? ~ ")")? ~
yield_clause? ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?
}
procedure_name = @{ ident ~ ("." ~ ident)+ }
// === DDL: CREATE / DROP INDEX ===
create_index_stmt = {
^"CREATE" ~ ^"INDEX" ~ ^"ON" ~ ":" ~ symbolic_name ~
"(" ~ ident ~ ("," ~ ident)* ~ ")"
}
drop_index_stmt = {
^"DROP" ~ ^"INDEX" ~ ^"ON" ~ ":" ~ symbolic_name ~
"(" ~ ident ~ ("," ~ ident)* ~ ")"
}
yield_clause = {
^"YIELD" ~ (yield_star | yield_items)
}
yield_star = { "*" }
yield_items = {
yield_item ~ ("," ~ yield_item)*
}
yield_item = {
ident ~ (^"AS" ~ ident)?
}
// === Standalone WITH...RETURN ===
with_stmt = {
with_clause ~
((with_clause ~ match_part?) | match_part | unwind_clause)* ~
return_clause ~
order_by_clause? ~
skip_clause? ~
limit_clause?
}
// === MATCH statement ===
match_stmt = {
match_part ~
(match_part | (with_clause ~ match_part?) | unwind_clause)* ~
return_clause ~
order_by_clause? ~
skip_clause? ~
limit_clause?
}
match_part = {
(^"MATCH" ~ pattern_list ~ where_clause? ~ optional_match_clause*) |
(optional_match_clause+)
}
with_clause = { ^"WITH" ~ distinct_keyword? ~ return_items ~ order_by_clause? ~ skip_clause? ~ limit_clause? ~ where_clause? }
optional_match_clause = { ^"OPTIONAL" ~ ^"MATCH" ~ pattern_list ~ where_clause? }
// === UNWIND statement ===
unwind_stmt = {
^"UNWIND" ~ expr ~ ^"AS" ~ ident ~
(unwind_create | unwind_return)
}
unwind_create = {
^"CREATE" ~ create_pattern_list ~
((with_clause ~ match_part?) | unwind_clause)* ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?
}
unwind_return = {
where_clause? ~
((with_clause ~ match_part?) | unwind_clause)* ~
return_clause ~
order_by_clause? ~
skip_clause? ~
limit_clause?
}
// === UNWIND clause (within MATCH) ===
unwind_clause = { ^"UNWIND" ~ expr ~ ^"AS" ~ ident }
// === CREATE statement ===
create_stmt = {
^"CREATE" ~ create_pattern_list ~
(^"CREATE" ~ create_pattern_list)* ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?
}
create_pattern_list = { create_pattern ~ ("," ~ create_pattern)* }
create_pattern = { node_pattern ~ (rel_pattern ~ node_pattern)* }
// === MATCH ... CREATE statement ===
match_create_stmt = {
^"MATCH" ~ pattern_list ~
where_clause? ~
^"CREATE" ~ create_pattern_list ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?
}
// === MATCH ... MERGE statement ===
match_merge_stmt = {
^"MATCH" ~ pattern_list ~
where_clause? ~
^"MERGE" ~ pattern_item ~
(on_create_clause ~ on_match_clause? | on_match_clause ~ on_create_clause?)? ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?
}
// === DELETE statement ===
delete_stmt = {
(^"MATCH" ~ pattern_list ~ optional_match_clause* | optional_match_clause+) ~
where_clause? ~
detach_keyword? ~ ^"DELETE" ~ delete_expr_list ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?
}
detach_keyword = { ^"DETACH" }
// === SET statement ===
set_stmt = {
(^"MATCH" ~ pattern_list ~ optional_match_clause* | optional_match_clause+) ~
where_clause? ~
^"SET" ~ assignment_list ~
(with_clause ~ match_part?)* ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?
}
assignment_list = { set_item ~ ("," ~ set_item)* }
set_item = { set_label | set_map_merge | set_map | assignment }
set_label = { ident ~ label_spec }
set_map_merge = { ident ~ "+=" ~ expr }
set_map = { ident ~ "=" ~ expr }
assignment = { (property_access | expr_property_access) ~ "=" ~ expr }
expr_property_access = { "(" ~ expr ~ ")" ~ "." ~ property_key }
// Property-only assignment list (for MERGE ON CREATE/ON MATCH).
property_assignment_list = { assignment ~ ("," ~ assignment)* }
// === REMOVE statement ===
remove_stmt = {
(^"MATCH" ~ pattern_list ~ optional_match_clause* | optional_match_clause+) ~
where_clause? ~
^"REMOVE" ~ remove_item_list ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?
}
remove_item_list = { remove_item ~ ("," ~ remove_item)* }
remove_item = { remove_label | remove_property }
remove_property = { property_access }
remove_label = { ident ~ label_spec }
// === MERGE statement ===
merge_stmt = {
^"MERGE" ~ pattern_item ~
(on_create_clause ~ on_match_clause? | on_match_clause ~ on_create_clause?)? ~
(return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)?
}
on_create_clause = { ^"ON" ~ ^"CREATE" ~ ^"SET" ~ assignment_list }
on_match_clause = { ^"ON" ~ ^"MATCH" ~ ^"SET" ~ assignment_list }
// === Patterns ===
pattern_list = { pattern_item ~ ("," ~ pattern_item)* }
pattern_item = { path_pattern | pattern }
path_pattern = { ident ~ "=" ~ (shortest_path_fn | all_shortest_paths_fn | pattern) }
shortest_path_fn = { ^"shortestPath" ~ "(" ~ pattern ~ ")" }
all_shortest_paths_fn = { ^"allShortestPaths" ~ "(" ~ pattern ~ ")" }
pattern = { node_pattern ~ (rel_pattern ~ node_pattern)* }
node_pattern = { "(" ~ ident? ~ label_spec? ~ property_map? ~ ")" }
label_spec = { (":" ~ symbolic_name)+ }
rel_pattern = { rel_both | rel_right | rel_left | rel_undirected | rel_right_bare | rel_both_bare | rel_left_bare | rel_undirected_bare }
rel_both = { "<-[" ~ rel_detail ~ "]->" }
rel_right = { "-[" ~ rel_detail ~ "]->" }
rel_left = { "<-[" ~ rel_detail ~ "]-" }
rel_undirected = { "-[" ~ rel_detail ~ "]-" }
rel_right_bare = { "-->" }
rel_left_bare = { "<--" }
rel_both_bare = { "<-->" }
rel_undirected_bare = { "--" }
rel_detail = { ident? ~ rel_type_spec? ~ property_map? ~ var_length? }
rel_type_spec = { ":" ~ symbolic_name ~ ("|" ~ ":"? ~ symbolic_name)* }
var_length = { "*" ~ (int_range | fixed_length)? ~ property_map? }
int_range = { integer? ~ ".." ~ integer? }
fixed_length = { integer }
// === Clauses ===
where_clause = { ^"WHERE" ~ bool_expr }
return_clause = { ^"RETURN" ~ distinct_keyword? ~ return_items }
distinct_keyword = { ^"DISTINCT" }
return_items = { return_item ~ ("," ~ return_item)* }
return_item = { expr ~ alias? }
alias = { ^"AS" ~ (backtick_ident | symbolic_name) }
order_by_clause = { ^"ORDER" ~ ^"BY" ~ sort_items }
sort_items = { sort_item ~ ("," ~ sort_item)* }
sort_item = { expr ~ sort_direction? }
sort_direction = { ^"DESCENDING" | ^"ASCENDING" | ^"DESC" | ^"ASC" }
skip_clause = { ^"SKIP" ~ expr }
limit_clause = { ^"LIMIT" ~ expr }
// === Expressions ===
bool_expr = { expr }
xor_term = { bool_term ~ (xor_op ~ bool_term)* }
bool_term = { bool_factor ~ (and_op ~ bool_factor)* }
bool_factor = { not_op* ~ bool_primary }
bool_primary = { cmp_or_value }
// Comparison: predicate_expr (comp_op predicate_expr)*
// Supports chained comparisons like `1 < x < 3` → `1 < x AND x < 3`.
// Predicates (IS NULL, IS NOT NULL, IN, string predicates) bind tighter than comparison.
cmp_or_value = { predicate_expr ~ comp_suffix* }
comp_suffix = { comp_op ~ predicate_expr }
// Predicate expressions: cmp_primary with optional IS NULL / IS NOT NULL / IN / string predicate suffix.
predicate_expr = { cmp_primary ~ (is_not_null_suffix | is_null_suffix | in_suffix | string_pred_suffix)? }
cmp_primary = { exists_full_subquery | exists_subquery | pattern_predicate | add_expr | "(" ~ expr ~ ")" }
pattern_predicate = { node_pattern ~ (rel_pattern ~ node_pattern)+ }
is_not_null_suffix = { is_kw ~ not_kw ~ null_kw }
is_null_suffix = { is_kw ~ null_kw }
in_suffix = { in_kw ~ add_expr }
string_pred_suffix = { string_pred_op ~ add_expr }
string_pred_op = { starts_with_op | ends_with_op | contains_op | regex_match_op }
is_kw = @{ ^"IS" ~ !(ASCII_ALPHANUMERIC | "_") }
in_kw = @{ ^"IN" ~ !(ASCII_ALPHANUMERIC | "_") }
null_kw = @{ ^"NULL" ~ !(ASCII_ALPHANUMERIC | "_") }
not_kw = @{ ^"NOT" ~ !(ASCII_ALPHANUMERIC | "_") }
exists_subquery = { ^"EXISTS" ~ "{" ~ pattern_list ~ where_clause? ~ "}" }
exists_full_subquery = { ^"EXISTS" ~ "{" ~ ^"MATCH" ~ pattern_list ~ where_clause? ~ ((with_clause ~ (^"MATCH" ~ pattern_list ~ where_clause?)?) | unwind_clause | multi_set_clause | multi_delete_clause | multi_remove_clause | multi_create_clause | multi_merge_clause)* ~ (return_clause ~ order_by_clause? ~ skip_clause? ~ limit_clause?)? ~ "}" }
case_expr = {
^"CASE"
~ case_operand?
~ case_when_clause+
~ case_else_clause?
~ ^"END"
}
case_operand = { !^"WHEN" ~ expr }
case_when_clause = { ^"WHEN" ~ expr ~ ^"THEN" ~ expr }
case_else_clause = { ^"ELSE" ~ expr }
or_op = @{ ^"OR" ~ !(ASCII_ALPHANUMERIC | "_") }
xor_op = @{ ^"XOR" ~ !(ASCII_ALPHANUMERIC | "_") }
and_op = @{ ^"AND" ~ !(ASCII_ALPHANUMERIC | "_") }
not_op = @{ ^"NOT" ~ !(ASCII_ALPHANUMERIC | "_") }
comp_op = { "<>" | "<=" | ">=" | "<" | ">" | "=" }
starts_with_op = { ^"STARTS" ~ ^"WITH" }
ends_with_op = { ^"ENDS" ~ ^"WITH" }
contains_op = { ^"CONTAINS" }
regex_match_op = { "=~" }
expr = { xor_term ~ (or_op ~ xor_term)* }
in_expr = { add_expr ~ (^"IN" ~ add_expr)? }
add_expr = { mul_expr ~ (add_op ~ mul_expr)* }
mul_expr = { exp_expr ~ (mul_op ~ exp_expr)* }
exp_expr = { atom_expr ~ (exp_op ~ atom_expr)* }
exp_op = { "^" }
add_op = { "+" | "-" }
mul_op = { "*" | "/" | "%" }
atom_expr = { atom_primary ~ (subscript | dot_access)* }
dot_access = { "." ~ property_key }
atom_primary = _{ case_expr | quantifier_expr | dotted_function_call | function_call | unknown_function_call | property_access | has_label_expr | literal | pattern_comprehension | list_comprehension | list_literal | map_literal | star | parameter | unary_minus_expr | "(" ~ expr ~ ")" | variable }
unary_minus_expr = { "-" ~ atom_expr }
has_label_expr = { ident ~ label_spec }
dotted_function_call = { dotted_function_name ~ "(" ~ function_args ~ ")" }
dotted_function_name = @{
^"datetime.fromepochmillis" | ^"datetime.fromepoch" |
^"date.truncate" | ^"localtime.truncate" | ^"time.truncate" |
^"localdatetime.truncate" | ^"datetime.truncate" |
^"duration.between" | ^"duration.inMonths" | ^"duration.inDays" | ^"duration.inSeconds" |
^"date.transaction" | ^"date.statement" | ^"date.realtime" |
^"localtime.transaction" | ^"localtime.statement" | ^"localtime.realtime" |
^"time.transaction" | ^"time.statement" | ^"time.realtime" |
^"localdatetime.transaction" | ^"localdatetime.statement" | ^"localdatetime.realtime" |
^"datetime.transaction" | ^"datetime.statement" | ^"datetime.realtime"
}
quantifier_expr = { quantifier_name ~ "(" ~ ident ~ in_kw ~ expr ~ where_clause ~ ")" }
quantifier_name = @{ (^"none" | ^"single" | ^"any" | ^"all") ~ !(ASCII_ALPHANUMERIC | "_") }
subscript = { "[" ~ subscript_inner ~ "]" }
subscript_inner = { slice_full | slice_from | slice_to | expr }
slice_full = { expr ~ ".." ~ expr }
slice_from = { expr ~ ".." }
slice_to = { ".." ~ expr }
parameter = { "$" ~ param_name }
param_name = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* | ASCII_DIGIT+ }
pattern_comprehension = { "[" ~ (ident ~ "=")? ~ pattern ~ (^"WHERE" ~ bool_expr)? ~ "|" ~ expr ~ "]" }
list_comprehension = { "[" ~ ident ~ ^"IN" ~ expr ~ list_comp_where? ~ list_comp_map? ~ "]" }
list_comp_where = { ^"WHERE" ~ bool_expr }
list_comp_map = { "|" ~ expr }
list_literal = { "[" ~ (expr ~ ("," ~ expr)*)? ~ "]" }
// Map literal in expression context: {k: expr, k2: expr2, ...}. Uses distinct
// rule names from `property_map` (which is used in node/edge patterns with
// slightly different semantics and requires at least one pair).
map_literal = { "{" ~ (map_pair ~ ("," ~ map_pair)*)? ~ "}" }
map_pair = { map_key ~ ":" ~ expr }
map_key = @{ backtick_ident | symbolic_name }
property_access = { ident ~ "." ~ property_key }
variable = { ident }
star = { "*" }
function_call = { function_name ~ "(" ~ function_args ~ ")" }
// Catch-all for misspelled function names. The planner rejects these with an
// `UnknownFunction` error and a typo suggestion. Must come AFTER function_call
// in `atom_primary` so legitimate names match first.
unknown_function_call = { plain_ident ~ "(" ~ function_args ~ ")" }
function_name = @{ ^"count" | ^"sum" | ^"avg" | ^"min" | ^"max" | ^"collect" | ^"percentileDisc" | ^"percentileCont" | ^"stDev" | ^"stDevP" | ^"length" | ^"nodes" | ^"relationships" | ^"toLower" | ^"toUpper" | ^"toBoolean" | ^"toString" | ^"toInteger" | ^"toFloat" | ^"keys" | ^"labels" | ^"id" | ^"coalesce" | ^"type" | ^"head" | ^"tail" | ^"last" | ^"size" | ^"abs" | ^"sqrt" | ^"sign" | ^"ceil" | ^"floor" | ^"round" | ^"log10" | ^"log" | ^"startNode" | ^"endNode" | ^"exists" | ^"exp" | ^"e" | ^"pi" | ^"substring" | ^"left" | ^"right" | ^"lTrim" | ^"rTrim" | ^"replace" | ^"split" | ^"trim" | ^"reverse" | ^"range" | ^"rand" | ^"properties" | ^"localdatetime" | ^"localtime" | ^"datetime" | ^"date" | ^"duration" | ^"time" }
function_args = { star | distinct_keyword ~ expr_list | expr_list | "" }
expr_list = { expr ~ ("," ~ expr)* }
// === Property maps ===
property_map = { "{" ~ property_pair ~ ("," ~ property_pair)* ~ "}" }
property_pair = { property_key ~ ":" ~ expr }
// Property keys allow keywords (e.g. {exists: 42}) since the colon provides
// unambiguous context, similar to label_spec using symbolic_name.
property_key = @{ backtick_ident | ((ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")*) }
// === Identifiers and literals ===
ident_list = { ident ~ ("," ~ ident)* }
ident = @{ backtick_ident | fn_name_as_ident | plain_ident }
fn_name_as_ident = @{ function_name_word ~ !("(" | ASCII_ALPHANUMERIC | "_") }
function_name_word = _{
^"count" | ^"sum" | ^"avg" | ^"min" | ^"max" | ^"collect" |
^"percentileDisc" | ^"percentileCont" | ^"stDev" | ^"stDevP" |
^"length" | ^"nodes" | ^"relationships" |
^"toLower" | ^"toUpper" | ^"toBoolean" | ^"toString" | ^"toInteger" | ^"toFloat" |
^"keys" | ^"labels" | ^"id" | ^"coalesce" | ^"type" | ^"head" | ^"tail" | ^"last" | ^"size" |
^"abs" | ^"sqrt" | ^"sign" | ^"ceil" | ^"floor" | ^"round" | ^"log10" | ^"log" |
^"startNode" | ^"endNode" | ^"exists" | ^"exp" | ^"e" | ^"pi" |
^"substring" | ^"left" | ^"right" | ^"lTrim" | ^"rTrim" | ^"replace" | ^"split" | ^"trim" | ^"reverse" |
^"range" | ^"rand" | ^"properties" |
^"localdatetime" | ^"localtime" | ^"datetime" | ^"date" | ^"duration" | ^"time"
}
plain_ident = @{ !keyword ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
backtick_ident = @{ "`" ~ (!"`" ~ ANY)+ ~ "`" }
// Like ident but allows keywords — used for labels and relationship types
// where the leading ":" provides unambiguous context.
symbolic_name = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
keyword = {
(^"MATCH" | ^"WHERE" | ^"RETURN" | ^"CREATE" | ^"DELETE" | ^"DETACH" | ^"SET" | ^"REMOVE" |
^"MERGE" | ^"ORDER" | ^"BY" | ^"SKIP" | ^"LIMIT" | ^"AND" | ^"OR" | ^"NOT" |
^"AS" | ^"IS" | ^"ON" | ^"ASCENDING" | ^"DESCENDING" | ^"ASC" | ^"DESC" | ^"STARTS" | ^"ENDS" | ^"WITH" | ^"CONTAINS" |
^"OPTIONAL" | ^"UNWIND" | ^"TRUE" | ^"FALSE" | ^"NULL" |
^"CASE" | ^"WHEN" | ^"THEN" | ^"ELSE" | ^"END" | ^"EXISTS" | ^"IN" | ^"DISTINCT" | ^"UNION" | ^"ALL" | ^"XOR" |
^"NONE" | ^"SINGLE" | ^"ANY" |
^"shortestPath" | ^"allShortestPaths" | ^"EXPLAIN" |
^"count" | ^"sum" | ^"avg" | ^"min" | ^"max" | ^"collect" | ^"length" | ^"nodes")
~ !(ASCII_ALPHANUMERIC | "_")
}
literal = { float_literal | integer_literal | string_literal | bool_literal | null_literal }
integer_literal = @{ "-"? ~ (hex_integer | octal_integer | ASCII_DIGIT+) }
hex_integer = { ("0x" | "0X") ~ ASCII_HEX_DIGIT+ }
octal_integer = { ("0o" | "0O") ~ ASCII_OCT_DIGIT+ }
float_literal = @{
"-"? ~ (
ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT+ ~ exponent? |
"." ~ ASCII_DIGIT+ ~ exponent? |
ASCII_DIGIT+ ~ exponent
)
}
exponent = { ("e" | "E") ~ ("+" | "-")? ~ ASCII_DIGIT+ }
string_literal = ${ single_quoted_string | double_quoted_string }
single_quoted_string = ${ "'" ~ string_inner_sq ~ "'" }
double_quoted_string = ${ "\"" ~ string_inner_dq ~ "\"" }
string_inner_sq = @{ (!"'" ~ !"\\" ~ ANY | "\\" ~ ANY)* }
string_inner_dq = @{ (!"\"" ~ !"\\" ~ ANY | "\\" ~ ANY)* }
bool_literal = { ^"TRUE" | ^"FALSE" }
null_literal = { ^"NULL" }
integer = @{ ASCII_DIGIT+ }