Skip to main content

akar_parser/parser/
expression.rs

1//! Expression parsing — arithmetic, boolean, string, list, map, case, function calls.
2
3use super::Rule;
4use super::dml::{parse_property_kv, parse_query_pairs};
5use crate::ast::*;
6
7/// Parse any expression rule to an AST node.
8pub fn parse_expression(pair: pest::iterators::Pair<Rule>) -> Result<Expression, String> {
9    // Handle compound binary expressions by collecting children
10    let rule = pair.as_rule();
11    let children: Vec<_> = pair.clone().into_inner().collect();
12
13    // Handle CASE expression
14    if rule == Rule::case_expr {
15        return parse_case_expr(pair);
16    }
17
18    // Handle comparison_expr specially — it can have 1, 2, or 3 children depending
19    // on which operator is used (IS NULL, IN, STARTS WITH, =, etc.)
20    if rule == Rule::comparison_expr {
21        if children.len() == 1 {
22            return parse_expression(children[0].clone());
23        }
24        let left = parse_expression(children[0].clone())?;
25        if children.len() == 2 {
26            // Postfix/special operators (IS NULL, IN, STARTS WITH, ...)
27            return parse_comparison_suffix(children[1].clone(), left);
28        }
29        if children.len() == 3 {
30            // Standard comparison: left comparison_op right
31            let op_str = children[1].as_str();
32            let op = match op_str {
33                "=" => BinaryOp::Equal,
34                "<>" => BinaryOp::NotEqual,
35                "<" => BinaryOp::LessThan,
36                ">" => BinaryOp::GreaterThan,
37                "<=" => BinaryOp::LessThanOrEqual,
38                ">=" => BinaryOp::GreaterThanOrEqual,
39                _ => return Err(format!("Unknown comparison_op: {}", op_str)),
40            };
41            let right = parse_expression(children[2].clone())?;
42            return Ok(Expression::BinaryOp(op, Box::new(left), Box::new(right)));
43        }
44        return Err(format!("Unexpected comparison_expr with {} children", children.len()));
45    }
46
47    // Unwrap single-child wrappers (priority/precedence levels)
48    if matches!(
49        rule,
50        Rule::expression
51            | Rule::or_expr
52            | Rule::xor_expr
53            | Rule::and_expr
54            | Rule::not_expr
55            | Rule::additive_expr
56            | Rule::multiplicative_expr
57            | Rule::unary_expr
58    ) {
59        if children.len() == 1 {
60            return parse_expression(children[0].clone());
61        }
62        if children.len() >= 3 {
63            let mut result = parse_expression(children[0].clone())?;
64            let mut i = 1;
65            while i + 1 < children.len() {
66                let op = match children[i].as_str() {
67                    "OR" => BinaryOp::Or,
68                    "XOR" => BinaryOp::Xor,
69                    "AND" => BinaryOp::And,
70                    "+" => BinaryOp::Add,
71                    "-" => BinaryOp::Subtract,
72                    "*" => BinaryOp::Multiply,
73                    "/" => BinaryOp::Divide,
74                    "%" => BinaryOp::Modulo,
75                    _ => return Err(format!("Unknown op: {}", children[i].as_str())),
76                };
77                let right = parse_expression(children[i + 1].clone())?;
78                result = Expression::BinaryOp(op, Box::new(result), Box::new(right));
79                i += 2;
80            }
81            return Ok(result);
82        }
83    }
84
85    // Handle unary NOT
86    if rule == Rule::not_expr && children.len() == 2 {
87        let inner = parse_expression(children[1].clone())?;
88        return Ok(Expression::UnaryOp(UnaryOp::Not, Box::new(inner)));
89    }
90
91    match rule {
92        Rule::primary => {
93            // Handle function calls encoded by grammar as: variable ~ function_args?
94            // e.g. nextval('seq') or COUNT(a)
95            if children.len() == 2
96                && children[0].as_rule() == Rule::variable
97                && children[1].as_rule() == Rule::function_args
98            {
99                let name = children[0].as_str().to_string();
100                let mut args = Vec::new();
101                let args_text = children[1].as_str().replace(" ", "");
102                for c in children[1].clone().into_inner() {
103                    if c.as_rule() == Rule::expression {
104                        args.push(parse_expression(c)?);
105                    }
106                }
107                if args.is_empty() && args_text == "(*)" {
108                    args.push(Expression::Star);
109                }
110                return Ok(Expression::FunctionCall(name, args));
111            }
112            parse_expression(children.into_iter().next().ok_or("Empty primary")?)
113        }
114        Rule::literal => parse_literal(children.into_iter().next().ok_or("Empty literal")?),
115        Rule::string => Ok(Expression::Constant(Constant::String(unescape_string(pair.as_str())))),
116        Rule::integer => {
117            let v: i64 = pair.as_str().parse().map_err(|e| format!("Int: {e}"))?;
118            Ok(Expression::Constant(Constant::Integer(v)))
119        }
120        Rule::float => {
121            let v: f64 = pair.as_str().parse().map_err(|e| format!("Float: {e}"))?;
122            Ok(Expression::Constant(Constant::Float(v)))
123        }
124        Rule::boolean_literal => Ok(Expression::Constant(Constant::Bool(
125            pair.as_str().to_uppercase() == "TRUE",
126        ))),
127        Rule::null_literal => Ok(Expression::Constant(Constant::Null)),
128        Rule::variable => Ok(Expression::Variable(pair.as_str().to_string())),
129        Rule::parameter => {
130            let name = pair.as_str().strip_prefix('$').unwrap_or(pair.as_str()).to_string();
131            Ok(Expression::Parameter(name))
132        }
133        Rule::list_literal => {
134            let items = children
135                .into_iter()
136                .filter(|c| c.as_rule() == Rule::expression)
137                .map(parse_expression)
138                .collect::<Result<Vec<_>, _>>()?;
139            Ok(Expression::List(items))
140        }
141        Rule::map_literal => {
142            let mut entries = Vec::new();
143            for c in children {
144                if c.as_rule() == Rule::property_key_value {
145                    entries.push(parse_property_kv(c)?);
146                }
147            }
148            Ok(Expression::Map(entries))
149        }
150        Rule::postfix_expr => {
151            let mut children = pair.clone().into_inner();
152            let mut result = parse_expression(children.next().ok_or("Empty postfix")?)?;
153            for child in children {
154                if child.as_rule() == Rule::property_access {
155                    let prop = child.into_inner().next().unwrap().as_str().to_string();
156                    result = Expression::PropertyAccess(Box::new(result), prop);
157                } else if child.as_rule() == Rule::expression {
158                    // List subscript `lst[i]` (Cypher 0-based). `[` `]` are
159                    // unnamed tokens, so the index arrives as a bare
160                    // `Rule::expression` child. Rewrite to
161                    // `list_extract(lst, i + 1)` because `list_extract` is
162                    // 1-based (P53.24). Previously the index was silently
163                    // dropped, returning the whole list/map.
164                    let idx = parse_expression(child)?;
165                    let one = Expression::Constant(Constant::Integer(1));
166                    result = Expression::FunctionCall(
167                        "list_extract".to_string(),
168                        vec![
169                            result,
170                            Expression::BinaryOp(BinaryOp::Add, Box::new(idx), Box::new(one)),
171                        ],
172                    );
173                }
174            }
175            Ok(result)
176        }
177        Rule::exists_subquery => {
178            // EXISTS { MATCH ... }
179            for c in children {
180                if c.as_rule() == Rule::query_statement {
181                    let query = parse_query_pairs(c)?;
182                    return Ok(Expression::ExistsSubquery(Box::new(query)));
183                }
184            }
185            Err("EXISTS subquery requires a query statement".into())
186        }
187        Rule::list_predicate => {
188            // ANY(x IN list WHERE predicate), ALL/NONE/SINGLE
189            // Parse tree has: variable, expression(list), expression(predicate)
190            // Quantifier is extracted from the matched string prefix.
191            let children: Vec<_> = pair.clone().into_inner().collect();
192            if children.len() < 3 {
193                return Err(format!("Invalid list predicate syntax: {} children", children.len()));
194            }
195            // Extract quantifier from the raw token: the first word of the pair
196            let full_text = pair.as_str();
197            let quantifier_str = full_text.split('(').next().unwrap_or("").to_uppercase();
198            let quantifier = match quantifier_str.as_str() {
199                "ANY" => Quantifier::Any,
200                "ALL" => Quantifier::All,
201                "NONE" => Quantifier::None,
202                "SINGLE" => Quantifier::Single,
203                _ => return Err(format!("Unknown quantifier: {}", quantifier_str)),
204            };
205            let var_name = children[0].as_str().to_string();
206            let list = parse_expression(children[1].clone())?;
207            let predicate = parse_expression(children[2].clone())?;
208            Ok(Expression::ListPredicate {
209                quantifier,
210                list: Box::new(list),
211                var_name,
212                predicate: Box::new(predicate),
213            })
214        }
215        Rule::lambda_expr => {
216            let mut children = pair.clone().into_inner();
217            let var_name = children.next().ok_or("Lambda missing variable")?.as_str().to_string();
218            let body = parse_expression(children.next().ok_or("Lambda missing body")?)?;
219            Ok(Expression::Lambda {
220                var_name,
221                body: Box::new(body),
222            })
223        }
224        Rule::function_args => {
225            // function_call can appear as child of postfix_expr
226            // The parent variable is the function name
227            // We detect this in the postfix chain handling
228            if children.is_empty() {
229                return Ok(Expression::Constant(Constant::Null));
230            }
231            // Extract function name from siblings in parent
232            let name = pair.as_str().split('(').next().unwrap_or("").to_string();
233            let args = children
234                .into_iter()
235                .filter(|c| c.as_rule() == Rule::expression)
236                .map(parse_expression)
237                .collect::<Result<Vec<_>, _>>()?;
238            Ok(Expression::FunctionCall(name, args))
239        }
240        Rule::property_access => Err("property_access should be handled within postfix_expr".into()),
241        _ => {
242            // Try unwrapping single child
243            if let Some(child) = children.into_iter().next() {
244                parse_expression(child)
245            } else {
246                Err(format!("Cannot parse: {:?}", rule))
247            }
248        }
249    }
250}
251
252/// Parse a comparison suffix operator node into an expression given the left-hand side.
253pub fn parse_comparison_suffix(pair: pest::iterators::Pair<Rule>, left: Expression) -> Result<Expression, String> {
254    /// Find the additive_expr child inside an operator rule.
255    fn get_rhs(pair: pest::iterators::Pair<Rule>) -> Result<Expression, String> {
256        let rhs_pair = pair
257            .into_inner()
258            .find(|p| p.as_rule() == Rule::additive_expr)
259            .ok_or_else(|| "Operator missing right-hand expression".to_string())?;
260        parse_expression(rhs_pair)
261    }
262
263    match pair.as_rule() {
264        Rule::is_check_op => {
265            let text = pair.as_str();
266            if text.contains("NOT") {
267                Ok(Expression::UnaryOp(UnaryOp::IsNotNull, Box::new(left)))
268            } else {
269                Ok(Expression::UnaryOp(UnaryOp::IsNull, Box::new(left)))
270            }
271        }
272        Rule::in_op => {
273            let right = get_rhs(pair)?;
274            Ok(Expression::BinaryOp(BinaryOp::In, Box::new(left), Box::new(right)))
275        }
276        Rule::not_in_op => {
277            let right = get_rhs(pair)?;
278            Ok(Expression::BinaryOp(BinaryOp::NotIn, Box::new(left), Box::new(right)))
279        }
280        Rule::starts_with_op => {
281            let right = get_rhs(pair)?;
282            Ok(Expression::BinaryOp(
283                BinaryOp::StartsWith,
284                Box::new(left),
285                Box::new(right),
286            ))
287        }
288        Rule::not_starts_with_op => {
289            let right = get_rhs(pair)?;
290            let inner = Expression::BinaryOp(BinaryOp::StartsWith, Box::new(left), Box::new(right));
291            Ok(Expression::UnaryOp(UnaryOp::Not, Box::new(inner)))
292        }
293        Rule::ends_with_op => {
294            let right = get_rhs(pair)?;
295            Ok(Expression::BinaryOp(
296                BinaryOp::EndsWith,
297                Box::new(left),
298                Box::new(right),
299            ))
300        }
301        Rule::not_ends_with_op => {
302            let right = get_rhs(pair)?;
303            let inner = Expression::BinaryOp(BinaryOp::EndsWith, Box::new(left), Box::new(right));
304            Ok(Expression::UnaryOp(UnaryOp::Not, Box::new(inner)))
305        }
306        Rule::contains_op => {
307            let right = get_rhs(pair)?;
308            Ok(Expression::BinaryOp(
309                BinaryOp::Contains,
310                Box::new(left),
311                Box::new(right),
312            ))
313        }
314        Rule::not_contains_op => {
315            let right = get_rhs(pair)?;
316            let inner = Expression::BinaryOp(BinaryOp::Contains, Box::new(left), Box::new(right));
317            Ok(Expression::UnaryOp(UnaryOp::Not, Box::new(inner)))
318        }
319        Rule::like_op => {
320            let right = get_rhs(pair)?;
321            Ok(Expression::BinaryOp(BinaryOp::Like, Box::new(left), Box::new(right)))
322        }
323        Rule::between_op => {
324            let mut children = pair.into_inner();
325            let lower_pair = children
326                .find(|p| p.as_rule() == Rule::additive_expr)
327                .ok_or_else(|| "BETWEEN missing lower bound expression".to_string())?;
328            let upper_pair = children
329                .find(|p| p.as_rule() == Rule::additive_expr)
330                .ok_or_else(|| "BETWEEN missing upper bound expression".to_string())?;
331            let lower_expr = parse_expression(lower_pair)?;
332            let upper_expr = parse_expression(upper_pair)?;
333            let ge = Expression::BinaryOp(
334                BinaryOp::GreaterThanOrEqual,
335                Box::new(left.clone()),
336                Box::new(lower_expr),
337            );
338            let le = Expression::BinaryOp(BinaryOp::LessThanOrEqual, Box::new(left), Box::new(upper_expr));
339            Ok(Expression::BinaryOp(BinaryOp::And, Box::new(ge), Box::new(le)))
340        }
341        r => Err(format!("Unknown comparison suffix: {:?}", r)),
342    }
343}
344
345/// Parse a `CASE [subject] WHEN ... THEN ... [ELSE ...] END` expression.
346pub fn parse_case_expr(pair: pest::iterators::Pair<Rule>) -> Result<Expression, String> {
347    let mut subject: Option<Expression> = None;
348    let mut alternatives: Vec<CaseAlternative> = Vec::new();
349    let mut else_expr: Option<Expression> = None;
350
351    for child in pair.into_inner() {
352        match child.as_rule() {
353            Rule::case_subject => {
354                // case_subject wraps a single expression (with lookahead to skip WHEN)
355                if let Some(expr_pair) = child.into_inner().next() {
356                    subject = Some(parse_expression(expr_pair)?);
357                }
358            }
359            Rule::case_when => {
360                // case_when contains exactly two expression children: WHEN expr, THEN expr
361                let mut exprs = child
362                    .into_inner()
363                    .filter(|p| p.as_rule() == Rule::expression)
364                    .map(parse_expression)
365                    .collect::<Result<Vec<_>, _>>()?;
366                if exprs.len() < 2 {
367                    return Err("CASE WHEN clause requires both WHEN and THEN expressions".into());
368                }
369                let then = exprs.remove(1);
370                let when = exprs.remove(0);
371                alternatives.push(CaseAlternative { when, then });
372            }
373            Rule::case_else => {
374                if let Some(expr_pair) = child.into_inner().find(|p| p.as_rule() == Rule::expression) {
375                    else_expr = Some(parse_expression(expr_pair)?);
376                }
377            }
378            _ => {} // Skip "CASE", "END" keyword tokens
379        }
380    }
381
382    if alternatives.is_empty() {
383        return Err("CASE expression requires at least one WHEN clause".into());
384    }
385
386    Ok(Expression::Case(CaseExpr {
387        subject: subject.map(Box::new),
388        alternatives,
389        else_expr: else_expr.map(Box::new),
390    }))
391}
392
393/// Parse a literal value.
394pub fn parse_literal(pair: pest::iterators::Pair<Rule>) -> Result<Expression, String> {
395    match pair.as_rule() {
396        Rule::string => Ok(Expression::Constant(Constant::String(unescape_string(pair.as_str())))),
397        Rule::integer => {
398            let s = pair.as_str();
399            Ok(Expression::Constant(Constant::Integer(
400                s.trim().parse().map_err(|e| format!("Int: {e} (for string '{s}')"))?,
401            )))
402        }
403        Rule::float => {
404            let s = pair.as_str();
405            Ok(Expression::Constant(Constant::Float(
406                s.trim().parse().map_err(|e| format!("Float: {e} (for string '{s}')"))?,
407            )))
408        }
409        Rule::boolean_literal => Ok(Expression::Constant(Constant::Bool(
410            pair.as_str().to_uppercase() == "TRUE",
411        ))),
412        Rule::null_literal => Ok(Expression::Constant(Constant::Null)),
413        _ => Err(format!("Unknown literal: {:?}", pair.as_rule())),
414    }
415}
416
417/// Unescape a string literal — handles \n, \t, \r, \\, \", \'.
418pub fn unescape_string(s: &str) -> String {
419    let s = s.trim_matches(|c| c == '"' || c == '\'');
420    let mut r = String::with_capacity(s.len());
421    let mut chars = s.chars();
422    while let Some(c) = chars.next() {
423        if c == '\\' {
424            match chars.next() {
425                Some('n') => r.push('\n'),
426                Some('t') => r.push('\t'),
427                Some('r') => r.push('\r'),
428                Some('\\') => r.push('\\'),
429                Some('"') => r.push('"'),
430                Some('\'') => r.push('\''),
431                Some(o) => {
432                    r.push('\\');
433                    r.push(o);
434                }
435                None => r.push('\\'),
436            }
437        } else {
438            r.push(c);
439        }
440    }
441    r
442}