Documentation
¶
Overview ¶
Package parser translates the ANTLR4-generated Cypher parse tree into the typed AST defined in github.com/FlavioCFOliveira/GoGraph/cypher/ast. The generated sources (under cypher/parser/gen/) are produced by the ANTLR4 tool and must not be hand-edited.
Build the parser from the grammar with:
go generate ./cypher/parser/...
Error Recovery Contract ¶
The parser uses ANTLR's default single-token insertion/deletion strategy (antlr.DefaultErrorStrategy). Under this strategy:
On a syntax error the parser attempts to recover by either inserting a missing single token or deleting the current token. This allows parsing to continue past isolated mistakes and collect further errors.
A maximum of [maxParseErrors] errors (currently 5) are collected per parse. Once the cap is reached, additional errors are silently dropped. This prevents cascading error floods on pathological inputs where a single structural mistake causes the parser to mis-identify every subsequent token as erroneous.
When at least one error is present the parse tree may be partial: some subtrees may have been constructed using the inserted/deleted tokens produced by error recovery. Callers must not rely on the AST being semantically correct when errors are returned.
Parse returns only the first error. ParseStrict returns all collected errors (up to the cap) and is intended for tooling such as editors and linters that benefit from seeing multiple errors at once.
Concurrency ¶
Parse and ParseStrict are safe to call concurrently. Each invocation creates independent lexer, parser, and error-listener instances; no state is shared between calls.
Package parser translates the antlr4-generated parse tree into the typed AST defined in github.com/FlavioCFOliveira/GoGraph/cypher/ast.
Index ¶
Examples ¶
Constants ¶
const AutoParamPrefix = " auto_"
AutoParamPrefix is the reserved name prefix StripLiterals gives the parameters it extracts. It contains spaces, and the rewritten text always quotes the name in backticks, so it cannot collide with a user parameter: an unquoted openCypher parameter name is an identifier or a decimal index, and neither can contain a space.
Variables ¶
This section is empty.
Functions ¶
func IsAutoParam ¶ added in v0.11.0
IsAutoParam reports whether name was produced by StripLiterals.
It exists because an auto-parameter must behave like the LITERAL it replaced, not like a parameter the caller supplied. The engine deliberately surfaces a type-incompatible user parameter as a typed error rather than silently matching nothing, while openCypher says a type-incompatible literal simply compares false and yields zero rows. Hoisting must not convert the second behaviour into the first, so the parameter type check skips these names.
func Parse ¶
Parse lexes and parses a Cypher query string and converts the resulting parse tree into a typed AST node. It returns the first error encountered.
Errors:
- *ParseError — syntax error from the ANTLR lexer/parser.
- *SemaError — unsupported grammar rule encountered during tree walking.
Example ¶
ExampleParse demonstrates basic Cypher parsing. The returned AST can be inspected or pretty-printed by downstream tooling.
package main
import (
"fmt"
"github.com/FlavioCFOliveira/GoGraph/cypher/parser"
)
func main() {
q, err := parser.Parse("MATCH (n:Person) RETURN n.name")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("ok, type:", fmt.Sprintf("%T", q))
}
Output: ok, type: *ast.SingleQuery
Example (InspectAST) ¶
ExampleParse_inspectAST shows how to inspect the AST root that Parse returns. A single-part query parses to *ast.SingleQuery, whose reading clauses and RETURN can then be walked by downstream stages.
package main
import (
"fmt"
"github.com/FlavioCFOliveira/GoGraph/cypher/ast"
"github.com/FlavioCFOliveira/GoGraph/cypher/parser"
)
func main() {
q, err := parser.Parse("MATCH (n:Person) RETURN n.name")
if err != nil {
fmt.Println("error:", err)
return
}
sq, ok := q.(*ast.SingleQuery)
if !ok {
fmt.Printf("unexpected root: %T\n", q)
return
}
fmt.Println("reading clauses:", len(sq.ReadingClauses))
fmt.Println("has RETURN:", sq.Return != nil)
if _, isMatch := sq.ReadingClauses[0].(*ast.Match); isMatch {
fmt.Println("first clause: MATCH")
}
}
Output: reading clauses: 1 has RETURN: true first clause: MATCH
func ParseStrict ¶
ParseStrict lexes and parses a Cypher query string and returns all syntax errors encountered rather than only the first. When the query is syntactically valid the AST is walked for semantic errors; a single *SemaError is returned in that case.
This function is intended for tooling (editors, linters) that need the full error set. Application code should use Parse.
Errors:
- One or more *ParseError — syntax errors from lexer/parser.
- A single *SemaError — unsupported grammar rule or structural violation.
Example ¶
ExampleParseStrict demonstrates multi-error collection for tooling use cases such as editors and linters. ParseStrict reports all syntax errors (up to the internal cap) rather than stopping at the first.
package main
import (
"errors"
"fmt"
"github.com/FlavioCFOliveira/GoGraph/cypher/parser"
)
func main() {
// Two independent syntax errors separated by a semicolon.
_, errs := parser.ParseStrict("RETURN , ; RETURN ,")
if len(errs) == 0 {
fmt.Println("no errors")
return
}
for _, e := range errs {
var pe *parser.ParseError
if errors.As(e, &pe) {
fmt.Printf("syntax error at %d:%d\n", pe.Line, pe.Column)
}
}
}
Output: syntax error at 1:7 syntax error at 1:11
func StripLiterals ¶ added in v0.11.0
StripLiterals rewrites query so that the string literals it can safely hoist become parameter references, and returns the rewritten text together with the values those parameters must be bound to. ok reports whether anything was hoisted; when it is false the caller must use query unchanged.
Why ¶
The plan cache is keyed on query text, so `{sk: 'a'}` and `{sk: 'b'}` are two different queries: each distinct literal re-parses and re-plans. Measured against the same query written with a parameter, that cost 65% more processor time per query (docs/cpu-vs-neo4j-memgraph-2026-08-11.md §6). Hoisting the literal collapses every spelling onto one cache entry.
It pays only because scanning is far cheaper than parsing: on the audit's point-lookup query, parsing costs 15.0 µs and analysing and translating a further ~1 µs, so avoiding the parse is nearly the whole saving. This scanner is a single pass over the bytes and costs ~330 ns.
Why a hand-written scanner ¶
The generated ANTLR lexer cannot be used for this. Its tokenisation is context-dependent in exactly the places that matter: `'p42'` arrives as ERRCHAR, ID, ERRCHAR rather than as a string token, and a bare `40` is sometimes ID and sometimes DIGIT, with the parser reinterpreting both later. Reproducing that reinterpretation here would be a second implementation of a subtle rule. Memgraph reached the same conclusion and hand-wrote its stripper rather than reuse its ANTLR lexer.
What it hoists, and what it will not ¶
Only STRING literals, and only inside a MATCH pattern or a WHERE predicate. Both limits were set by measurement rather than caution:
- Numbers are left alone. A number can appear where a parameter is invalid or plan-changing — the bounds of a variable-length pattern (`[r*1..3]`), SKIP and LIMIT — and has forms (hexadecimal, octal, exponent) the parser reinterprets from ID tokens.
- Every clause other than MATCH and WHERE is skipped. An earlier version hoisted everywhere except projections and regressed five TCK scenarios: a procedure argument (`CALL test.my.proc('Stefan', 1)`) and a map literal fed to `SET r += {...}` are both positions where a literal is not simply an expression whose value may arrive by parameter. RETURN and WITH are skipped too, because an unaliased projection is named after its own source text — `RETURN 'x'` yields a column called `'x'`.
Skipping a hoistable literal costs one cache entry. Hoisting one that should not be hoisted changes what the query means, so every uncertainty here resolves towards skipping.
Types ¶
type ParseError ¶
type ParseError struct {
// OffendingToken is the text of the token that triggered the error.
// It is empty for lexer errors where no token was formed.
OffendingToken string
// Message is the raw ANTLR error message, included as a fallback.
Message string
// Expected is the human-readable list of token names that were valid at
// the error position. It is nil when the expected set cannot be determined
// (e.g. lexer errors).
Expected []string
// Line and Column of the first problematic token (1-based line, 0-based column).
Line int
Column int
}
ParseError wraps ANTLR syntax errors reported during lexing or parsing.
func AsParseErrors ¶
func AsParseErrors(errs []error) ([]*ParseError, []error)
AsParseErrors returns all *ParseError values from an error slice produced by ParseStrict. Non-ParseError values are included as-is.
This is a convenience helper for callers that need to separate parse errors from sema errors.
func CheckQueryLength ¶ added in v0.2.0
func CheckQueryLength(query string) *ParseError
CheckQueryLength returns a *ParseError when query exceeds the maximum accepted query length (1 MiB), and nil otherwise. It is the byte-length half of [guardInput], exported so the DDL parse path — which routes through github.com/FlavioCFOliveira/GoGraph/cypher/ir.ParseDDL rather than this package's Parse — enforces the SAME cap with the SAME error message, keeping the limit defined in exactly one place ([maxQueryBytes]). The DDL tokeniser is iterative, so the nesting half of the guard is not needed there.
The returned error is a non-nil *ParseError when the limit is exceeded; the typed nil is deliberately avoided (callers compare the interface against nil).
func (*ParseError) Error ¶
func (e *ParseError) Error() string
Error returns a human-readable description of the syntax error.
Format: "unexpected '<token>' at <line>:<col>, expected one of {A, B, C}" When OffendingToken is empty the "unexpected" clause is omitted. When Expected is empty the "expected" clause is omitted.
type SemaError ¶
type SemaError struct {
// Rule is the grammar rule name that triggered the error (e.g. "foreach").
Rule string
// Message is a human-readable description of the problem.
Message string
// Pos is the source position of the offending node.
Pos ast.Position
}
SemaError is returned by the visitor when a parse-tree node corresponds to a grammar rule that is not supported in the read+write+DDL+procedure scope (FOREACH, CALL{}, multi-graph) or when a structural semantic constraint is violated.