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