Cypher-lite front-end: tokenize then parse·6 min
The Cypher-lite grammar
0 / 1
Time to build a query language. Full openCypher is enormous (WITH, aggregation, OPTIONAL MATCH, MERGE, CREATE...), so we scope a precise, finishable subset:
MATCH <pattern> [WHERE <predicate>] RETURN [DISTINCT] <items> [ORDER BY <key> [ASC|DESC]] [LIMIT n]
Patterns: node (var:Label {k:v}), relationship -[:TYPE]->, <-[:TYPE]-, -[:TYPE]-, var-length -[:TYPE*min..max]-. Predicates: a.prop OP literal (=, <>, <, >, <=, >=) joined by AND. Literals: string, int, boolean.
The pipeline every query engine shares
text ─▶ tokenizer ─▶ parser ─▶ AST ─▶ executor ─▶ rows
(Ch.5) (Ch.5) (Ch.6)
you build the left half this chapter, the right half next
src/ast.ts
export interface NodePattern {
variable: string | null;
label: string | null;
props: Array<{ key: string; value: Literal }>;
}
export interface RelPattern {
type: string | null;
dir: 'OUT' | 'IN' | 'BOTH';
varLength: { min: number; max: number } | null;
}
export interface PathPattern {
start: NodePattern;
segments: Array<{ rel: RelPattern; node: NodePattern }>;
}
export interface Query {
match: PathPattern;
where: Predicate | null;
distinct: boolean;
returnItems: ReturnItem[];
orderBy: OrderBy[];
limit: number | null;
}
One true shape
Grading-by-AST works only because there is one canonical shape — published in src/ast.ts. You import these types; the only freedom is the values, which the input determines. Build your AST to match these interfaces exactly.