Skip to main content

uqa_graph/cypher/
parser.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Recursive-descent parser for the supported openCypher grammar.
8
9use std::collections::BTreeMap;
10
11use uqa_core::Value;
12
13use crate::cypher::ast::{
14    BinaryOp, CaseExpr, CreateClause, CypherClause, CypherExpr, CypherQuery, DeleteClause,
15    FunctionCall, InList, IsNotNull, IsNull, ListComprehension, ListIndex, ListLiteral, ListSlice,
16    Literal, MapLiteral, MatchClause, MergeClause, NodePattern, OrderByItem, Parameter,
17    PathElement, PathPattern, PropertyAccess, RelDirection, RelPattern, ReturnClause, ReturnItem,
18    SetClause, SetItem, SetOperator, UnaryOp, UnwindClause, Variable, WithClause,
19};
20use crate::cypher::lexer::{is_keyword, tokenize, LexError, Token, TokenKind};
21
22mod atoms;
23mod clauses;
24mod expressions;
25mod limits;
26mod patterns;
27mod stream;
28
29#[derive(Debug, thiserror::Error, PartialEq)]
30pub enum ParseError {
31    #[error(transparent)]
32    Lex(#[from] LexError),
33    #[error("expected {expected}, got {got:?} ({value:?}) at position {position}")]
34    Expected {
35        expected: &'static str,
36        got: TokenKind,
37        value: String,
38        position: usize,
39    },
40    #[error("expected keyword {keyword:?}, got {got:?} at position {position}")]
41    ExpectedKeyword {
42        keyword: &'static str,
43        got: String,
44        position: usize,
45    },
46    #[error("unexpected token {got:?} at position {position}")]
47    Unexpected { got: String, position: usize },
48    #[error("Cypher expression nesting limit of {limit} exceeded at position {position}")]
49    ExpressionTooDeep { limit: usize, position: usize },
50}
51
52/// Parse a Cypher query string into a Cypher query AST.
53pub fn parse_cypher(source: &str) -> Result<CypherQuery, ParseError> {
54    let tokens = tokenize(source)?;
55    let mut parser = Parser {
56        tokens,
57        pos: 0,
58        expression_recursion: 0,
59    };
60    parser.parse()
61}
62
63struct Parser {
64    tokens: Vec<Token>,
65    pos: usize,
66    expression_recursion: usize,
67}
68
69const RESERVED_KEYWORDS: &[&str] = &[
70    "AND",
71    "AS",
72    "ASC",
73    "BY",
74    "CASE",
75    "CONTAINS",
76    "CREATE",
77    "DELETE",
78    "DESC",
79    "DETACH",
80    "DISTINCT",
81    "ELSE",
82    "END",
83    "ENDS",
84    "EXISTS",
85    "FALSE",
86    "IN",
87    "IS",
88    "LIMIT",
89    "MATCH",
90    "MERGE",
91    "NODE",
92    "NOT",
93    "NULL",
94    "ON",
95    "OPTIONAL",
96    "OR",
97    "ORDER",
98    "RELATIONSHIP",
99    "REMOVE",
100    "RETURN",
101    "SET",
102    "SKIP",
103    "STARTS",
104    "THEN",
105    "TRUE",
106    "UNWIND",
107    "WHEN",
108    "WHERE",
109    "WITH",
110    "XOR",
111];
112
113#[cfg(test)]
114mod tests;