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