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