1use 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#[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#[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
220fn 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" => Ok(Expr::TypeLiteral(name)),
322 "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 delimited(
342 '"',
343 repeat(0.., string_char).fold(String::new, |mut s, c| {
344 s.push(c);
345 s
346 }),
347 '"',
348 ),
349 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 original_input = input.trim();
660 let mut input = original_input;
661 match expr.parse_next(&mut input) {
662 Ok(e) => {
663 let remaining = input.trim();
664 if remaining.is_empty() {
665 Ok(e)
666 } else {
667 Err(EvalError::ParseError(format!(
668 "unexpected trailing input: '{}'",
669 remaining
670 )))
671 }
672 }
673 Err(_) => {
674 if original_input.starts_with('#') {
676 Err(EvalError::ParseError(
677 "comments are not supported (lines starting with '#' are treated as constraints)".to_string()
678 ))
679 } else if original_input.contains("//") {
680 Err(EvalError::ParseError(
681 "comments are not supported ('// ...' is not valid)".to_string(),
682 ))
683 } else if original_input.is_empty() {
684 Err(EvalError::ParseError("empty constraint".to_string()))
685 } else {
686 let first_word = original_input.split_whitespace().next().unwrap_or("");
688 if !first_word
689 .chars()
690 .next()
691 .map(|c| c.is_alphabetic() || c == '_')
692 .unwrap_or(false)
693 && !first_word.starts_with('(')
694 && !first_word.starts_with('-')
695 && !first_word.starts_with('"')
696 && !first_word.starts_with('[')
697 && !first_word.starts_with('{')
698 && !first_word
699 .chars()
700 .next()
701 .map(|c| c.is_numeric())
702 .unwrap_or(false)
703 {
704 Err(EvalError::ParseError(format!(
705 "invalid syntax near '{}' - constraints must be expressions like 'x > 0' or 'len(arr) == 3'",
706 first_word
707 )))
708 } else {
709 Err(EvalError::ParseError(format!(
710 "invalid expression syntax in '{}'",
711 if original_input.len() > 50 {
712 format!("{}...", &original_input[..50])
713 } else {
714 original_input.to_string()
715 }
716 )))
717 }
718 }
719 }
720 }
721}
722
723pub fn evaluate(expr: &Expr, vars: &HashMap<String, Value>) -> Result<Value, EvalError> {
726 match expr {
727 Expr::Number(n) => Ok(Value::Number(*n)),
728 Expr::String(s) => Ok(Value::String(s.clone())),
729 Expr::Bool(b) => Ok(Value::Bool(*b)),
730 Expr::Null => Ok(Value::Null),
731 Expr::TypeLiteral(t) => Ok(Value::Type(t.clone())),
732 Expr::Var(name) => vars
733 .get(name)
734 .cloned()
735 .ok_or_else(|| EvalError::UndefinedVariable(name.clone())),
736 Expr::Array(elements) => {
737 let values: Result<Vec<_>, _> = elements.iter().map(|e| evaluate(e, vars)).collect();
738 Ok(Value::Array(values?))
739 }
740 Expr::Object(entries) => {
741 let mut map = HashMap::new();
742 for (key, val_expr) in entries {
743 map.insert(key.clone(), evaluate(val_expr, vars)?);
744 }
745 Ok(Value::Object(map))
746 }
747 Expr::UnaryOp { op, expr } => {
748 let val = evaluate(expr, vars)?;
749 match op {
750 UnaryOp::Not => Ok(Value::Bool(!val.as_bool()?)),
751 UnaryOp::Neg => Ok(Value::Number(-val.as_number()?)),
752 }
753 }
754 Expr::BinaryOp { op, left, right } => eval_binary_op(*op, left, right, vars),
755 Expr::FuncCall { name, args } => eval_func_call(name, args, vars),
756 Expr::Index { expr, index } => {
757 let base = evaluate(expr, vars)?;
758 let idx = evaluate(index, vars)?;
759 match &base {
760 Value::Array(arr) => {
761 let i = idx.as_number()?;
762 let actual_index = if i < 0.0 {
763 let neg_idx = (-i) as usize;
765 if neg_idx > arr.len() {
766 return Err(EvalError::IndexOutOfBounds {
767 index: i as i64,
768 len: arr.len(),
769 });
770 }
771 arr.len() - neg_idx
772 } else {
773 i as usize
774 };
775 arr.get(actual_index)
776 .cloned()
777 .ok_or(EvalError::IndexOutOfBounds {
778 index: i as i64,
779 len: arr.len(),
780 })
781 }
782 Value::String(s) => {
783 let i = idx.as_number()?;
784 let chars: Vec<char> = s.chars().collect();
785 let actual_index = if i < 0.0 {
786 let neg_idx = (-i) as usize;
787 if neg_idx > chars.len() {
788 return Err(EvalError::IndexOutOfBounds {
789 index: i as i64,
790 len: chars.len(),
791 });
792 }
793 chars.len() - neg_idx
794 } else {
795 i as usize
796 };
797 chars
798 .get(actual_index)
799 .map(|c| Value::String(c.to_string()))
800 .ok_or(EvalError::IndexOutOfBounds {
801 index: i as i64,
802 len: chars.len(),
803 })
804 }
805 Value::Object(obj) => {
806 let key = idx.as_string()?;
807 obj.get(key)
808 .cloned()
809 .ok_or_else(|| EvalError::KeyNotFound(key.to_string()))
810 }
811 _ => Err(EvalError::TypeError {
812 expected: "array, string, or object",
813 got: base.type_name(),
814 }),
815 }
816 }
817 Expr::Property { expr, name } => {
818 let base = evaluate(expr, vars)?;
819 let obj = base.as_object()?;
820 obj.get(name)
821 .cloned()
822 .ok_or_else(|| EvalError::KeyNotFound(name.clone()))
823 }
824 Expr::ForAll {
825 predicate,
826 var,
827 iterable,
828 } => {
829 let iter_val = evaluate(iterable, vars)?;
830 let items = match &iter_val {
831 Value::Array(arr) => arr.clone(),
832 Value::Object(obj) => obj.values().cloned().collect(),
833 _ => {
834 return Err(EvalError::TypeError {
835 expected: "array or object",
836 got: iter_val.type_name(),
837 });
838 }
839 };
840 for item in items {
841 let mut local_vars = vars.clone();
842 local_vars.insert(var.clone(), item);
843 let result = evaluate(predicate, &local_vars)?;
844 if !result.as_bool()? {
845 return Ok(Value::Bool(false));
846 }
847 }
848 Ok(Value::Bool(true))
849 }
850 }
851}
852
853fn eval_func_call(
854 name: &str,
855 args: &[Expr],
856 vars: &HashMap<String, Value>,
857) -> Result<Value, EvalError> {
858 match name {
859 "len" => {
860 if args.len() != 1 {
861 return Err(EvalError::WrongArgCount {
862 func: name.to_string(),
863 expected: 1,
864 got: args.len(),
865 });
866 }
867 let val = evaluate(&args[0], vars)?;
868 match val {
869 Value::String(s) => Ok(Value::Number(s.chars().count() as f64)),
870 Value::Array(a) => Ok(Value::Number(a.len() as f64)),
871 Value::Object(o) => Ok(Value::Number(o.len() as f64)),
872 _ => Err(EvalError::TypeError {
873 expected: "string, array, or object",
874 got: val.type_name(),
875 }),
876 }
877 }
878 "type" => {
879 if args.len() != 1 {
880 return Err(EvalError::WrongArgCount {
881 func: name.to_string(),
882 expected: 1,
883 got: args.len(),
884 });
885 }
886 let val = evaluate(&args[0], vars)?;
887 Ok(Value::Type(val.type_name().to_string()))
888 }
889 "keys" => {
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 obj = val.as_object()?;
899 let mut keys: Vec<String> = obj.keys().cloned().collect();
900 keys.sort();
901 let keys: Vec<Value> = keys.into_iter().map(Value::String).collect();
902 Ok(Value::Array(keys))
903 }
904 "values" => {
905 if args.len() != 1 {
906 return Err(EvalError::WrongArgCount {
907 func: name.to_string(),
908 expected: 1,
909 got: args.len(),
910 });
911 }
912 let val = evaluate(&args[0], vars)?;
913 let obj = val.as_object()?;
914 let mut pairs: Vec<(&String, &Value)> = obj.iter().collect();
916 pairs.sort_by_key(|(k, _)| *k);
917 let values: Vec<Value> = pairs.into_iter().map(|(_, v)| v.clone()).collect();
918 Ok(Value::Array(values))
919 }
920 "sum" => {
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 let arr = val.as_array()?;
930 let mut total = 0.0;
931 for item in arr {
932 total += item.as_number()?;
933 }
934 Ok(Value::Number(total))
935 }
936 "min" => {
937 if args.len() != 1 {
938 return Err(EvalError::WrongArgCount {
939 func: name.to_string(),
940 expected: 1,
941 got: args.len(),
942 });
943 }
944 let val = evaluate(&args[0], vars)?;
945 let arr = val.as_array()?;
946 if arr.is_empty() {
947 return Err(EvalError::TypeError {
948 expected: "non-empty array",
949 got: "empty array",
950 });
951 }
952 let mut min_val = arr[0].as_number()?;
953 for item in arr.iter().skip(1) {
954 let n = item.as_number()?;
955 if n < min_val {
956 min_val = n;
957 }
958 }
959 Ok(Value::Number(min_val))
960 }
961 "max" => {
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 let arr = val.as_array()?;
971 if arr.is_empty() {
972 return Err(EvalError::TypeError {
973 expected: "non-empty array",
974 got: "empty array",
975 });
976 }
977 let mut max_val = arr[0].as_number()?;
978 for item in arr.iter().skip(1) {
979 let n = item.as_number()?;
980 if n > max_val {
981 max_val = n;
982 }
983 }
984 Ok(Value::Number(max_val))
985 }
986 "abs" => {
987 if args.len() != 1 {
988 return Err(EvalError::WrongArgCount {
989 func: name.to_string(),
990 expected: 1,
991 got: args.len(),
992 });
993 }
994 let val = evaluate(&args[0], vars)?;
995 Ok(Value::Number(val.as_number()?.abs()))
996 }
997 "lower" => {
998 if args.len() != 1 {
999 return Err(EvalError::WrongArgCount {
1000 func: name.to_string(),
1001 expected: 1,
1002 got: args.len(),
1003 });
1004 }
1005 let val = evaluate(&args[0], vars)?;
1006 Ok(Value::String(val.as_string()?.to_lowercase()))
1007 }
1008 "upper" => {
1009 if args.len() != 1 {
1010 return Err(EvalError::WrongArgCount {
1011 func: name.to_string(),
1012 expected: 1,
1013 got: args.len(),
1014 });
1015 }
1016 let val = evaluate(&args[0], vars)?;
1017 Ok(Value::String(val.as_string()?.to_uppercase()))
1018 }
1019 "unique" => {
1020 if args.len() != 1 {
1021 return Err(EvalError::WrongArgCount {
1022 func: name.to_string(),
1023 expected: 1,
1024 got: args.len(),
1025 });
1026 }
1027 let val = evaluate(&args[0], vars)?;
1028 let arr = val.as_array()?;
1029 let mut result = Vec::new();
1030 for item in arr {
1031 if !result.iter().any(|v| values_equal(v, item)) {
1032 result.push(item.clone());
1033 }
1034 }
1035 Ok(Value::Array(result))
1036 }
1037 "env" => {
1038 if args.len() != 1 {
1039 return Err(EvalError::WrongArgCount {
1040 func: name.to_string(),
1041 expected: 1,
1042 got: args.len(),
1043 });
1044 }
1045 let val = evaluate(&args[0], vars)?;
1046 let var_name = val.as_string()?;
1047 match std::env::var(var_name) {
1048 Ok(value) => Ok(Value::String(value)),
1049 Err(_) => Ok(Value::Null),
1050 }
1051 }
1052 _ => Err(EvalError::UndefinedFunction(name.to_string())),
1053 }
1054}
1055
1056fn eval_binary_op(
1057 op: BinaryOp,
1058 left: &Expr,
1059 right: &Expr,
1060 vars: &HashMap<String, Value>,
1061) -> Result<Value, EvalError> {
1062 if op == BinaryOp::And {
1063 let l = evaluate(left, vars)?.as_bool()?;
1064 if !l {
1065 return Ok(Value::Bool(false));
1066 }
1067 return Ok(Value::Bool(evaluate(right, vars)?.as_bool()?));
1068 }
1069 if op == BinaryOp::Or {
1070 let l = evaluate(left, vars)?.as_bool()?;
1071 if l {
1072 return Ok(Value::Bool(true));
1073 }
1074 return Ok(Value::Bool(evaluate(right, vars)?.as_bool()?));
1075 }
1076
1077 let l = evaluate(left, vars)?;
1078 let r = evaluate(right, vars)?;
1079
1080 match op {
1081 BinaryOp::Add => match (&l, &r) {
1082 (Value::String(ls), Value::String(rs)) => Ok(Value::String(format!("{}{}", ls, rs))),
1083 (Value::Array(la), Value::Array(ra)) => {
1084 let mut result = la.clone();
1085 result.extend(ra.clone());
1086 Ok(Value::Array(result))
1087 }
1088 _ => Ok(Value::Number(l.as_number()? + r.as_number()?)),
1089 },
1090 BinaryOp::Sub => Ok(Value::Number(l.as_number()? - r.as_number()?)),
1091 BinaryOp::Mul => Ok(Value::Number(l.as_number()? * r.as_number()?)),
1092 BinaryOp::Mod => {
1093 let divisor = r.as_number()?;
1094 if divisor == 0.0 {
1095 Err(EvalError::DivisionByZero)
1096 } else {
1097 Ok(Value::Number(l.as_number()? % divisor))
1098 }
1099 }
1100 BinaryOp::Div => {
1101 let divisor = r.as_number()?;
1102 if divisor == 0.0 {
1103 Err(EvalError::DivisionByZero)
1104 } else {
1105 Ok(Value::Number(l.as_number()? / divisor))
1106 }
1107 }
1108 BinaryOp::Pow => Ok(Value::Number(l.as_number()?.powf(r.as_number()?))),
1109 BinaryOp::Eq => Ok(Value::Bool(values_equal(&l, &r))),
1110 BinaryOp::Ne => Ok(Value::Bool(!values_equal(&l, &r))),
1111 BinaryOp::Lt => match (&l, &r) {
1112 (Value::String(ls), Value::String(rs)) => Ok(Value::Bool(ls < rs)),
1113 _ => Ok(Value::Bool(l.as_number()? < r.as_number()?)),
1114 },
1115 BinaryOp::Le => match (&l, &r) {
1116 (Value::String(ls), Value::String(rs)) => Ok(Value::Bool(ls <= rs)),
1117 _ => Ok(Value::Bool(l.as_number()? <= r.as_number()?)),
1118 },
1119 BinaryOp::Gt => match (&l, &r) {
1120 (Value::String(ls), Value::String(rs)) => Ok(Value::Bool(ls > rs)),
1121 _ => Ok(Value::Bool(l.as_number()? > r.as_number()?)),
1122 },
1123 BinaryOp::Ge => match (&l, &r) {
1124 (Value::String(ls), Value::String(rs)) => Ok(Value::Bool(ls >= rs)),
1125 _ => Ok(Value::Bool(l.as_number()? >= r.as_number()?)),
1126 },
1127 BinaryOp::Contains | BinaryOp::NotContains => {
1128 let result = match &l {
1129 Value::String(haystack) => {
1130 let needle = r.as_string()?;
1131 haystack.contains(needle)
1132 }
1133 Value::Array(arr) => arr.iter().any(|v| values_equal(v, &r)),
1134 Value::Object(obj) => {
1135 let key = r.as_string()?;
1136 obj.contains_key(key)
1137 }
1138 _ => {
1139 return Err(EvalError::TypeError {
1140 expected: "string, array, or object",
1141 got: l.type_name(),
1142 })
1143 }
1144 };
1145 Ok(Value::Bool(if op == BinaryOp::NotContains {
1146 !result
1147 } else {
1148 result
1149 }))
1150 }
1151 BinaryOp::StartsWith | BinaryOp::NotStartsWith => {
1152 let s = l.as_string()?;
1153 let prefix = r.as_string()?;
1154 let result = s.starts_with(prefix);
1155 Ok(Value::Bool(if op == BinaryOp::NotStartsWith {
1156 !result
1157 } else {
1158 result
1159 }))
1160 }
1161 BinaryOp::EndsWith | BinaryOp::NotEndsWith => {
1162 let s = l.as_string()?;
1163 let suffix = r.as_string()?;
1164 let result = s.ends_with(suffix);
1165 Ok(Value::Bool(if op == BinaryOp::NotEndsWith {
1166 !result
1167 } else {
1168 result
1169 }))
1170 }
1171 BinaryOp::Matches | BinaryOp::NotMatches => {
1172 let s = l.as_string()?;
1173 let pattern = r.as_string()?;
1174 let re =
1175 regex::Regex::new(pattern).map_err(|e| EvalError::InvalidRegex(e.to_string()))?;
1176 let result = re.is_match(s);
1177 Ok(Value::Bool(if op == BinaryOp::NotMatches {
1178 !result
1179 } else {
1180 result
1181 }))
1182 }
1183 BinaryOp::And | BinaryOp::Or => unreachable!(),
1184 }
1185}
1186
1187fn values_equal(a: &Value, b: &Value) -> bool {
1188 match (a, b) {
1189 (Value::Number(a), Value::Number(b)) => (a - b).abs() < f64::EPSILON,
1190 (Value::String(a), Value::String(b)) => a == b,
1191 (Value::Bool(a), Value::Bool(b)) => a == b,
1192 (Value::Null, Value::Null) => true,
1193 (Value::Null, Value::Type(t)) | (Value::Type(t), Value::Null) => t == "null",
1195 (Value::Array(a), Value::Array(b)) => {
1196 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
1197 }
1198 (Value::Object(a), Value::Object(b)) => {
1199 a.len() == b.len()
1200 && a.iter()
1201 .all(|(k, v)| b.get(k).map(|bv| values_equal(v, bv)).unwrap_or(false))
1202 }
1203 (Value::Type(a), Value::Type(b)) => a == b,
1204 _ => false,
1205 }
1206}
1207
1208pub fn eval_bool(expr_str: &str, vars: &HashMap<String, Value>) -> Result<bool, EvalError> {
1211 let ast = parse(expr_str)?;
1212 let result = evaluate(&ast, vars)?;
1213 result.as_bool()
1214}
1215
1216#[cfg(test)]
1217mod tests {
1218 use super::*;
1219
1220 fn vars(pairs: &[(&str, Value)]) -> HashMap<String, Value> {
1221 pairs
1222 .iter()
1223 .map(|(k, v)| (k.to_string(), v.clone()))
1224 .collect()
1225 }
1226
1227 #[test]
1228 fn test_number_parsing() {
1229 assert_eq!(parse("42").unwrap(), Expr::Number(42.0));
1230 assert_eq!(parse("0.5").unwrap(), Expr::Number(0.5));
1231 }
1232
1233 #[test]
1234 fn test_string_parsing() {
1235 assert_eq!(
1236 parse(r#""hello""#).unwrap(),
1237 Expr::String("hello".to_string())
1238 );
1239 }
1240
1241 #[test]
1242 fn test_arithmetic() {
1243 let v = vars(&[]);
1244 assert!(eval_bool("1 + 2 == 3", &v).unwrap());
1245 assert!(eval_bool("10 - 3 == 7", &v).unwrap());
1246 assert!(eval_bool("4 * 5 == 20", &v).unwrap());
1247 assert!(eval_bool("10 / 2 == 5", &v).unwrap());
1248 assert!(eval_bool("2 ^ 3 == 8", &v).unwrap());
1249 assert!(eval_bool("1 + 2 * 3 == 7", &v).unwrap());
1250 assert!(eval_bool("(1 + 2) * 3 == 9", &v).unwrap());
1251 }
1252
1253 #[test]
1254 fn test_comparisons() {
1255 let v = vars(&[("n", Value::Number(42.0))]);
1256 assert!(eval_bool("n > 0", &v).unwrap());
1257 assert!(eval_bool("n < 100", &v).unwrap());
1258 assert!(eval_bool("n >= 42", &v).unwrap());
1259 assert!(eval_bool("n <= 42", &v).unwrap());
1260 assert!(eval_bool("n == 42", &v).unwrap());
1261 assert!(eval_bool("n != 0", &v).unwrap());
1262 }
1263
1264 #[test]
1265 fn test_boolean_logic() {
1266 let v = vars(&[("n", Value::Number(42.0))]);
1267 assert!(eval_bool("n > 0 and n < 100", &v).unwrap());
1268 assert!(eval_bool("n < 0 or n > 0", &v).unwrap());
1269 assert!(eval_bool("not (n < 0)", &v).unwrap());
1270 }
1271
1272 #[test]
1273 fn test_array_contains() {
1274 let v = vars(&[("n", Value::Number(2.0))]);
1275 assert!(eval_bool("[1, 2, 3] contains n", &v).unwrap());
1276 assert!(!eval_bool("[4, 5, 6] contains n", &v).unwrap());
1277 }
1278
1279 #[test]
1280 fn test_array_not_contains() {
1281 let v = vars(&[("n", Value::Number(5.0))]);
1282 assert!(eval_bool("not ([1, 2, 3] contains n)", &v).unwrap());
1283 assert!(!eval_bool("not ([4, 5, 6] contains n)", &v).unwrap());
1284 }
1285
1286 #[test]
1287 fn test_object_contains_key() {
1288 let mut obj = HashMap::new();
1289 obj.insert("name".to_string(), Value::String("alice".to_string()));
1290 obj.insert("age".to_string(), Value::Number(30.0));
1291 let v = vars(&[("o", Value::Object(obj))]);
1292 assert!(eval_bool("o contains \"name\"", &v).unwrap());
1293 assert!(eval_bool("o contains \"age\"", &v).unwrap());
1294 assert!(!eval_bool("o contains \"email\"", &v).unwrap());
1295 }
1296
1297 #[test]
1298 fn test_string_operators() {
1299 let v = vars(&[("s", Value::String("hello world".to_string()))]);
1300 assert!(eval_bool(r#"s contains "world""#, &v).unwrap());
1301 assert!(eval_bool(r#"s startswith "hello""#, &v).unwrap());
1302 assert!(eval_bool(r#"s endswith "world""#, &v).unwrap());
1303 }
1304
1305 #[test]
1306 fn test_negated_string_operators() {
1307 let v = vars(&[("s", Value::String("hello world".to_string()))]);
1308 assert!(eval_bool(r#"s not contains "foo""#, &v).unwrap());
1309 assert!(!eval_bool(r#"s not contains "world""#, &v).unwrap());
1310 assert!(eval_bool(r#"s not startswith "foo""#, &v).unwrap());
1311 assert!(!eval_bool(r#"s not startswith "hello""#, &v).unwrap());
1312 assert!(eval_bool(r#"s not endswith "foo""#, &v).unwrap());
1313 assert!(!eval_bool(r#"s not endswith "world""#, &v).unwrap());
1314 }
1315
1316 #[test]
1317 fn test_regex_matches() {
1318 let v = vars(&[("s", Value::String("hello123".to_string()))]);
1319 assert!(eval_bool(r#"s matches /^hello\d+$/"#, &v).unwrap());
1320 }
1321
1322 #[test]
1323 fn test_negated_regex_matches() {
1324 let v = vars(&[("s", Value::String("hello123".to_string()))]);
1325 assert!(eval_bool(r#"s not matches /^foo/"#, &v).unwrap());
1326 assert!(!eval_bool(r#"s not matches /^hello\d+$/"#, &v).unwrap());
1327 }
1328
1329 #[test]
1330 fn test_negated_array_contains() {
1331 let v = vars(&[("n", Value::Number(5.0))]);
1332 assert!(eval_bool("[1, 2, 3] not contains n", &v).unwrap());
1333 assert!(!eval_bool("[4, 5, 6] not contains n", &v).unwrap());
1334 }
1335
1336 #[test]
1337 fn test_len_function() {
1338 let v = vars(&[("s", Value::String("hello".to_string()))]);
1339 assert!(eval_bool("len(s) == 5", &v).unwrap());
1340 }
1341
1342 #[test]
1343 fn test_backslash_in_string() {
1344 let v = vars(&[("p", Value::String("C:\\Users\\test".to_string()))]);
1346
1347 assert!(eval_bool(r#"p contains "test""#, &v).unwrap());
1349
1350 assert!(eval_bool(r#"p contains "\\""#, &v).unwrap());
1352
1353 assert!(eval_bool(r#"p contains "Users""#, &v).unwrap());
1355 }
1356
1357 #[test]
1358 fn test_array_indexing() {
1359 let v = vars(&[(
1360 "a",
1361 Value::Array(vec![
1362 Value::Number(10.0),
1363 Value::Number(20.0),
1364 Value::Number(30.0),
1365 ]),
1366 )]);
1367 assert!(eval_bool("a[0] == 10", &v).unwrap());
1368 assert!(eval_bool("a[1] == 20", &v).unwrap());
1369 assert!(eval_bool("a[2] == 30", &v).unwrap());
1370 }
1371
1372 #[test]
1373 fn test_object_property_access() {
1374 let mut obj = HashMap::new();
1375 obj.insert("name".to_string(), Value::String("alice".to_string()));
1376 obj.insert("age".to_string(), Value::Number(30.0));
1377 let v = vars(&[("o", Value::Object(obj))]);
1378
1379 assert!(eval_bool(r#"o.name == "alice""#, &v).unwrap());
1380 assert!(eval_bool("o.age == 30", &v).unwrap());
1381 assert!(eval_bool(r#"o["name"] == "alice""#, &v).unwrap());
1382 }
1383
1384 #[test]
1385 fn test_nested_access() {
1386 let inner = Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]);
1387 let mut obj = HashMap::new();
1388 obj.insert("items".to_string(), inner);
1389 let v = vars(&[("o", Value::Object(obj))]);
1390
1391 assert!(eval_bool("o.items[0] == 1", &v).unwrap());
1392 assert!(eval_bool("o.items[1] == 2", &v).unwrap());
1393 assert!(eval_bool("len(o.items) == 2", &v).unwrap());
1394 }
1395
1396 #[test]
1397 fn test_type_function() {
1398 let v = vars(&[
1399 ("n", Value::Number(42.0)),
1400 ("s", Value::String("hello".to_string())),
1401 ("b", Value::Bool(true)),
1402 ("a", Value::Array(vec![])),
1403 ]);
1404
1405 assert!(eval_bool("type(n) == number", &v).unwrap());
1406 assert!(eval_bool("type(s) == string", &v).unwrap());
1407 assert!(eval_bool("type(b) == bool", &v).unwrap());
1408 assert!(eval_bool("type(a) == array", &v).unwrap());
1409 }
1410
1411 #[test]
1412 fn test_keys_function() {
1413 let mut obj = HashMap::new();
1414 obj.insert("a".to_string(), Value::Number(1.0));
1415 obj.insert("b".to_string(), Value::Number(2.0));
1416 let v = vars(&[("o", Value::Object(obj))]);
1417
1418 assert!(eval_bool("len(keys(o)) == 2", &v).unwrap());
1419 }
1420
1421 #[test]
1422 fn test_forall_array() {
1423 let v = vars(&[(
1424 "a",
1425 Value::Array(vec![
1426 Value::Number(1.0),
1427 Value::Number(2.0),
1428 Value::Number(3.0),
1429 ]),
1430 )]);
1431
1432 assert!(eval_bool("x <= 3 forall x in a", &v).unwrap());
1433 assert!(eval_bool("x > 0 forall x in a", &v).unwrap());
1434 assert!(!eval_bool("x > 2 forall x in a", &v).unwrap());
1435 }
1436
1437 #[test]
1438 fn test_forall_object() {
1439 let mut obj = HashMap::new();
1440 obj.insert("a".to_string(), Value::Number(1.0));
1441 obj.insert("b".to_string(), Value::Number(2.0));
1442 obj.insert("c".to_string(), Value::Number(3.0));
1443 let v = vars(&[("o", Value::Object(obj))]);
1444
1445 assert!(eval_bool("x <= 3 forall x in o", &v).unwrap());
1446 assert!(eval_bool("type(x) == number forall x in o", &v).unwrap());
1447 }
1448
1449 #[test]
1450 fn test_object_literal() {
1451 let v = vars(&[]);
1452 assert!(eval_bool(r#"{"a": 1, "b": 2}.a == 1"#, &v).unwrap());
1453 assert!(eval_bool(r#"len({"x": 1, "y": 2}) == 2"#, &v).unwrap());
1454 }
1455
1456 #[test]
1457 fn test_type_literal() {
1458 let v = vars(&[("n", Value::Number(42.0))]);
1459 assert!(eval_bool("type(n) == number", &v).unwrap());
1460 assert!(!eval_bool("type(n) == string", &v).unwrap());
1461 }
1462
1463 #[test]
1464 fn test_len_object() {
1465 let mut obj = HashMap::new();
1466 obj.insert("a".to_string(), Value::Number(1.0));
1467 obj.insert("b".to_string(), Value::Number(2.0));
1468 let v = vars(&[("o", Value::Object(obj))]);
1469
1470 assert!(eval_bool("len(o) == 2", &v).unwrap());
1471 }
1472
1473 #[test]
1474 fn test_bool_comparison() {
1475 let v = vars(&[("b", Value::Bool(true))]);
1476 assert!(eval_bool("b == true", &v).unwrap());
1477 assert!(eval_bool("b != false", &v).unwrap());
1478 assert!(eval_bool("(1 == 1) == true", &v).unwrap());
1479 }
1480
1481 #[test]
1482 fn test_env_function() {
1483 std::env::set_var("CCTR_TEST_VAR", "test_value");
1484 let v = vars(&[]);
1485 assert!(eval_bool(r#"env("CCTR_TEST_VAR") == "test_value""#, &v).unwrap());
1486 assert!(eval_bool(r#"type(env("CCTR_TEST_VAR")) == string"#, &v).unwrap());
1487 assert!(eval_bool(r#"env("CCTR_NONEXISTENT_VAR_12345") == null"#, &v).unwrap());
1489 assert!(eval_bool(r#"type(env("CCTR_NONEXISTENT_VAR_12345")) == null"#, &v).unwrap());
1490 std::env::remove_var("CCTR_TEST_VAR");
1491 }
1492}