A Rust parser for the Cypher® property graph language, based on the openCypher specification.
This project is independent and is not affiliated with, endorsed by, or sponsored by Neo4j, Inc. Cypher® and Neo4j® are registered trademarks of Neo4j, Inc.
decypher provides a typed AST for Cypher® and openCypher queries, built on a hand-written error-resilient rowan parser derived from the openCypher EBNF specification.
Note: This project is a fork and complete re-implementation of the original pest-based parser by Austin Poor. The parser has been rewritten from the ground up using rowan instead of pest.
Add to your Cargo.toml:
[dependencies]
decypher = "0.2"use decypher::parse;
let query = parse("MATCH (n:Person) WHERE n.age > 18 RETURN n.name;").unwrap();
println!("{:#?}", query);- Typed AST — Every Cypher construct maps to a Rust enum or struct with full type safety.
- Source spans — Every AST node carries a
Span { start, end }(byte offsets into the input) for diagnostics. - Ergonomic errors —
CypherErrorwith syntax, AST build, and unsupported-production variants viathiserror. - Cypher emission — The
ToCyphertrait renders any AST node back into valid openCypher text, enabling round-trips (parse → ast → to_cypher → parse). serdesupport — Optionalserdefeature forSerialize/Deserializederives on all AST nodes.- Typed CST (unstable) — A rust-analyzer-style typed wrapper layer over a lossless rowan CST, available under
decypher::cst. Each CST node (SourceFile,MatchClause,Expression, …) exposes typed accessor methods instead of rawSyntaxKindmatches. This is what the publicparse()function uses internally.
use decypher::cst::{parse, AstNode, BinOp, Expression};
let result = parse("MATCH (n:Person) WHERE n.age > 18 RETURN n.name");
let source = result.tree();
for stmt in source.statements() {
for clause in stmt.clauses() {
// use pattern matching on the Clause enum
}
}The ToCypher trait converts AST nodes back into openCypher text. This is useful for query rewriting, formatting, and round-trip testing.
use decypher::ast::ToCypher;
use decypher::parse;
let query = parse("MATCH (n:Person) WHERE n.age > 18 RETURN n.name;").unwrap();
let cypher = query.to_cypher();
check!(cypher.contains("MATCH"));The AST is unstable until 0.2.0. Grammar completeness is tracked against the openCypher EBNF. Unsupported grammar productions return CypherError::Unsupported rather than panicking.
The ToCypher trait and round-trip guarantees are also unstable — formatting output may change between releases until 0.2.0.
src/parser/— Hand-written error-resilient rowan parser (grammar + lexer).src/syntax/— Rowan language definition and typed CST wrappers.src/ast/— Typed AST node definitions and the CST → AST lowering logic (src/ast/build_cst/).src/error.rs—CypherErrorandSpantypes.src/lib.rs— Public API (parse,Query,CypherError).assets/cypher.ebnf— OpenCypher grammar definition from the openCypher site.
Contributions of any size are welcome! Please feel free to submit issues or PRs.
This library code is triple-licensed under EUPL-1.2, MIT, or Apache-2.0; choose whichever suits your use case best.
Cypher® and Neo4j® are registered trademarks of Neo4j, Inc.
