codenexus 0.3.4

A queryable code knowledge graph tool built on LadybugDB and tree-sitter
// Copyright (c) 2026 Kirky.X. All rights reserved.
// SPDX-License-Identifier: MIT
//
// Cypher subset grammar for H12 query validation.
//
// Supported clauses: MATCH, OPTIONAL MATCH, WHERE, WITH, RETURN, ORDER BY,
// LIMIT, SKIP, DISTINCT, UNWIND, UNION [ALL], EXISTS, variable-length paths
// (`-[r*1..3]->`), aggregations (count/sum/avg/min/max/collect via func_call).
//
// Unsupported constructs (CREATE, DELETE, SET, MERGE, REMOVE, CALL, FOREACH,
// LOAD CSV, etc.) fail to parse → explicit QueryError at the Rust boundary.

WHITESPACE = _{ " " | "\t" | "\n" | "\r" | comment }
comment = _{ "//" ~ (!"\n" ~ ANY)* }

query = { SOI ~ statement ~ ";"? ~ EOI }
statement = { clause+ }

clause = _{
      optional_match
    | match_clause
    | where_clause
    | with_clause
    | return_clause
    | unwind_clause
    | union_clause
}

optional_match = { kw_optional ~ kw_match ~ pattern }
match_clause   = { kw_match ~ pattern }
where_clause   = { kw_where ~ expr }
with_clause    = { kw_with ~ distinct? ~ return_list ~ order_by? ~ skip? ~ limit? }
return_clause  = { kw_return ~ distinct? ~ return_list ~ order_by? ~ skip? ~ limit? }
unwind_clause  = { kw_unwind ~ expr ~ kw_as ~ var }
union_clause   = { kw_union ~ kw_all? }
distinct       = { kw_distinct }

// --- Patterns ---------------------------------------------------------------
pattern       = { node_pattern ~ (rel_pattern ~ node_pattern)* }
node_pattern  = { "(" ~ var? ~ label? ~ props? ~ ")" }
label         = { (":" ~ ident)+ }
props         = { "{" ~ (prop ~ ("," ~ prop)*)? ~ "}" }
prop          = { ident ~ ":" ~ expr }

rel_pattern = {
      "<-" ~ bracket_rel ~ "->"
    | "<-" ~ bracket_rel ~ "-"
    | "-"  ~ bracket_rel ~ "->"
    | "-"  ~ bracket_rel ~ "-"
}
bracket_rel = { "[" ~ var? ~ rel_type? ~ var_length? ~ "]" }
rel_type    = { ":" ~ ident ~ ("|" ~ ":" ~ ident)* }
var_length  = { "*" ~ range? }
range       = { number? ~ (".." ~ number?)? }

// --- Return / With lists ----------------------------------------------------
return_list  = { return_star | (return_item ~ ("," ~ return_item)*) }
return_star  = { "*" }
return_item  = { expr ~ (kw_as ~ var)? }

// --- Modifiers --------------------------------------------------------------
order_by  = { kw_order ~ kw_by ~ order_item ~ ("," ~ order_item)* }
order_item = { expr ~ direction? }
direction = _{ kw_asc | kw_desc | kw_ascending | kw_descending }
limit     = { kw_limit ~ number }
skip      = { kw_skip ~ number }

// --- Expressions ------------------------------------------------------------
expr         = { or_expr }
or_expr      = { and_expr ~ (kw_or ~ and_expr)* }
and_expr     = { not_expr ~ (kw_and ~ not_expr)* }
not_expr     = { (kw_not)* ~ comparison }
comparison   = {
    additive ~ (
        (comp_op ~ additive)
      | (kw_starts ~ kw_with ~ additive)
      | (kw_ends ~ kw_with ~ additive)
      | (kw_contains ~ additive)
      | (kw_in ~ additive)
      | (kw_is ~ kw_not? ~ kw_null)
    )?
}
comp_op  = _{ "<=" | ">=" | "<>" | "!=" | "=~" | "=" | "<" | ">" }
additive = { mult ~ (add_op ~ mult)* }
add_op   = _{ "+" | "-" }
mult     = { unary ~ (mul_op ~ unary)* }
mul_op   = _{ "*" | "/" | "%" }
unary    = { (unary_op)* ~ primary }
unary_op = _{ "-" | "+" }

primary = _{
      list_lit
    | number
    | string_lit
    | kw_true
    | kw_false
    | kw_null
    | exists_expr
    | func_call
    | property_access
    | var
    | "(" ~ expr ~ ")"
}
list_lit       = { "[" ~ (expr ~ ("," ~ expr)*)? ~ "]" }
exists_expr    = { kw_exists ~ "(" ~ (pattern | expr) ~ ")" }
func_call      = { ident ~ "(" ~ (distinct? ~ func_args?)? ~ ")" }
func_args      = { func_arg ~ ("," ~ func_arg)* }
func_arg       = _{ star | expr }
star           = { "*" }
property_access = { var ~ ("." ~ ident)+ }
var            = { ident }

// --- Tokens -----------------------------------------------------------------
ident       = @{ ident_start ~ ident_rest* }
ident_start = @{ ASCII_ALPHA | "_" }
ident_rest  = @{ ASCII_ALPHANUMERIC | "_" }
number      = @{ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? }
string_lit  = @{ ("\"" ~ (!"\"" ~ ANY)* ~ "\"") | ("'" ~ (!"'" ~ ANY)* ~ "'") }

// --- Keywords (atomic, case-insensitive, word-bounded) ----------------------
// `!ident_rest` ensures e.g. `MATCH` does not match the prefix of `MATCHING`.
kw_optional  = @{ ^"OPTIONAL" ~ !ident_rest }
kw_match     = @{ ^"MATCH" ~ !ident_rest }
kw_where     = @{ ^"WHERE" ~ !ident_rest }
kw_with      = @{ ^"WITH" ~ !ident_rest }
kw_return    = @{ ^"RETURN" ~ !ident_rest }
kw_unwind    = @{ ^"UNWIND" ~ !ident_rest }
kw_union     = @{ ^"UNION" ~ !ident_rest }
kw_all       = @{ ^"ALL" ~ !ident_rest }
kw_as        = @{ ^"AS" ~ !ident_rest }
kw_order     = @{ ^"ORDER" ~ !ident_rest }
kw_by        = @{ ^"BY" ~ !ident_rest }
kw_limit     = @{ ^"LIMIT" ~ !ident_rest }
kw_skip      = @{ ^"SKIP" ~ !ident_rest }
kw_distinct  = @{ ^"DISTINCT" ~ !ident_rest }
kw_and       = @{ ^"AND" ~ !ident_rest }
kw_or        = @{ ^"OR" ~ !ident_rest }
kw_not       = @{ ^"NOT" ~ !ident_rest }
kw_in        = @{ ^"IN" ~ !ident_rest }
kw_is        = @{ ^"IS" ~ !ident_rest }
kw_null      = @{ ^"NULL" ~ !ident_rest }
kw_true      = @{ ^"TRUE" ~ !ident_rest }
kw_false     = @{ ^"FALSE" ~ !ident_rest }
kw_exists    = @{ ^"EXISTS" ~ !ident_rest }
kw_starts    = @{ ^"STARTS" ~ !ident_rest }
kw_ends      = @{ ^"ENDS" ~ !ident_rest }
kw_contains  = @{ ^"CONTAINS" ~ !ident_rest }
kw_asc       = @{ ^"ASC" ~ !ident_rest }
kw_desc      = @{ ^"DESC" ~ !ident_rest }
kw_ascending = @{ ^"ASCENDING" ~ !ident_rest }
kw_descending= @{ ^"DESCENDING" ~ !ident_rest }