Build your own knowledge graph database
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.