1use pest::iterators::Pair;
2use pest::Parser;
3use pest_derive::Parser;
4
5use crate::ast::*;
6use crate::error::QueryError;
7
8#[derive(Parser)]
9#[grammar = "cypher.pest"]
10struct CypherParser;
11
12pub fn parse(input: &str) -> Result<Statement, QueryError> {
13 let mut pairs = CypherParser::parse(Rule::query, input)
14 .map_err(|e| QueryError::Parse(e.to_string()))?;
15 let query_pair = pairs.next().expect("query rule always produces one pair");
16 let statement_pair = query_pair
17 .into_inner()
18 .find(|p| p.as_rule() == Rule::statement)
19 .expect("query grammar guarantees a statement");
20 parse_statement(statement_pair)
21}
22
23fn parse_statement(pair: Pair<Rule>) -> Result<Statement, QueryError> {
24 let inner = pair.into_inner().next().expect("statement has one child");
25 match inner.as_rule() {
26 Rule::create_stmt => parse_create_stmt(inner),
27 Rule::match_stmt => parse_match_stmt(inner),
28 r => unreachable!("unexpected statement child rule {r:?}"),
29 }
30}
31
32fn parse_create_stmt(pair: Pair<Rule>) -> Result<Statement, QueryError> {
33 let patterns = pair
34 .into_inner()
35 .filter(|p| p.as_rule() == Rule::pattern)
36 .map(parse_pattern)
37 .collect::<Result<Vec<_>, _>>()?;
38 Ok(Statement::Create(patterns))
39}
40
41fn parse_match_stmt(pair: Pair<Rule>) -> Result<Statement, QueryError> {
42 let mut parts = Vec::new();
43 let mut tail = None;
44 let mut order_by = None;
45 let mut limit = None;
46 for p in pair.into_inner() {
47 match p.as_rule() {
48 Rule::match_part => parts.push(parse_match_part(p)?),
49 Rule::tail_clause => tail = Some(parse_tail_clause(p)?),
50 Rule::order_by_clause => order_by = Some(parse_order_by_clause(p)?),
51 Rule::limit_clause => limit = Some(parse_limit_clause(p)?),
52 r => unreachable!("unexpected match_stmt child rule {r:?}"),
53 }
54 }
55
56 let with_count = parts.iter().filter(|p| p.with.is_some()).count();
65 if with_count > 1 {
66 return Err(QueryError::Parse(
67 "chaining past one WITH boundary in a single MATCH isn't supported yet".into(),
68 ));
69 }
70 for (i, part) in parts.iter().enumerate() {
71 if i + 1 < parts.len() && part.with.is_none() && !parts[i + 1].optional {
72 return Err(QueryError::Parse(
73 "multiple MATCH clauses must be separated by WITH".into(),
74 ));
75 }
76 }
77
78 Ok(Statement::Match {
79 parts,
80 tail: tail.ok_or_else(|| QueryError::Parse("MATCH requires RETURN/DELETE/SET".into()))?,
81 order_by,
82 limit,
83 })
84}
85
86fn parse_match_part(pair: Pair<Rule>) -> Result<QueryPart, QueryError> {
87 let mut optional = false;
88 let mut patterns = Vec::new();
89 let mut where_clause = None;
90 let mut with = None;
91 for p in pair.into_inner() {
92 match p.as_rule() {
93 Rule::match_keyword => {
94 optional = p.as_str().to_ascii_uppercase().starts_with("OPTIONAL");
95 }
96 Rule::pattern => patterns.push(parse_pattern(p)?),
97 Rule::where_clause => {
98 let expr_pair = p.into_inner().next().expect("WHERE has an expr");
99 where_clause = Some(parse_expr(expr_pair)?);
100 }
101 Rule::with_clause => with = Some(parse_with_clause(p)?),
102 r => unreachable!("unexpected match_part child rule {r:?}"),
103 }
104 }
105 let pattern = splice_patterns(patterns)?;
106 Ok(QueryPart {
107 optional,
108 pattern,
109 where_clause,
110 with,
111 })
112}
113
114fn parse_with_clause(pair: Pair<Rule>) -> Result<WithClause, QueryError> {
115 let mut items = Vec::new();
116 let mut order_by = None;
117 let mut limit = None;
118 for p in pair.into_inner() {
119 match p.as_rule() {
120 Rule::return_item => items.push(parse_return_item(p)?),
121 Rule::order_by_clause => order_by = Some(parse_order_by_clause(p)?),
122 Rule::limit_clause => limit = Some(parse_limit_clause(p)?),
123 r => unreachable!("unexpected with_clause child rule {r:?}"),
124 }
125 }
126 Ok(WithClause { items, order_by, limit })
127}
128
129fn parse_order_by_clause(pair: Pair<Rule>) -> Result<Vec<(ReturnExpr, SortDir)>, QueryError> {
130 pair.into_inner()
131 .filter(|c| c.as_rule() == Rule::sort_item)
132 .map(parse_sort_item)
133 .collect()
134}
135
136fn parse_limit_clause(pair: Pair<Rule>) -> Result<i64, QueryError> {
137 let n_pair = pair.into_inner().next().expect("LIMIT has an int_literal");
138 n_pair
139 .as_str()
140 .parse::<i64>()
141 .map_err(|_| QueryError::Parse("invalid LIMIT value".into()))
142}
143
144fn splice_patterns(mut patterns: Vec<Pattern>) -> Result<Pattern, QueryError> {
154 if patterns.is_empty() {
155 return Err(QueryError::Parse("MATCH requires a pattern".into()));
156 }
157 let mut combined = patterns.remove(0);
158 for next in patterns {
159 let Some(start_var) = next.start.var.clone() else {
160 return Err(QueryError::Parse(
161 "a comma-separated MATCH pattern must start from a named variable".into(),
162 ));
163 };
164 let last_var = combined
165 .hops
166 .last()
167 .map(|(_, n)| n.var.clone())
168 .unwrap_or_else(|| combined.start.var.clone());
169 if last_var.as_deref() != Some(start_var.as_str()) {
170 return Err(QueryError::Parse(format!(
171 "comma-separated MATCH pattern must continue from the previous pattern's last \
172 variable ('{}'), not '{start_var}' — general cross-joins aren't supported",
173 last_var.unwrap_or_default()
174 )));
175 }
176 let target = match combined.hops.last_mut() {
177 Some((_, node)) => node,
178 None => &mut combined.start,
179 };
180 target.labels.extend(next.start.labels);
181 target.props.extend(next.start.props);
182 combined.hops.extend(next.hops);
183 }
184 Ok(combined)
185}
186
187fn parse_sort_item(pair: Pair<Rule>) -> Result<(ReturnExpr, SortDir), QueryError> {
188 let mut inner = pair.into_inner();
189 let expr = parse_return_expr(inner.next().expect("sort_item has a return_expr"))?;
190 let dir = match inner.next() {
191 Some(d) if d.as_str().eq_ignore_ascii_case("desc") => SortDir::Desc,
192 _ => SortDir::Asc,
193 };
194 Ok((expr, dir))
195}
196
197fn parse_tail_clause(pair: Pair<Rule>) -> Result<Tail, QueryError> {
198 let inner = pair.into_inner().next().expect("tail_clause has one child");
199 match inner.as_rule() {
200 Rule::return_clause => {
201 let items = inner
202 .into_inner()
203 .filter(|p| p.as_rule() == Rule::return_item)
204 .map(parse_return_item)
205 .collect::<Result<Vec<_>, _>>()?;
206 Ok(Tail::Return(items))
207 }
208 Rule::detach_delete_clause => {
209 let vars = inner
210 .into_inner()
211 .filter(|p| p.as_rule() == Rule::identifier)
212 .map(|p| p.as_str().to_string())
213 .collect();
214 Ok(Tail::DetachDelete(vars))
215 }
216 Rule::delete_clause => {
217 let vars = inner
218 .into_inner()
219 .filter(|p| p.as_rule() == Rule::identifier)
220 .map(|p| p.as_str().to_string())
221 .collect();
222 Ok(Tail::Delete(vars))
223 }
224 Rule::set_clause => {
225 let items = inner
226 .into_inner()
227 .filter(|p| p.as_rule() == Rule::set_item)
228 .map(parse_set_item)
229 .collect::<Result<Vec<_>, _>>()?;
230 Ok(Tail::Set(items))
231 }
232 r => unreachable!("unexpected tail_clause child rule {r:?}"),
233 }
234}
235
236fn parse_set_item(pair: Pair<Rule>) -> Result<(PropAccess, Literal), QueryError> {
237 let mut inner = pair.into_inner();
238 let prop_access_pair = inner.next().expect("set_item has a prop_access");
239 let literal_pair = inner.next().expect("set_item has a literal");
240 Ok((parse_prop_access(prop_access_pair), parse_literal(literal_pair)?))
241}
242
243fn parse_return_item(pair: Pair<Rule>) -> Result<ReturnItem, QueryError> {
244 let mut inner = pair.into_inner();
245 let expr_pair = inner.next().expect("return_item has a return_expr");
246 let expr = parse_return_expr(expr_pair)?;
247 let alias = inner.next().map(|p| p.as_str().to_string());
248 Ok(ReturnItem { expr, alias })
249}
250
251fn parse_return_expr(pair: Pair<Rule>) -> Result<ReturnExpr, QueryError> {
252 let inner = pair.into_inner().next().expect("return_expr has one child");
253 match inner.as_rule() {
254 Rule::case_expr => parse_case_expr(inner),
255 Rule::function_call => parse_function_call(inner),
256 Rule::prop_access => Ok(ReturnExpr::Prop(parse_prop_access(inner))),
257 Rule::literal => Ok(ReturnExpr::Lit(parse_literal(inner)?)),
258 Rule::identifier => Ok(ReturnExpr::Var(inner.as_str().to_string())),
259 r => unreachable!("unexpected return_expr child rule {r:?}"),
260 }
261}
262
263fn parse_case_expr(pair: Pair<Rule>) -> Result<ReturnExpr, QueryError> {
264 let mut inner = pair.into_inner();
265 let test = parse_return_expr(inner.next().expect("case_expr has a test expr"))?;
266 let mut whens = Vec::new();
267 let mut else_ = None;
268 for p in inner {
269 match p.as_rule() {
270 Rule::case_when => {
271 let mut when_inner = p.into_inner();
272 let when = parse_return_expr(when_inner.next().expect("case_when has a WHEN expr"))?;
273 let then = parse_return_expr(when_inner.next().expect("case_when has a THEN expr"))?;
274 whens.push((when, then));
275 }
276 _ => else_ = Some(Box::new(parse_return_expr(p)?)),
278 }
279 }
280 Ok(ReturnExpr::Case {
281 test: Some(Box::new(test)),
282 whens,
283 else_,
284 })
285}
286
287fn parse_function_call(pair: Pair<Rule>) -> Result<ReturnExpr, QueryError> {
288 let mut inner = pair.into_inner();
289 let name = inner.next().expect("function_call has a name").as_str().to_string();
290 let args = inner.map(parse_return_expr).collect::<Result<Vec<_>, _>>()?;
291 Ok(ReturnExpr::Call(name, args))
292}
293
294fn parse_prop_access(pair: Pair<Rule>) -> PropAccess {
295 let mut inner = pair.into_inner();
296 let var = inner.next().expect("prop_access has a var").as_str().to_string();
297 let prop = inner.next().expect("prop_access has a prop").as_str().to_string();
298 PropAccess { var, prop }
299}
300
301fn parse_pattern(pair: Pair<Rule>) -> Result<Pattern, QueryError> {
302 let mut inner = pair.into_inner();
303 let start = parse_node_pattern(inner.next().expect("pattern has a start node"))?;
304 let mut hops = Vec::new();
305 loop {
306 let Some(rel_pair) = inner.next() else { break };
307 let node_pair = inner
308 .next()
309 .ok_or_else(|| QueryError::Parse("dangling relationship in pattern".into()))?;
310 hops.push((parse_rel_pattern(rel_pair)?, parse_node_pattern(node_pair)?));
311 }
312 Ok(Pattern { start, hops })
313}
314
315fn parse_node_pattern(pair: Pair<Rule>) -> Result<NodePattern, QueryError> {
316 let mut var = None;
317 let mut labels = Vec::new();
318 let mut props = Vec::new();
319 for p in pair.into_inner() {
320 match p.as_rule() {
321 Rule::node_var => var = Some(p.as_str().to_string()),
322 Rule::node_label => {
323 labels.push(p.into_inner().next().expect("node_label has an identifier").as_str().to_string())
324 }
325 Rule::prop_map => props = parse_prop_map(p)?,
326 r => unreachable!("unexpected node_pattern child rule {r:?}"),
327 }
328 }
329 Ok(NodePattern { var, labels, props })
330}
331
332fn parse_rel_pattern(pair: Pair<Rule>) -> Result<RelPattern, QueryError> {
333 let inner = pair.into_inner().next().expect("rel_pattern has one child");
334 let direction = match inner.as_rule() {
335 Rule::rel_right => RelDirection::Right,
336 Rule::rel_left => RelDirection::Left,
337 Rule::rel_either => RelDirection::Either,
338 r => unreachable!("unexpected rel_pattern child rule {r:?}"),
339 };
340 let mut var = None;
341 let mut rel_type = None;
342 let mut props = Vec::new();
343 let mut hop_range = None;
344 for p in inner.into_inner() {
345 match p.as_rule() {
346 Rule::rel_var => var = Some(p.as_str().to_string()),
347 Rule::rel_type => {
348 rel_type = Some(p.into_inner().next().expect("rel_type has an identifier").as_str().to_string())
349 }
350 Rule::rel_range => hop_range = Some(parse_rel_range(p.as_str())?),
351 Rule::prop_map => props = parse_prop_map(p)?,
352 r => unreachable!("unexpected rel_right/rel_left/rel_either child rule {r:?}"),
353 }
354 }
355 Ok(RelPattern {
356 var,
357 rel_type,
358 props,
359 direction,
360 hop_range,
361 })
362}
363
364fn parse_rel_range(text: &str) -> Result<(u32, Option<u32>), QueryError> {
369 let rest = &text[1..]; if rest.is_empty() {
371 return Ok((0, None));
372 }
373 if let Some(idx) = rest.find("..") {
374 let min_str = &rest[..idx];
375 let max_str = &rest[idx + 2..];
376 let min = if min_str.is_empty() {
377 0
378 } else {
379 min_str
380 .parse()
381 .map_err(|_| QueryError::Parse("invalid variable-length min hop count".into()))?
382 };
383 let max = if max_str.is_empty() {
384 None
385 } else {
386 Some(
387 max_str
388 .parse()
389 .map_err(|_| QueryError::Parse("invalid variable-length max hop count".into()))?,
390 )
391 };
392 Ok((min, max))
393 } else {
394 let n: u32 = rest
395 .parse()
396 .map_err(|_| QueryError::Parse("invalid variable-length hop count".into()))?;
397 Ok((n, Some(n)))
398 }
399}
400
401fn parse_prop_map(pair: Pair<Rule>) -> Result<Vec<(String, Literal)>, QueryError> {
402 pair.into_inner()
403 .filter(|p| p.as_rule() == Rule::prop_kv)
404 .map(|p| {
405 let mut inner = p.into_inner();
406 let key = inner.next().expect("prop_kv has a key").as_str().to_string();
407 let value = parse_literal(inner.next().expect("prop_kv has a value"))?;
408 Ok((key, value))
409 })
410 .collect()
411}
412
413fn parse_literal(pair: Pair<Rule>) -> Result<Literal, QueryError> {
414 let inner = pair.into_inner().next().expect("literal has one child");
415 Ok(match inner.as_rule() {
416 Rule::int_literal => Literal::Int(
417 inner
418 .as_str()
419 .parse()
420 .map_err(|_| QueryError::Parse("invalid integer literal".into()))?,
421 ),
422 Rule::float_literal => Literal::Float(
423 inner
424 .as_str()
425 .parse()
426 .map_err(|_| QueryError::Parse("invalid float literal".into()))?,
427 ),
428 Rule::string_literal => {
429 let s = inner.as_str();
430 Literal::String(s[1..s.len() - 1].to_string())
431 }
432 Rule::bool_literal => Literal::Bool(inner.as_str().eq_ignore_ascii_case("true")),
433 Rule::null_literal => Literal::Null,
434 Rule::param => {
435 let name = inner.into_inner().next().expect("param has an identifier").as_str().to_string();
436 Literal::Param(name)
437 }
438 r => unreachable!("unexpected literal child rule {r:?}"),
439 })
440}
441
442fn parse_expr(pair: Pair<Rule>) -> Result<Expr, QueryError> {
443 parse_or_expr(pair.into_inner().next().expect("expr has an or_expr"))
445}
446
447fn parse_or_expr(pair: Pair<Rule>) -> Result<Expr, QueryError> {
448 let mut parts = pair.into_inner();
449 let mut acc = parse_and_expr(parts.next().expect("or_expr has at least one and_expr"))?;
450 for rest in parts {
451 acc = Expr::Or(Box::new(acc), Box::new(parse_and_expr(rest)?));
452 }
453 Ok(acc)
454}
455
456fn parse_and_expr(pair: Pair<Rule>) -> Result<Expr, QueryError> {
457 let mut parts = pair.into_inner();
458 let mut acc = parse_unary_expr(parts.next().expect("and_expr has at least one unary_expr"))?;
459 for rest in parts {
460 acc = Expr::And(Box::new(acc), Box::new(parse_unary_expr(rest)?));
461 }
462 Ok(acc)
463}
464
465fn parse_unary_expr(pair: Pair<Rule>) -> Result<Expr, QueryError> {
466 let inner = pair.into_inner().next().expect("unary_expr has one child");
467 match inner.as_rule() {
468 Rule::unary_expr => Ok(Expr::Not(Box::new(parse_unary_expr(inner)?))),
469 Rule::comparison => parse_comparison(inner),
470 Rule::expr => parse_expr(inner),
471 r => unreachable!("unexpected unary_expr child rule {r:?}"),
472 }
473}
474
475fn parse_comparison(pair: Pair<Rule>) -> Result<Expr, QueryError> {
476 let mut inner = pair.into_inner();
477 let prop_access = parse_prop_access(inner.next().expect("comparison has a prop_access"));
478 let op_pair = inner.next().expect("comparison has a compare_op");
479 let op = match op_pair.as_str() {
480 "=" => CompareOp::Eq,
481 "<>" => CompareOp::Ne,
482 "<" => CompareOp::Lt,
483 "<=" => CompareOp::Le,
484 ">" => CompareOp::Gt,
485 ">=" => CompareOp::Ge,
486 other => unreachable!("unexpected compare_op {other:?}"),
487 };
488 let literal = parse_literal(inner.next().expect("comparison has a literal"))?;
489 Ok(Expr::Compare(prop_access, op, literal))
490}