Skip to main content

lora_parser/parser/
mod.rs

1//! Pest-driven Cypher parser: lower a raw query string into a [`lora_ast::Document`].
2//!
3//! Layout:
4//! - `query` — top-of-tree query-shape lowerings (`regular_query`,
5//!   `single_query` (grouped into WITH-separated parts),
6//!   `reading_clause`, `updating_clause`).
7//! - `clauses` — clause-level lowerings (MATCH, UNWIND, CREATE, MERGE,
8//!   DELETE, SET, REMOVE, CALL, YIELD, WITH, WHERE, RETURN, projection
9//!   body / items, ORDER BY, sort items).
10//! - `patterns` — graph-pattern lowerings (pattern, pattern part,
11//!   shortest-path pattern, pattern element + chain, node / relationship
12//!   patterns, range literals, properties).
13//! - `expressions` — operator-precedence chain from `expression` down
14//!   through comparison / add / mul / pow / unary / postfix and CASE.
15//! - `literals` — leaves of the expression tree: literals, strings,
16//!   maps, lists, comprehensions, parameters, list predicates, REDUCE,
17//!   EXISTS subqueries, function calls, name-part / variable / symbolic
18//!   / schema-name / integer-literal lowerings.
19//! - `util` — small helpers shared across the lowerings (`single_inner`,
20//!   `pair_span`, `merge_spans`, `unexpected_rule`).
21//! - `tests` — unit tests covering the parser surface.
22
23use crate::errors::ParseError;
24use lora_ast::*;
25use pest::iterators::Pair;
26use pest::Parser;
27
28mod clauses;
29mod expressions;
30mod limits;
31mod literals;
32mod patterns;
33mod query;
34mod schema;
35mod util;
36
37#[cfg(test)]
38mod tests;
39
40use clauses::lower_standalone_call;
41use query::lower_regular_query;
42use schema::lower_schema_command;
43use util::{pair_span, unexpected_rule};
44
45#[derive(pest_derive::Parser)]
46#[grammar = "cypher.pest"]
47pub(super) struct LoraParser;
48
49pub fn parse_query(input: &str) -> Result<Document, ParseError> {
50    limits::reset_budgets();
51    let nesting = limits::check_nesting(input)?;
52    // pest's recursive descent has no stack guards of its own: make room
53    // for all of it up front.
54    lora_ast::stack::guard_with(limits::parse_stack(nesting), || {
55        let mut pairs = LoraParser::parse(Rule::query, input)
56            .map_err(|e| ParseError::new(e.to_string(), 0, input.len()))?;
57
58        let pair = pairs
59            .next()
60            .ok_or_else(|| ParseError::new("expected query", 0, input.len()))?;
61
62        lower_query(pair)
63    })
64}
65
66fn lower_query(pair: Pair<Rule>) -> Result<Document, ParseError> {
67    let span = pair_span(&pair);
68    let mut inner = pair.into_inner();
69
70    let stmt = inner
71        .find(|p| p.as_rule() == Rule::statement)
72        .ok_or_else(|| ParseError::new("expected statement", span.start, span.end))?;
73
74    Ok(Document {
75        statement: lower_statement(stmt)?,
76        span,
77    })
78}
79
80fn lower_statement(pair: Pair<Rule>) -> Result<Statement, ParseError> {
81    let inner = util::single_inner(pair)?;
82    match inner.as_rule() {
83        Rule::regular_query => Ok(Statement::Query(Query::Regular(lower_regular_query(
84            inner,
85        )?))),
86        Rule::standalone_call => Ok(Statement::Query(Query::StandaloneCall(
87            lower_standalone_call(inner)?,
88        ))),
89        Rule::schema_command => Ok(Statement::Schema(lower_schema_command(inner)?)),
90        _ => Err(unexpected_rule("statement", inner)),
91    }
92}