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                for c in children[1].clone().into_inner() {
102                    if c.as_rule() == Rule::star {
103                        args.push(Expression::Star);
104                    } else if c.as_rule() == Rule::expression {
105                        args.push(parse_expression(c)?);
106                    }
107                }
108                return Ok(Expression::FunctionCall(name, args));
109            }
110            parse_expression(children.into_iter().next().ok_or("Empty primary")?)
111        }
112        Rule::literal => parse_literal(children.into_iter().next().ok_or("Empty literal")?),
113        Rule::string => Ok(Expression::Constant(Constant::String(unescape_string(pair.as_str())))),
114        Rule::integer => {
115            let v: i64 = pair.as_str().parse().map_err(|e| format!("Int: {e}"))?;
116            Ok(Expression::Constant(Constant::Integer(v)))
117        }
118        Rule::float => {
119            let v: f64 = pair.as_str().parse().map_err(|e| format!("Float: {e}"))?;
120            Ok(Expression::Constant(Constant::Float(v)))
121        }
122        Rule::boolean_literal => Ok(Expression::Constant(Constant::Bool(
123            pair.as_str().to_uppercase() == "TRUE",
124        ))),
125        Rule::null_literal => Ok(Expression::Constant(Constant::Null)),
126        Rule::variable => Ok(Expression::Variable(pair.as_str().to_string())),
127        Rule::parameter => {
128            let name = pair.as_str().strip_prefix('$').unwrap_or(pair.as_str()).to_string();
129            Ok(Expression::Parameter(name))
130        }
131        Rule::list_literal => {
132            let items = children
133                .into_iter()
134                .filter(|c| c.as_rule() == Rule::expression)
135                .map(parse_expression)
136                .collect::<Result<Vec<_>, _>>()?;
137            Ok(Expression::List(items))
138        }
139        Rule::map_literal => {
140            let mut entries = Vec::new();
141            for c in children {
142                if c.as_rule() == Rule::property_key_value {
143                    entries.push(parse_property_kv(c)?);
144                }
145            }
146            Ok(Expression::Map(entries))
147        }
148        Rule::postfix_expr => {
149            let mut children = pair.clone().into_inner();
150            let mut result = parse_expression(children.next().ok_or("Empty postfix")?)?;
151            for child in children {
152                if child.as_rule() == Rule::property_access {
153                    let prop = child.into_inner().next().unwrap().as_str().to_string();
154                    result = Expression::PropertyAccess(Box::new(result), prop);
155                } else if child.as_rule() == Rule::expression {
156                    // List subscript `lst[i]` (Cypher 0-based). `[` `]` are
157                    // unnamed tokens, so the index arrives as a bare
158                    // `Rule::expression` child. Rewrite to
159                    // `list_extract(lst, i + 1)` because `list_extract` is
160                    // 1-based (P53.24). Previously the index was silently
161                    // dropped, returning the whole list/map.
162                    let idx = parse_expression(child)?;
163                    let one = Expression::Constant(Constant::Integer(1));
164                    result = Expression::FunctionCall(
165                        "list_extract".to_string(),
166                        vec![
167                            result,
168                            Expression::BinaryOp(BinaryOp::Add, Box::new(idx), Box::new(one)),
169                        ],
170                    );
171                }
172            }
173            Ok(result)
174        }
175        Rule::exists_subquery => {
176            // EXISTS { MATCH ... }
177            for c in children {
178                if c.as_rule() == Rule::query_statement {
179                    let query = parse_query_pairs(c)?;
180                    return Ok(Expression::ExistsSubquery(Box::new(query)));
181                }
182            }
183            Err("EXISTS subquery requires a query statement".into())
184        }
185        Rule::list_predicate => {
186            // ANY(x IN list WHERE predicate), ALL/NONE/SINGLE
187            // Parse tree has: variable, expression(list), expression(predicate)
188            // Quantifier is extracted from the matched string prefix.
189            let children: Vec<_> = pair.clone().into_inner().collect();
190            if children.len() < 3 {
191                return Err(format!("Invalid list predicate syntax: {} children", children.len()));
192            }
193            // Extract quantifier from the raw token: the first word of the pair
194            let full_text = pair.as_str();
195            let quantifier_str = full_text.split('(').next().unwrap_or("").to_uppercase();
196            let quantifier = match quantifier_str.as_str() {
197                "ANY" => Quantifier::Any,
198                "ALL" => Quantifier::All,
199                "NONE" => Quantifier::None,
200                "SINGLE" => Quantifier::Single,
201                _ => return Err(format!("Unknown quantifier: {}", quantifier_str)),
202            };
203            let var_name = children[0].as_str().to_string();
204            let list = parse_expression(children[1].clone())?;
205            let predicate = parse_expression(children[2].clone())?;
206            Ok(Expression::ListPredicate {
207                quantifier,
208                list: Box::new(list),
209                var_name,
210                predicate: Box::new(predicate),
211            })
212        }
213        Rule::lambda_expr => {
214            let mut children = pair.clone().into_inner();
215            let var_name = children.next().ok_or("Lambda missing variable")?.as_str().to_string();
216            let body = parse_expression(children.next().ok_or("Lambda missing body")?)?;
217            Ok(Expression::Lambda {
218                var_name,
219                body: Box::new(body),
220            })
221        }
222        Rule::function_args => {
223            // function_call can appear as child of postfix_expr
224            // The parent variable is the function name
225            // We detect this in the postfix chain handling
226            if children.is_empty() {
227                return Ok(Expression::Constant(Constant::Null));
228            }
229            // Extract function name from siblings in parent
230            let name = pair.as_str().split('(').next().unwrap_or("").to_string();
231            let args = children
232                .into_iter()
233                .filter(|c| c.as_rule() == Rule::expression)
234                .map(parse_expression)
235                .collect::<Result<Vec<_>, _>>()?;
236            Ok(Expression::FunctionCall(name, args))
237        }
238        Rule::property_access => Err("property_access should be handled within postfix_expr".into()),
239        _ => {
240            // Try unwrapping single child
241            if let Some(child) = children.into_iter().next() {
242                parse_expression(child)
243            } else {
244                Err(format!("Cannot parse: {:?}", rule))
245            }
246        }
247    }
248}
249
250/// Parse a comparison suffix operator node into an expression given the left-hand side.
251pub fn parse_comparison_suffix(pair: pest::iterators::Pair<Rule>, left: Expression) -> Result<Expression, String> {
252    /// Find the additive_expr child inside an operator rule.
253    fn get_rhs(pair: pest::iterators::Pair<Rule>) -> Result<Expression, String> {
254        let rhs_pair = pair
255            .into_inner()
256            .find(|p| p.as_rule() == Rule::additive_expr)
257            .ok_or_else(|| "Operator missing right-hand expression".to_string())?;
258        parse_expression(rhs_pair)
259    }
260
261    match pair.as_rule() {
262        Rule::is_check_op => {
263            let text = pair.as_str();
264            if text.contains("NOT") {
265                Ok(Expression::UnaryOp(UnaryOp::IsNotNull, Box::new(left)))
266            } else {
267                Ok(Expression::UnaryOp(UnaryOp::IsNull, Box::new(left)))
268            }
269        }
270        Rule::in_op => {
271            let right = get_rhs(pair)?;
272            Ok(Expression::BinaryOp(BinaryOp::In, Box::new(left), Box::new(right)))
273        }
274        Rule::not_in_op => {
275            let right = get_rhs(pair)?;
276            Ok(Expression::BinaryOp(BinaryOp::NotIn, Box::new(left), Box::new(right)))
277        }
278        Rule::starts_with_op => {
279            let right = get_rhs(pair)?;
280            Ok(Expression::BinaryOp(
281                BinaryOp::StartsWith,
282                Box::new(left),
283                Box::new(right),
284            ))
285        }
286        Rule::not_starts_with_op => {
287            let right = get_rhs(pair)?;
288            let inner = Expression::BinaryOp(BinaryOp::StartsWith, Box::new(left), Box::new(right));
289            Ok(Expression::UnaryOp(UnaryOp::Not, Box::new(inner)))
290        }
291        Rule::ends_with_op => {
292            let right = get_rhs(pair)?;
293            Ok(Expression::BinaryOp(
294                BinaryOp::EndsWith,
295                Box::new(left),
296                Box::new(right),
297            ))
298        }
299        Rule::not_ends_with_op => {
300            let right = get_rhs(pair)?;
301            let inner = Expression::BinaryOp(BinaryOp::EndsWith, Box::new(left), Box::new(right));
302            Ok(Expression::UnaryOp(UnaryOp::Not, Box::new(inner)))
303        }
304        Rule::contains_op => {
305            let right = get_rhs(pair)?;
306            Ok(Expression::BinaryOp(
307                BinaryOp::Contains,
308                Box::new(left),
309                Box::new(right),
310            ))
311        }
312        Rule::not_contains_op => {
313            let right = get_rhs(pair)?;
314            let inner = Expression::BinaryOp(BinaryOp::Contains, Box::new(left), Box::new(right));
315            Ok(Expression::UnaryOp(UnaryOp::Not, Box::new(inner)))
316        }
317        Rule::like_op => {
318            let right = get_rhs(pair)?;
319            Ok(Expression::BinaryOp(BinaryOp::Like, Box::new(left), Box::new(right)))
320        }
321        Rule::between_op => {
322            let mut children = pair.into_inner();
323            let lower_pair = children
324                .find(|p| p.as_rule() == Rule::additive_expr)
325                .ok_or_else(|| "BETWEEN missing lower bound expression".to_string())?;
326            let upper_pair = children
327                .find(|p| p.as_rule() == Rule::additive_expr)
328                .ok_or_else(|| "BETWEEN missing upper bound expression".to_string())?;
329            let lower_expr = parse_expression(lower_pair)?;
330            let upper_expr = parse_expression(upper_pair)?;
331            let ge = Expression::BinaryOp(
332                BinaryOp::GreaterThanOrEqual,
333                Box::new(left.clone()),
334                Box::new(lower_expr),
335            );
336            let le = Expression::BinaryOp(BinaryOp::LessThanOrEqual, Box::new(left), Box::new(upper_expr));
337            Ok(Expression::BinaryOp(BinaryOp::And, Box::new(ge), Box::new(le)))
338        }
339        r => Err(format!("Unknown comparison suffix: {:?}", r)),
340    }
341}
342
343/// Parse a `CASE [subject] WHEN ... THEN ... [ELSE ...] END` expression.
344pub fn parse_case_expr(pair: pest::iterators::Pair<Rule>) -> Result<Expression, String> {
345    let mut subject: Option<Expression> = None;
346    let mut alternatives: Vec<CaseAlternative> = Vec::new();
347    let mut else_expr: Option<Expression> = None;
348
349    for child in pair.into_inner() {
350        match child.as_rule() {
351            Rule::case_subject => {
352                // case_subject wraps a single expression (with lookahead to skip WHEN)
353                if let Some(expr_pair) = child.into_inner().next() {
354                    subject = Some(parse_expression(expr_pair)?);
355                }
356            }
357            Rule::case_when => {
358                // case_when contains exactly two expression children: WHEN expr, THEN expr
359                let mut exprs = child
360                    .into_inner()
361                    .filter(|p| p.as_rule() == Rule::expression)
362                    .map(parse_expression)
363                    .collect::<Result<Vec<_>, _>>()?;
364                if exprs.len() < 2 {
365                    return Err("CASE WHEN clause requires both WHEN and THEN expressions".into());
366                }
367                let then = exprs.remove(1);
368                let when = exprs.remove(0);
369                alternatives.push(CaseAlternative { when, then });
370            }
371            Rule::case_else => {
372                if let Some(expr_pair) = child.into_inner().find(|p| p.as_rule() == Rule::expression) {
373                    else_expr = Some(parse_expression(expr_pair)?);
374                }
375            }
376            _ => {} // Skip "CASE", "END" keyword tokens
377        }
378    }
379
380    if alternatives.is_empty() {
381        return Err("CASE expression requires at least one WHEN clause".into());
382    }
383
384    Ok(Expression::Case(CaseExpr {
385        subject: subject.map(Box::new),
386        alternatives,
387        else_expr: else_expr.map(Box::new),
388    }))
389}
390
391/// Parse a literal value.
392pub fn parse_literal(pair: pest::iterators::Pair<Rule>) -> Result<Expression, String> {
393    match pair.as_rule() {
394        Rule::string => Ok(Expression::Constant(Constant::String(unescape_string(pair.as_str())))),
395        Rule::integer => {
396            let s = pair.as_str();
397            Ok(Expression::Constant(Constant::Integer(
398                s.trim().parse().map_err(|e| format!("Int: {e} (for string '{s}')"))?,
399            )))
400        }
401        Rule::float => {
402            let s = pair.as_str();
403            Ok(Expression::Constant(Constant::Float(
404                s.trim().parse().map_err(|e| format!("Float: {e} (for string '{s}')"))?,
405            )))
406        }
407        Rule::boolean_literal => Ok(Expression::Constant(Constant::Bool(
408            pair.as_str().to_uppercase() == "TRUE",
409        ))),
410        Rule::null_literal => Ok(Expression::Constant(Constant::Null)),
411        _ => Err(format!("Unknown literal: {:?}", pair.as_rule())),
412    }
413}
414
415/// Unescape a string literal — handles \n, \t, \r, \\, \", \'.
416pub fn unescape_string(s: &str) -> String {
417    let s = s.trim_matches(|c| c == '"' || c == '\'');
418    let mut r = String::with_capacity(s.len());
419    let mut chars = s.chars();
420    while let Some(c) = chars.next() {
421        if c == '\\' {
422            match chars.next() {
423                Some('n') => r.push('\n'),
424                Some('t') => r.push('\t'),
425                Some('r') => r.push('\r'),
426                Some('\\') => r.push('\\'),
427                Some('"') => r.push('"'),
428                Some('\'') => r.push('\''),
429                Some(o) => {
430                    r.push('\\');
431                    r.push(o);
432                }
433                None => r.push('\\'),
434            }
435        } else {
436            r.push(c);
437        }
438    }
439    r
440}