cctr_expr/
lib.rs

1//! Expression language parser and evaluator for cctr constraints.
2//!
3//! Supports:
4//! - Numbers: `42`, `-3.14`, `0.5`
5//! - Strings: `"hello"`, `"with \"escapes\""`
6//! - Booleans: `true`, `false`
7//! - Arrays: `[1, 2, 3]`, `["a", "b"]`
8//! - Objects: `{"key": value, ...}`
9//! - Arithmetic: `+`, `-`, `*`, `/`, `^`
10//! - Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`
11//! - Logical: `and`, `or`, `not`
12//! - String ops: `contains`, `startswith`, `endswith`, `matches`
13//! - Membership: `in`
14//! - Array/object access: `a[0]`, `obj["key"]`, `obj.key`
15//! - Functions: `len(s)`, `type(v)`, `keys(obj)`
16//! - Quantifiers: `expr forall x in arr`
17//!
18//! # Example
19//!
20//! ```
21//! use cctr_expr::{eval_bool, Value};
22//! use std::collections::HashMap;
23//!
24//! let mut vars = HashMap::new();
25//! vars.insert("n".to_string(), Value::Number(42.0));
26//!
27//! assert!(eval_bool("n > 0 and n < 100", &vars).unwrap());
28//! ```
29
30use std::collections::HashMap;
31use thiserror::Error;
32use winnow::ascii::{digit1, multispace0, multispace1};
33use winnow::combinator::{alt, delimited, opt, preceded, repeat, separated, terminated};
34use winnow::error::ContextError;
35use winnow::prelude::*;
36use winnow::token::{any, none_of, one_of, take_while};
37
38// ============ Value Types ============
39
40#[derive(Debug, Clone, PartialEq)]
41pub enum Value {
42    Number(f64),
43    String(String),
44    Bool(bool),
45    Null,
46    Array(Vec<Value>),
47    Object(HashMap<String, Value>),
48    Type(String),
49}
50
51impl Value {
52    pub fn as_bool(&self) -> Result<bool, EvalError> {
53        match self {
54            Value::Bool(b) => Ok(*b),
55            _ => Err(EvalError::TypeError {
56                expected: "bool",
57                got: self.type_name(),
58            }),
59        }
60    }
61
62    pub fn as_number(&self) -> Result<f64, EvalError> {
63        match self {
64            Value::Number(n) => Ok(*n),
65            _ => Err(EvalError::TypeError {
66                expected: "number",
67                got: self.type_name(),
68            }),
69        }
70    }
71
72    pub fn as_string(&self) -> Result<&str, EvalError> {
73        match self {
74            Value::String(s) => Ok(s),
75            _ => Err(EvalError::TypeError {
76                expected: "string",
77                got: self.type_name(),
78            }),
79        }
80    }
81
82    pub fn as_array(&self) -> Result<&[Value], EvalError> {
83        match self {
84            Value::Array(a) => Ok(a),
85            _ => Err(EvalError::TypeError {
86                expected: "array",
87                got: self.type_name(),
88            }),
89        }
90    }
91
92    pub fn as_object(&self) -> Result<&HashMap<String, Value>, EvalError> {
93        match self {
94            Value::Object(o) => Ok(o),
95            _ => Err(EvalError::TypeError {
96                expected: "object",
97                got: self.type_name(),
98            }),
99        }
100    }
101
102    pub fn type_name(&self) -> &'static str {
103        match self {
104            Value::Number(_) => "number",
105            Value::String(_) => "string",
106            Value::Bool(_) => "bool",
107            Value::Null => "null",
108            Value::Array(_) => "array",
109            Value::Object(_) => "object",
110            Value::Type(_) => "type",
111        }
112    }
113
114    pub fn type_value(&self) -> Value {
115        Value::Type(self.type_name().to_string())
116    }
117}
118
119// ============ AST Types ============
120
121#[derive(Debug, Clone, PartialEq)]
122pub enum Expr {
123    Number(f64),
124    String(String),
125    Bool(bool),
126    Null,
127    Var(String),
128    Array(Vec<Expr>),
129    Object(Vec<(String, Expr)>),
130    TypeLiteral(String),
131    UnaryOp {
132        op: UnaryOp,
133        expr: Box<Expr>,
134    },
135    BinaryOp {
136        op: BinaryOp,
137        left: Box<Expr>,
138        right: Box<Expr>,
139    },
140    FuncCall {
141        name: String,
142        args: Vec<Expr>,
143    },
144    Index {
145        expr: Box<Expr>,
146        index: Box<Expr>,
147    },
148    Property {
149        expr: Box<Expr>,
150        name: String,
151    },
152    ForAll {
153        predicate: Box<Expr>,
154        var: String,
155        iterable: Box<Expr>,
156    },
157}
158
159#[derive(Debug, Clone, Copy, PartialEq)]
160pub enum UnaryOp {
161    Not,
162    Neg,
163}
164
165#[derive(Debug, Clone, Copy, PartialEq)]
166pub enum BinaryOp {
167    Add,
168    Sub,
169    Mul,
170    Div,
171    Mod,
172    Pow,
173    Eq,
174    Ne,
175    Lt,
176    Le,
177    Gt,
178    Ge,
179    And,
180    Or,
181    Contains,
182    NotContains,
183    StartsWith,
184    NotStartsWith,
185    EndsWith,
186    NotEndsWith,
187    Matches,
188    NotMatches,
189}
190
191#[derive(Error, Debug, Clone, PartialEq)]
192pub enum EvalError {
193    #[error("type error: expected {expected}, got {got}")]
194    TypeError {
195        expected: &'static str,
196        got: &'static str,
197    },
198    #[error("undefined variable: {0}")]
199    UndefinedVariable(String),
200    #[error("undefined function: {0}")]
201    UndefinedFunction(String),
202    #[error("invalid regex: {0}")]
203    InvalidRegex(String),
204    #[error("division by zero")]
205    DivisionByZero,
206    #[error("parse error: {0}")]
207    ParseError(String),
208    #[error("wrong number of arguments for {func}: expected {expected}, got {got}")]
209    WrongArgCount {
210        func: String,
211        expected: usize,
212        got: usize,
213    },
214    #[error("index out of bounds: {index} (len: {len})")]
215    IndexOutOfBounds { index: i64, len: usize },
216    #[error("key not found: {0}")]
217    KeyNotFound(String),
218}
219
220// ============ Parser ============
221
222fn ws<'a, P, O>(p: P) -> impl Parser<&'a str, O, ContextError>
223where
224    P: Parser<&'a str, O, ContextError>,
225{
226    delimited(multispace0, p, multispace0)
227}
228
229fn number(input: &mut &str) -> ModalResult<Expr> {
230    let neg: Option<char> = opt('-').parse_next(input)?;
231    let int_part: &str = digit1.parse_next(input)?;
232    let frac_part: Option<&str> = opt(preceded('.', digit1)).parse_next(input)?;
233
234    let mut s = String::new();
235    if neg.is_some() {
236        s.push('-');
237    }
238    s.push_str(int_part);
239    if let Some(frac) = frac_part {
240        s.push('.');
241        s.push_str(frac);
242    }
243
244    Ok(Expr::Number(s.parse().unwrap()))
245}
246
247fn string_char(input: &mut &str) -> ModalResult<char> {
248    let c: char = none_of('"').parse_next(input)?;
249    if c == '\\' {
250        let escaped: char = any.parse_next(input)?;
251        Ok(match escaped {
252            'n' => '\n',
253            't' => '\t',
254            'r' => '\r',
255            '"' => '"',
256            '\\' => '\\',
257            c => c,
258        })
259    } else {
260        Ok(c)
261    }
262}
263
264fn string_literal(input: &mut &str) -> ModalResult<Expr> {
265    let chars: String = delimited(
266        '"',
267        repeat(0.., string_char).fold(String::new, |mut s, c| {
268            s.push(c);
269            s
270        }),
271        '"',
272    )
273    .parse_next(input)?;
274    Ok(Expr::String(chars))
275}
276
277fn regex_literal(input: &mut &str) -> ModalResult<Expr> {
278    '/'.parse_next(input)?;
279    let mut s = String::new();
280    loop {
281        let c: char = any.parse_next(input)?;
282        if c == '/' {
283            break;
284        }
285        if c == '\\' {
286            let escaped: char = any.parse_next(input)?;
287            s.push('\\');
288            s.push(escaped);
289        } else {
290            s.push(c);
291        }
292    }
293    Ok(Expr::String(s))
294}
295
296fn ident(input: &mut &str) -> ModalResult<String> {
297    let first: char = one_of(|c: char| c.is_ascii_alphabetic() || c == '_').parse_next(input)?;
298    let rest: &str =
299        take_while(0.., |c: char| c.is_ascii_alphanumeric() || c == '_').parse_next(input)?;
300    Ok(format!("{}{}", first, rest))
301}
302
303fn var_or_bool_or_func(input: &mut &str) -> ModalResult<Expr> {
304    let name = ident.parse_next(input)?;
305
306    let _ = multispace0.parse_next(input)?;
307    if input.starts_with('(') {
308        '('.parse_next(input)?;
309        let _ = multispace0.parse_next(input)?;
310        let args: Vec<Expr> = separated(0.., ws(expr), ws(',')).parse_next(input)?;
311        let _ = multispace0.parse_next(input)?;
312        ')'.parse_next(input)?;
313        return Ok(Expr::FuncCall { name, args });
314    }
315
316    match name.as_str() {
317        "true" => Ok(Expr::Bool(true)),
318        "false" => Ok(Expr::Bool(false)),
319        // null is both a value and a type literal - as a standalone value we treat it as Null,
320        // but when used in type comparison (type(x) == null) it matches as a TypeLiteral
321        "null" => Ok(Expr::TypeLiteral(name)),
322        // Type keywords
323        "number" | "string" | "bool" | "array" | "object" => Ok(Expr::TypeLiteral(name)),
324        _ => Ok(Expr::Var(name)),
325    }
326}
327
328fn array(input: &mut &str) -> ModalResult<Expr> {
329    let elements: Vec<Expr> = delimited(
330        ('[', multispace0),
331        separated(0.., ws(expr), ws(',')),
332        (multispace0, ']'),
333    )
334    .parse_next(input)?;
335    Ok(Expr::Array(elements))
336}
337
338fn object_key(input: &mut &str) -> ModalResult<String> {
339    alt((
340        // Quoted key: "foo"
341        delimited(
342            '"',
343            repeat(0.., string_char).fold(String::new, |mut s, c| {
344                s.push(c);
345                s
346            }),
347            '"',
348        ),
349        // Unquoted identifier key
350        ident,
351    ))
352    .parse_next(input)
353}
354
355fn object_entry(input: &mut &str) -> ModalResult<(String, Expr)> {
356    let key = ws(object_key).parse_next(input)?;
357    ws(':').parse_next(input)?;
358    let value = ws(expr).parse_next(input)?;
359    Ok((key, value))
360}
361
362fn object(input: &mut &str) -> ModalResult<Expr> {
363    let entries: Vec<(String, Expr)> = delimited(
364        ('{', multispace0),
365        separated(0.., object_entry, ws(',')),
366        (multispace0, '}'),
367    )
368    .parse_next(input)?;
369    Ok(Expr::Object(entries))
370}
371
372const TYPE_KEYWORDS: &[&str] = &["number", "string", "bool", "null", "array", "object"];
373
374fn type_literal(input: &mut &str) -> ModalResult<Expr> {
375    for &kw in TYPE_KEYWORDS {
376        if input.starts_with(kw) {
377            let after = &(*input)[kw.len()..];
378            let next_char = after.chars().next();
379            if next_char
380                .map(|c| c.is_ascii_alphanumeric() || c == '_')
381                .unwrap_or(false)
382            {
383                continue;
384            }
385            *input = after;
386            return Ok(Expr::TypeLiteral(kw.to_string()));
387        }
388    }
389    Err(winnow::error::ErrMode::Backtrack(ContextError::new()))
390}
391
392fn atom(input: &mut &str) -> ModalResult<Expr> {
393    let _ = multispace0.parse_next(input)?;
394    alt((
395        delimited(('(', multispace0), expr, (multispace0, ')')),
396        array,
397        object,
398        string_literal,
399        regex_literal,
400        number,
401        var_or_bool_or_func,
402        type_literal,
403    ))
404    .parse_next(input)
405}
406
407fn postfix(input: &mut &str) -> ModalResult<Expr> {
408    let mut base = atom.parse_next(input)?;
409    loop {
410        let _ = multispace0.parse_next(input)?;
411        if input.starts_with('[') {
412            '['.parse_next(input)?;
413            let _ = multispace0.parse_next(input)?;
414            let index = expr.parse_next(input)?;
415            let _ = multispace0.parse_next(input)?;
416            ']'.parse_next(input)?;
417            base = Expr::Index {
418                expr: Box::new(base),
419                index: Box::new(index),
420            };
421        } else if input.starts_with('.') {
422            '.'.parse_next(input)?;
423            let name = ident.parse_next(input)?;
424            base = Expr::Property {
425                expr: Box::new(base),
426                name,
427            };
428        } else {
429            break;
430        }
431    }
432    Ok(base)
433}
434
435fn unary(input: &mut &str) -> ModalResult<Expr> {
436    let _ = multispace0.parse_next(input)?;
437    let neg: Option<char> = opt('-').parse_next(input)?;
438    if neg.is_some() {
439        let e = unary.parse_next(input)?;
440        return Ok(Expr::UnaryOp {
441            op: UnaryOp::Neg,
442            expr: Box::new(e),
443        });
444    }
445    postfix(input)
446}
447
448fn pow(input: &mut &str) -> ModalResult<Expr> {
449    let base = unary.parse_next(input)?;
450    let _ = multispace0.parse_next(input)?;
451    let caret: Option<char> = opt('^').parse_next(input)?;
452    if caret.is_some() {
453        let _ = multispace0.parse_next(input)?;
454        let exp = pow.parse_next(input)?;
455        Ok(Expr::BinaryOp {
456            op: BinaryOp::Pow,
457            left: Box::new(base),
458            right: Box::new(exp),
459        })
460    } else {
461        Ok(base)
462    }
463}
464
465fn term(input: &mut &str) -> ModalResult<Expr> {
466    let init = pow.parse_next(input)?;
467
468    repeat(0.., (ws(one_of(['*', '/', '%'])), pow))
469        .fold(
470            move || init.clone(),
471            |acc, (op_char, val): (char, Expr)| {
472                let op = match op_char {
473                    '*' => BinaryOp::Mul,
474                    '/' => BinaryOp::Div,
475                    '%' => BinaryOp::Mod,
476                    _ => unreachable!(),
477                };
478                Expr::BinaryOp {
479                    op,
480                    left: Box::new(acc),
481                    right: Box::new(val),
482                }
483            },
484        )
485        .parse_next(input)
486}
487
488fn arith(input: &mut &str) -> ModalResult<Expr> {
489    let init = term.parse_next(input)?;
490
491    repeat(0.., (ws(one_of(['+', '-'])), term))
492        .fold(
493            move || init.clone(),
494            |acc, (op_char, val): (char, Expr)| {
495                let op = if op_char == '+' {
496                    BinaryOp::Add
497                } else {
498                    BinaryOp::Sub
499                };
500                Expr::BinaryOp {
501                    op,
502                    left: Box::new(acc),
503                    right: Box::new(val),
504                }
505            },
506        )
507        .parse_next(input)
508}
509
510fn peek_non_ident(input: &mut &str) -> ModalResult<()> {
511    let next = input.chars().next();
512    if next
513        .map(|c| c.is_ascii_alphanumeric() || c == '_')
514        .unwrap_or(false)
515    {
516        Err(winnow::error::ErrMode::Backtrack(ContextError::new()))
517    } else {
518        Ok(())
519    }
520}
521
522fn cmp_op(input: &mut &str) -> ModalResult<BinaryOp> {
523    alt((
524        "==".value(BinaryOp::Eq),
525        "!=".value(BinaryOp::Ne),
526        "<=".value(BinaryOp::Le),
527        ">=".value(BinaryOp::Ge),
528        "<".value(BinaryOp::Lt),
529        ">".value(BinaryOp::Gt),
530        (
531            terminated("not", peek_non_ident),
532            multispace1,
533            terminated("contains", peek_non_ident),
534        )
535            .value(BinaryOp::NotContains),
536        (
537            terminated("not", peek_non_ident),
538            multispace1,
539            terminated("startswith", peek_non_ident),
540        )
541            .value(BinaryOp::NotStartsWith),
542        (
543            terminated("not", peek_non_ident),
544            multispace1,
545            terminated("endswith", peek_non_ident),
546        )
547            .value(BinaryOp::NotEndsWith),
548        (
549            terminated("not", peek_non_ident),
550            multispace1,
551            terminated("matches", peek_non_ident),
552        )
553            .value(BinaryOp::NotMatches),
554        terminated("contains", peek_non_ident).value(BinaryOp::Contains),
555        terminated("startswith", peek_non_ident).value(BinaryOp::StartsWith),
556        terminated("endswith", peek_non_ident).value(BinaryOp::EndsWith),
557        terminated("matches", peek_non_ident).value(BinaryOp::Matches),
558    ))
559    .parse_next(input)
560}
561
562fn comparison(input: &mut &str) -> ModalResult<Expr> {
563    let left = arith.parse_next(input)?;
564    let _ = multispace0.parse_next(input)?;
565
566    let op_opt: Option<BinaryOp> = opt(cmp_op).parse_next(input)?;
567    match op_opt {
568        Some(op) => {
569            let _ = multispace0.parse_next(input)?;
570            let right = arith.parse_next(input)?;
571            Ok(Expr::BinaryOp {
572                op,
573                left: Box::new(left),
574                right: Box::new(right),
575            })
576        }
577        None => Ok(left),
578    }
579}
580
581fn not_expr(input: &mut &str) -> ModalResult<Expr> {
582    let _ = multispace0.parse_next(input)?;
583    let not_kw: Option<&str> = opt(terminated("not", peek_non_ident)).parse_next(input)?;
584    if not_kw.is_some() {
585        let _ = multispace0.parse_next(input)?;
586        let e = not_expr.parse_next(input)?;
587        Ok(Expr::UnaryOp {
588            op: UnaryOp::Not,
589            expr: Box::new(e),
590        })
591    } else {
592        comparison(input)
593    }
594}
595
596fn and_expr(input: &mut &str) -> ModalResult<Expr> {
597    let init = not_expr.parse_next(input)?;
598
599    repeat(
600        0..,
601        preceded((multispace0, "and", peek_non_ident, multispace0), not_expr),
602    )
603    .fold(
604        move || init.clone(),
605        |acc, val| Expr::BinaryOp {
606            op: BinaryOp::And,
607            left: Box::new(acc),
608            right: Box::new(val),
609        },
610    )
611    .parse_next(input)
612}
613
614fn or_expr(input: &mut &str) -> ModalResult<Expr> {
615    let init = and_expr.parse_next(input)?;
616
617    repeat(
618        0..,
619        preceded((multispace0, "or", peek_non_ident, multispace0), and_expr),
620    )
621    .fold(
622        move || init.clone(),
623        |acc, val| Expr::BinaryOp {
624            op: BinaryOp::Or,
625            left: Box::new(acc),
626            right: Box::new(val),
627        },
628    )
629    .parse_next(input)
630}
631
632fn forall_expr(input: &mut &str) -> ModalResult<Expr> {
633    let predicate = or_expr.parse_next(input)?;
634    let _ = multispace0.parse_next(input)?;
635
636    let forall_kw: Option<&str> = opt(terminated("forall", peek_non_ident)).parse_next(input)?;
637    if forall_kw.is_some() {
638        let _ = multispace0.parse_next(input)?;
639        let var = ident.parse_next(input)?;
640        let _ = multispace0.parse_next(input)?;
641        terminated("in", peek_non_ident).parse_next(input)?;
642        let _ = multispace0.parse_next(input)?;
643        let iterable = or_expr.parse_next(input)?;
644        Ok(Expr::ForAll {
645            predicate: Box::new(predicate),
646            var,
647            iterable: Box::new(iterable),
648        })
649    } else {
650        Ok(predicate)
651    }
652}
653
654fn expr(input: &mut &str) -> ModalResult<Expr> {
655    forall_expr(input)
656}
657
658pub fn parse(input: &str) -> Result<Expr, EvalError> {
659    let mut input = input.trim();
660    match expr.parse_next(&mut input) {
661        Ok(e) => {
662            let remaining = input.trim();
663            if remaining.is_empty() {
664                Ok(e)
665            } else {
666                Err(EvalError::ParseError(format!(
667                    "unexpected trailing input: {:?}",
668                    remaining
669                )))
670            }
671        }
672        Err(e) => Err(EvalError::ParseError(format!("{:?}", e))),
673    }
674}
675
676// ============ Evaluator ============
677
678pub fn evaluate(expr: &Expr, vars: &HashMap<String, Value>) -> Result<Value, EvalError> {
679    match expr {
680        Expr::Number(n) => Ok(Value::Number(*n)),
681        Expr::String(s) => Ok(Value::String(s.clone())),
682        Expr::Bool(b) => Ok(Value::Bool(*b)),
683        Expr::Null => Ok(Value::Null),
684        Expr::TypeLiteral(t) => Ok(Value::Type(t.clone())),
685        Expr::Var(name) => vars
686            .get(name)
687            .cloned()
688            .ok_or_else(|| EvalError::UndefinedVariable(name.clone())),
689        Expr::Array(elements) => {
690            let values: Result<Vec<_>, _> = elements.iter().map(|e| evaluate(e, vars)).collect();
691            Ok(Value::Array(values?))
692        }
693        Expr::Object(entries) => {
694            let mut map = HashMap::new();
695            for (key, val_expr) in entries {
696                map.insert(key.clone(), evaluate(val_expr, vars)?);
697            }
698            Ok(Value::Object(map))
699        }
700        Expr::UnaryOp { op, expr } => {
701            let val = evaluate(expr, vars)?;
702            match op {
703                UnaryOp::Not => Ok(Value::Bool(!val.as_bool()?)),
704                UnaryOp::Neg => Ok(Value::Number(-val.as_number()?)),
705            }
706        }
707        Expr::BinaryOp { op, left, right } => eval_binary_op(*op, left, right, vars),
708        Expr::FuncCall { name, args } => eval_func_call(name, args, vars),
709        Expr::Index { expr, index } => {
710            let base = evaluate(expr, vars)?;
711            let idx = evaluate(index, vars)?;
712            match &base {
713                Value::Array(arr) => {
714                    let i = idx.as_number()?;
715                    let actual_index = if i < 0.0 {
716                        // Negative indexing: -1 is last element, -2 is second to last, etc.
717                        let neg_idx = (-i) as usize;
718                        if neg_idx > arr.len() {
719                            return Err(EvalError::IndexOutOfBounds {
720                                index: i as i64,
721                                len: arr.len(),
722                            });
723                        }
724                        arr.len() - neg_idx
725                    } else {
726                        i as usize
727                    };
728                    arr.get(actual_index)
729                        .cloned()
730                        .ok_or(EvalError::IndexOutOfBounds {
731                            index: i as i64,
732                            len: arr.len(),
733                        })
734                }
735                Value::String(s) => {
736                    let i = idx.as_number()?;
737                    let chars: Vec<char> = s.chars().collect();
738                    let actual_index = if i < 0.0 {
739                        let neg_idx = (-i) as usize;
740                        if neg_idx > chars.len() {
741                            return Err(EvalError::IndexOutOfBounds {
742                                index: i as i64,
743                                len: chars.len(),
744                            });
745                        }
746                        chars.len() - neg_idx
747                    } else {
748                        i as usize
749                    };
750                    chars
751                        .get(actual_index)
752                        .map(|c| Value::String(c.to_string()))
753                        .ok_or(EvalError::IndexOutOfBounds {
754                            index: i as i64,
755                            len: chars.len(),
756                        })
757                }
758                Value::Object(obj) => {
759                    let key = idx.as_string()?;
760                    obj.get(key)
761                        .cloned()
762                        .ok_or_else(|| EvalError::KeyNotFound(key.to_string()))
763                }
764                _ => Err(EvalError::TypeError {
765                    expected: "array, string, or object",
766                    got: base.type_name(),
767                }),
768            }
769        }
770        Expr::Property { expr, name } => {
771            let base = evaluate(expr, vars)?;
772            let obj = base.as_object()?;
773            obj.get(name)
774                .cloned()
775                .ok_or_else(|| EvalError::KeyNotFound(name.clone()))
776        }
777        Expr::ForAll {
778            predicate,
779            var,
780            iterable,
781        } => {
782            let iter_val = evaluate(iterable, vars)?;
783            let items = match &iter_val {
784                Value::Array(arr) => arr.clone(),
785                Value::Object(obj) => obj.values().cloned().collect(),
786                _ => {
787                    return Err(EvalError::TypeError {
788                        expected: "array or object",
789                        got: iter_val.type_name(),
790                    });
791                }
792            };
793            for item in items {
794                let mut local_vars = vars.clone();
795                local_vars.insert(var.clone(), item);
796                let result = evaluate(predicate, &local_vars)?;
797                if !result.as_bool()? {
798                    return Ok(Value::Bool(false));
799                }
800            }
801            Ok(Value::Bool(true))
802        }
803    }
804}
805
806fn eval_func_call(
807    name: &str,
808    args: &[Expr],
809    vars: &HashMap<String, Value>,
810) -> Result<Value, EvalError> {
811    match name {
812        "len" => {
813            if args.len() != 1 {
814                return Err(EvalError::WrongArgCount {
815                    func: name.to_string(),
816                    expected: 1,
817                    got: args.len(),
818                });
819            }
820            let val = evaluate(&args[0], vars)?;
821            match val {
822                Value::String(s) => Ok(Value::Number(s.chars().count() as f64)),
823                Value::Array(a) => Ok(Value::Number(a.len() as f64)),
824                Value::Object(o) => Ok(Value::Number(o.len() as f64)),
825                _ => Err(EvalError::TypeError {
826                    expected: "string, array, or object",
827                    got: val.type_name(),
828                }),
829            }
830        }
831        "type" => {
832            if args.len() != 1 {
833                return Err(EvalError::WrongArgCount {
834                    func: name.to_string(),
835                    expected: 1,
836                    got: args.len(),
837                });
838            }
839            let val = evaluate(&args[0], vars)?;
840            Ok(Value::Type(val.type_name().to_string()))
841        }
842        "keys" => {
843            if args.len() != 1 {
844                return Err(EvalError::WrongArgCount {
845                    func: name.to_string(),
846                    expected: 1,
847                    got: args.len(),
848                });
849            }
850            let val = evaluate(&args[0], vars)?;
851            let obj = val.as_object()?;
852            let mut keys: Vec<String> = obj.keys().cloned().collect();
853            keys.sort();
854            let keys: Vec<Value> = keys.into_iter().map(Value::String).collect();
855            Ok(Value::Array(keys))
856        }
857        "values" => {
858            if args.len() != 1 {
859                return Err(EvalError::WrongArgCount {
860                    func: name.to_string(),
861                    expected: 1,
862                    got: args.len(),
863                });
864            }
865            let val = evaluate(&args[0], vars)?;
866            let obj = val.as_object()?;
867            // Sort by keys and return corresponding values
868            let mut pairs: Vec<(&String, &Value)> = obj.iter().collect();
869            pairs.sort_by_key(|(k, _)| *k);
870            let values: Vec<Value> = pairs.into_iter().map(|(_, v)| v.clone()).collect();
871            Ok(Value::Array(values))
872        }
873        "sum" => {
874            if args.len() != 1 {
875                return Err(EvalError::WrongArgCount {
876                    func: name.to_string(),
877                    expected: 1,
878                    got: args.len(),
879                });
880            }
881            let val = evaluate(&args[0], vars)?;
882            let arr = val.as_array()?;
883            let mut total = 0.0;
884            for item in arr {
885                total += item.as_number()?;
886            }
887            Ok(Value::Number(total))
888        }
889        "min" => {
890            if args.len() != 1 {
891                return Err(EvalError::WrongArgCount {
892                    func: name.to_string(),
893                    expected: 1,
894                    got: args.len(),
895                });
896            }
897            let val = evaluate(&args[0], vars)?;
898            let arr = val.as_array()?;
899            if arr.is_empty() {
900                return Err(EvalError::TypeError {
901                    expected: "non-empty array",
902                    got: "empty array",
903                });
904            }
905            let mut min_val = arr[0].as_number()?;
906            for item in arr.iter().skip(1) {
907                let n = item.as_number()?;
908                if n < min_val {
909                    min_val = n;
910                }
911            }
912            Ok(Value::Number(min_val))
913        }
914        "max" => {
915            if args.len() != 1 {
916                return Err(EvalError::WrongArgCount {
917                    func: name.to_string(),
918                    expected: 1,
919                    got: args.len(),
920                });
921            }
922            let val = evaluate(&args[0], vars)?;
923            let arr = val.as_array()?;
924            if arr.is_empty() {
925                return Err(EvalError::TypeError {
926                    expected: "non-empty array",
927                    got: "empty array",
928                });
929            }
930            let mut max_val = arr[0].as_number()?;
931            for item in arr.iter().skip(1) {
932                let n = item.as_number()?;
933                if n > max_val {
934                    max_val = n;
935                }
936            }
937            Ok(Value::Number(max_val))
938        }
939        "abs" => {
940            if args.len() != 1 {
941                return Err(EvalError::WrongArgCount {
942                    func: name.to_string(),
943                    expected: 1,
944                    got: args.len(),
945                });
946            }
947            let val = evaluate(&args[0], vars)?;
948            Ok(Value::Number(val.as_number()?.abs()))
949        }
950        "lower" => {
951            if args.len() != 1 {
952                return Err(EvalError::WrongArgCount {
953                    func: name.to_string(),
954                    expected: 1,
955                    got: args.len(),
956                });
957            }
958            let val = evaluate(&args[0], vars)?;
959            Ok(Value::String(val.as_string()?.to_lowercase()))
960        }
961        "upper" => {
962            if args.len() != 1 {
963                return Err(EvalError::WrongArgCount {
964                    func: name.to_string(),
965                    expected: 1,
966                    got: args.len(),
967                });
968            }
969            let val = evaluate(&args[0], vars)?;
970            Ok(Value::String(val.as_string()?.to_uppercase()))
971        }
972        "unique" => {
973            if args.len() != 1 {
974                return Err(EvalError::WrongArgCount {
975                    func: name.to_string(),
976                    expected: 1,
977                    got: args.len(),
978                });
979            }
980            let val = evaluate(&args[0], vars)?;
981            let arr = val.as_array()?;
982            let mut result = Vec::new();
983            for item in arr {
984                if !result.iter().any(|v| values_equal(v, item)) {
985                    result.push(item.clone());
986                }
987            }
988            Ok(Value::Array(result))
989        }
990        _ => Err(EvalError::UndefinedFunction(name.to_string())),
991    }
992}
993
994fn eval_binary_op(
995    op: BinaryOp,
996    left: &Expr,
997    right: &Expr,
998    vars: &HashMap<String, Value>,
999) -> Result<Value, EvalError> {
1000    if op == BinaryOp::And {
1001        let l = evaluate(left, vars)?.as_bool()?;
1002        if !l {
1003            return Ok(Value::Bool(false));
1004        }
1005        return Ok(Value::Bool(evaluate(right, vars)?.as_bool()?));
1006    }
1007    if op == BinaryOp::Or {
1008        let l = evaluate(left, vars)?.as_bool()?;
1009        if l {
1010            return Ok(Value::Bool(true));
1011        }
1012        return Ok(Value::Bool(evaluate(right, vars)?.as_bool()?));
1013    }
1014
1015    let l = evaluate(left, vars)?;
1016    let r = evaluate(right, vars)?;
1017
1018    match op {
1019        BinaryOp::Add => match (&l, &r) {
1020            (Value::String(ls), Value::String(rs)) => Ok(Value::String(format!("{}{}", ls, rs))),
1021            (Value::Array(la), Value::Array(ra)) => {
1022                let mut result = la.clone();
1023                result.extend(ra.clone());
1024                Ok(Value::Array(result))
1025            }
1026            _ => Ok(Value::Number(l.as_number()? + r.as_number()?)),
1027        },
1028        BinaryOp::Sub => Ok(Value::Number(l.as_number()? - r.as_number()?)),
1029        BinaryOp::Mul => Ok(Value::Number(l.as_number()? * r.as_number()?)),
1030        BinaryOp::Mod => Ok(Value::Number(l.as_number()? % r.as_number()?)),
1031        BinaryOp::Div => {
1032            let divisor = r.as_number()?;
1033            if divisor == 0.0 {
1034                Err(EvalError::DivisionByZero)
1035            } else {
1036                Ok(Value::Number(l.as_number()? / divisor))
1037            }
1038        }
1039        BinaryOp::Pow => Ok(Value::Number(l.as_number()?.powf(r.as_number()?))),
1040        BinaryOp::Eq => Ok(Value::Bool(values_equal(&l, &r))),
1041        BinaryOp::Ne => Ok(Value::Bool(!values_equal(&l, &r))),
1042        BinaryOp::Lt => match (&l, &r) {
1043            (Value::String(ls), Value::String(rs)) => Ok(Value::Bool(ls < rs)),
1044            _ => Ok(Value::Bool(l.as_number()? < r.as_number()?)),
1045        },
1046        BinaryOp::Le => match (&l, &r) {
1047            (Value::String(ls), Value::String(rs)) => Ok(Value::Bool(ls <= rs)),
1048            _ => Ok(Value::Bool(l.as_number()? <= r.as_number()?)),
1049        },
1050        BinaryOp::Gt => match (&l, &r) {
1051            (Value::String(ls), Value::String(rs)) => Ok(Value::Bool(ls > rs)),
1052            _ => Ok(Value::Bool(l.as_number()? > r.as_number()?)),
1053        },
1054        BinaryOp::Ge => match (&l, &r) {
1055            (Value::String(ls), Value::String(rs)) => Ok(Value::Bool(ls >= rs)),
1056            _ => Ok(Value::Bool(l.as_number()? >= r.as_number()?)),
1057        },
1058        BinaryOp::Contains | BinaryOp::NotContains => {
1059            let result = match &l {
1060                Value::String(haystack) => {
1061                    let needle = r.as_string()?;
1062                    haystack.contains(needle)
1063                }
1064                Value::Array(arr) => arr.iter().any(|v| values_equal(v, &r)),
1065                Value::Object(obj) => {
1066                    let key = r.as_string()?;
1067                    obj.contains_key(key)
1068                }
1069                _ => {
1070                    return Err(EvalError::TypeError {
1071                        expected: "string, array, or object",
1072                        got: l.type_name(),
1073                    })
1074                }
1075            };
1076            Ok(Value::Bool(if op == BinaryOp::NotContains {
1077                !result
1078            } else {
1079                result
1080            }))
1081        }
1082        BinaryOp::StartsWith | BinaryOp::NotStartsWith => {
1083            let s = l.as_string()?;
1084            let prefix = r.as_string()?;
1085            let result = s.starts_with(prefix);
1086            Ok(Value::Bool(if op == BinaryOp::NotStartsWith {
1087                !result
1088            } else {
1089                result
1090            }))
1091        }
1092        BinaryOp::EndsWith | BinaryOp::NotEndsWith => {
1093            let s = l.as_string()?;
1094            let suffix = r.as_string()?;
1095            let result = s.ends_with(suffix);
1096            Ok(Value::Bool(if op == BinaryOp::NotEndsWith {
1097                !result
1098            } else {
1099                result
1100            }))
1101        }
1102        BinaryOp::Matches | BinaryOp::NotMatches => {
1103            let s = l.as_string()?;
1104            let pattern = r.as_string()?;
1105            let re =
1106                regex::Regex::new(pattern).map_err(|e| EvalError::InvalidRegex(e.to_string()))?;
1107            let result = re.is_match(s);
1108            Ok(Value::Bool(if op == BinaryOp::NotMatches {
1109                !result
1110            } else {
1111                result
1112            }))
1113        }
1114        BinaryOp::And | BinaryOp::Or => unreachable!(),
1115    }
1116}
1117
1118fn values_equal(a: &Value, b: &Value) -> bool {
1119    match (a, b) {
1120        (Value::Number(a), Value::Number(b)) => (a - b).abs() < f64::EPSILON,
1121        (Value::String(a), Value::String(b)) => a == b,
1122        (Value::Bool(a), Value::Bool(b)) => a == b,
1123        (Value::Null, Value::Null) => true,
1124        // Allow null literal to match Type("null") for type comparisons like `type(x) == null`
1125        (Value::Null, Value::Type(t)) | (Value::Type(t), Value::Null) => t == "null",
1126        (Value::Array(a), Value::Array(b)) => {
1127            a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
1128        }
1129        (Value::Object(a), Value::Object(b)) => {
1130            a.len() == b.len()
1131                && a.iter()
1132                    .all(|(k, v)| b.get(k).map(|bv| values_equal(v, bv)).unwrap_or(false))
1133        }
1134        (Value::Type(a), Value::Type(b)) => a == b,
1135        _ => false,
1136    }
1137}
1138
1139// ============ Public API ============
1140
1141pub fn eval_bool(expr_str: &str, vars: &HashMap<String, Value>) -> Result<bool, EvalError> {
1142    let ast = parse(expr_str)?;
1143    let result = evaluate(&ast, vars)?;
1144    result.as_bool()
1145}
1146
1147#[cfg(test)]
1148mod tests {
1149    use super::*;
1150
1151    fn vars(pairs: &[(&str, Value)]) -> HashMap<String, Value> {
1152        pairs
1153            .iter()
1154            .map(|(k, v)| (k.to_string(), v.clone()))
1155            .collect()
1156    }
1157
1158    #[test]
1159    fn test_number_parsing() {
1160        assert_eq!(parse("42").unwrap(), Expr::Number(42.0));
1161        assert_eq!(parse("0.5").unwrap(), Expr::Number(0.5));
1162    }
1163
1164    #[test]
1165    fn test_string_parsing() {
1166        assert_eq!(
1167            parse(r#""hello""#).unwrap(),
1168            Expr::String("hello".to_string())
1169        );
1170    }
1171
1172    #[test]
1173    fn test_arithmetic() {
1174        let v = vars(&[]);
1175        assert!(eval_bool("1 + 2 == 3", &v).unwrap());
1176        assert!(eval_bool("10 - 3 == 7", &v).unwrap());
1177        assert!(eval_bool("4 * 5 == 20", &v).unwrap());
1178        assert!(eval_bool("10 / 2 == 5", &v).unwrap());
1179        assert!(eval_bool("2 ^ 3 == 8", &v).unwrap());
1180        assert!(eval_bool("1 + 2 * 3 == 7", &v).unwrap());
1181        assert!(eval_bool("(1 + 2) * 3 == 9", &v).unwrap());
1182    }
1183
1184    #[test]
1185    fn test_comparisons() {
1186        let v = vars(&[("n", Value::Number(42.0))]);
1187        assert!(eval_bool("n > 0", &v).unwrap());
1188        assert!(eval_bool("n < 100", &v).unwrap());
1189        assert!(eval_bool("n >= 42", &v).unwrap());
1190        assert!(eval_bool("n <= 42", &v).unwrap());
1191        assert!(eval_bool("n == 42", &v).unwrap());
1192        assert!(eval_bool("n != 0", &v).unwrap());
1193    }
1194
1195    #[test]
1196    fn test_boolean_logic() {
1197        let v = vars(&[("n", Value::Number(42.0))]);
1198        assert!(eval_bool("n > 0 and n < 100", &v).unwrap());
1199        assert!(eval_bool("n < 0 or n > 0", &v).unwrap());
1200        assert!(eval_bool("not (n < 0)", &v).unwrap());
1201    }
1202
1203    #[test]
1204    fn test_array_contains() {
1205        let v = vars(&[("n", Value::Number(2.0))]);
1206        assert!(eval_bool("[1, 2, 3] contains n", &v).unwrap());
1207        assert!(!eval_bool("[4, 5, 6] contains n", &v).unwrap());
1208    }
1209
1210    #[test]
1211    fn test_array_not_contains() {
1212        let v = vars(&[("n", Value::Number(5.0))]);
1213        assert!(eval_bool("not ([1, 2, 3] contains n)", &v).unwrap());
1214        assert!(!eval_bool("not ([4, 5, 6] contains n)", &v).unwrap());
1215    }
1216
1217    #[test]
1218    fn test_object_contains_key() {
1219        let mut obj = HashMap::new();
1220        obj.insert("name".to_string(), Value::String("alice".to_string()));
1221        obj.insert("age".to_string(), Value::Number(30.0));
1222        let v = vars(&[("o", Value::Object(obj))]);
1223        assert!(eval_bool("o contains \"name\"", &v).unwrap());
1224        assert!(eval_bool("o contains \"age\"", &v).unwrap());
1225        assert!(!eval_bool("o contains \"email\"", &v).unwrap());
1226    }
1227
1228    #[test]
1229    fn test_string_operators() {
1230        let v = vars(&[("s", Value::String("hello world".to_string()))]);
1231        assert!(eval_bool(r#"s contains "world""#, &v).unwrap());
1232        assert!(eval_bool(r#"s startswith "hello""#, &v).unwrap());
1233        assert!(eval_bool(r#"s endswith "world""#, &v).unwrap());
1234    }
1235
1236    #[test]
1237    fn test_negated_string_operators() {
1238        let v = vars(&[("s", Value::String("hello world".to_string()))]);
1239        assert!(eval_bool(r#"s not contains "foo""#, &v).unwrap());
1240        assert!(!eval_bool(r#"s not contains "world""#, &v).unwrap());
1241        assert!(eval_bool(r#"s not startswith "foo""#, &v).unwrap());
1242        assert!(!eval_bool(r#"s not startswith "hello""#, &v).unwrap());
1243        assert!(eval_bool(r#"s not endswith "foo""#, &v).unwrap());
1244        assert!(!eval_bool(r#"s not endswith "world""#, &v).unwrap());
1245    }
1246
1247    #[test]
1248    fn test_regex_matches() {
1249        let v = vars(&[("s", Value::String("hello123".to_string()))]);
1250        assert!(eval_bool(r#"s matches /^hello\d+$/"#, &v).unwrap());
1251    }
1252
1253    #[test]
1254    fn test_negated_regex_matches() {
1255        let v = vars(&[("s", Value::String("hello123".to_string()))]);
1256        assert!(eval_bool(r#"s not matches /^foo/"#, &v).unwrap());
1257        assert!(!eval_bool(r#"s not matches /^hello\d+$/"#, &v).unwrap());
1258    }
1259
1260    #[test]
1261    fn test_negated_array_contains() {
1262        let v = vars(&[("n", Value::Number(5.0))]);
1263        assert!(eval_bool("[1, 2, 3] not contains n", &v).unwrap());
1264        assert!(!eval_bool("[4, 5, 6] not contains n", &v).unwrap());
1265    }
1266
1267    #[test]
1268    fn test_len_function() {
1269        let v = vars(&[("s", Value::String("hello".to_string()))]);
1270        assert!(eval_bool("len(s) == 5", &v).unwrap());
1271    }
1272
1273    #[test]
1274    fn test_backslash_in_string() {
1275        // Test that backslash is parsed correctly
1276        let v = vars(&[("p", Value::String("C:\\Users\\test".to_string()))]);
1277
1278        // Should contain "test"
1279        assert!(eval_bool(r#"p contains "test""#, &v).unwrap());
1280
1281        // Should contain backslash (escaped in the expression)
1282        assert!(eval_bool(r#"p contains "\\""#, &v).unwrap());
1283
1284        // Should contain "Users"
1285        assert!(eval_bool(r#"p contains "Users""#, &v).unwrap());
1286    }
1287
1288    #[test]
1289    fn test_array_indexing() {
1290        let v = vars(&[(
1291            "a",
1292            Value::Array(vec![
1293                Value::Number(10.0),
1294                Value::Number(20.0),
1295                Value::Number(30.0),
1296            ]),
1297        )]);
1298        assert!(eval_bool("a[0] == 10", &v).unwrap());
1299        assert!(eval_bool("a[1] == 20", &v).unwrap());
1300        assert!(eval_bool("a[2] == 30", &v).unwrap());
1301    }
1302
1303    #[test]
1304    fn test_object_property_access() {
1305        let mut obj = HashMap::new();
1306        obj.insert("name".to_string(), Value::String("alice".to_string()));
1307        obj.insert("age".to_string(), Value::Number(30.0));
1308        let v = vars(&[("o", Value::Object(obj))]);
1309
1310        assert!(eval_bool(r#"o.name == "alice""#, &v).unwrap());
1311        assert!(eval_bool("o.age == 30", &v).unwrap());
1312        assert!(eval_bool(r#"o["name"] == "alice""#, &v).unwrap());
1313    }
1314
1315    #[test]
1316    fn test_nested_access() {
1317        let inner = Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]);
1318        let mut obj = HashMap::new();
1319        obj.insert("items".to_string(), inner);
1320        let v = vars(&[("o", Value::Object(obj))]);
1321
1322        assert!(eval_bool("o.items[0] == 1", &v).unwrap());
1323        assert!(eval_bool("o.items[1] == 2", &v).unwrap());
1324        assert!(eval_bool("len(o.items) == 2", &v).unwrap());
1325    }
1326
1327    #[test]
1328    fn test_type_function() {
1329        let v = vars(&[
1330            ("n", Value::Number(42.0)),
1331            ("s", Value::String("hello".to_string())),
1332            ("b", Value::Bool(true)),
1333            ("a", Value::Array(vec![])),
1334        ]);
1335
1336        assert!(eval_bool("type(n) == number", &v).unwrap());
1337        assert!(eval_bool("type(s) == string", &v).unwrap());
1338        assert!(eval_bool("type(b) == bool", &v).unwrap());
1339        assert!(eval_bool("type(a) == array", &v).unwrap());
1340    }
1341
1342    #[test]
1343    fn test_keys_function() {
1344        let mut obj = HashMap::new();
1345        obj.insert("a".to_string(), Value::Number(1.0));
1346        obj.insert("b".to_string(), Value::Number(2.0));
1347        let v = vars(&[("o", Value::Object(obj))]);
1348
1349        assert!(eval_bool("len(keys(o)) == 2", &v).unwrap());
1350    }
1351
1352    #[test]
1353    fn test_forall_array() {
1354        let v = vars(&[(
1355            "a",
1356            Value::Array(vec![
1357                Value::Number(1.0),
1358                Value::Number(2.0),
1359                Value::Number(3.0),
1360            ]),
1361        )]);
1362
1363        assert!(eval_bool("x <= 3 forall x in a", &v).unwrap());
1364        assert!(eval_bool("x > 0 forall x in a", &v).unwrap());
1365        assert!(!eval_bool("x > 2 forall x in a", &v).unwrap());
1366    }
1367
1368    #[test]
1369    fn test_forall_object() {
1370        let mut obj = HashMap::new();
1371        obj.insert("a".to_string(), Value::Number(1.0));
1372        obj.insert("b".to_string(), Value::Number(2.0));
1373        obj.insert("c".to_string(), Value::Number(3.0));
1374        let v = vars(&[("o", Value::Object(obj))]);
1375
1376        assert!(eval_bool("x <= 3 forall x in o", &v).unwrap());
1377        assert!(eval_bool("type(x) == number forall x in o", &v).unwrap());
1378    }
1379
1380    #[test]
1381    fn test_object_literal() {
1382        let v = vars(&[]);
1383        assert!(eval_bool(r#"{"a": 1, "b": 2}.a == 1"#, &v).unwrap());
1384        assert!(eval_bool(r#"len({"x": 1, "y": 2}) == 2"#, &v).unwrap());
1385    }
1386
1387    #[test]
1388    fn test_type_literal() {
1389        let v = vars(&[("n", Value::Number(42.0))]);
1390        assert!(eval_bool("type(n) == number", &v).unwrap());
1391        assert!(!eval_bool("type(n) == string", &v).unwrap());
1392    }
1393
1394    #[test]
1395    fn test_len_object() {
1396        let mut obj = HashMap::new();
1397        obj.insert("a".to_string(), Value::Number(1.0));
1398        obj.insert("b".to_string(), Value::Number(2.0));
1399        let v = vars(&[("o", Value::Object(obj))]);
1400
1401        assert!(eval_bool("len(o) == 2", &v).unwrap());
1402    }
1403
1404    #[test]
1405    fn test_bool_comparison() {
1406        let v = vars(&[("b", Value::Bool(true))]);
1407        assert!(eval_bool("b == true", &v).unwrap());
1408        assert!(eval_bool("b != false", &v).unwrap());
1409        assert!(eval_bool("(1 == 1) == true", &v).unwrap());
1410    }
1411}