Skip to main content

acts_expr/
eval.rs

1//! The interpreter, and the injection surface it evaluates against.
2
3use crate::parser::{BinOp, Node};
4use crate::value::Value;
5use crate::{Error, Result};
6use std::collections::HashMap;
7use std::sync::Arc;
8
9/// The variables and methods one evaluation sees.
10///
11/// Variables are plain values — scalars, lists and objects ([`Value::Map`]) —
12/// and functions are [`Value::Function`]s, registered under the name they are
13/// called by. A function is called with its arguments as `f(x)`, or as a
14/// method with its receiver first, `x.f()`; a function stored *inside* an
15/// object is called that way through the object (`step1.normalize()`), which
16/// is how an injected object carries its own methods.
17#[derive(Debug, Clone, Default)]
18pub struct Context {
19    values: HashMap<Arc<str>, Value>,
20}
21
22impl Context {
23    pub fn new() -> Context {
24        Context::default()
25    }
26
27    /// Bind a name, replacing whatever it held. Chainable.
28    pub fn set(&mut self, name: impl Into<Arc<str>>, value: impl Into<Value>) -> &mut Context {
29        self.values.insert(name.into(), value.into());
30        self
31    }
32
33    pub fn get(&self, name: &str) -> Option<&Value> {
34        self.values.get(name)
35    }
36
37    pub fn contains(&self, name: &str) -> bool {
38        self.values.contains_key(name)
39    }
40
41    pub fn remove(&mut self, name: &str) -> Option<Value> {
42        self.values.remove(name)
43    }
44
45    pub fn clear(&mut self) {
46        self.values.clear();
47    }
48
49    pub fn len(&self) -> usize {
50        self.values.len()
51    }
52
53    pub fn is_empty(&self) -> bool {
54        self.values.is_empty()
55    }
56
57    /// The bound names, in no particular order.
58    pub fn names(&self) -> impl Iterator<Item = &str> {
59        self.values.keys().map(AsRef::as_ref)
60    }
61}
62
63impl<'a> FromIterator<(&'a str, Value)> for Context {
64    fn from_iter<T: IntoIterator<Item = (&'a str, Value)>>(iter: T) -> Self {
65        Context {
66            values: iter
67                .into_iter()
68                .map(|(name, value)| (Arc::from(name), value))
69                .collect(),
70        }
71    }
72}
73
74pub(crate) fn eval(nodes: &[Node], root: u32, context: &Context) -> Result<Value> {
75    eval_node(nodes, root, context)
76}
77
78fn eval_node(nodes: &[Node], index: u32, context: &Context) -> Result<Value> {
79    match &nodes[index as usize] {
80        Node::Null => Ok(Value::Null),
81        Node::Bool(b) => Ok(Value::Bool(*b)),
82        Node::Int(i) => Ok(Value::Int(*i)),
83        Node::Float(f) => Ok(Value::Float(*f)),
84        Node::Str(s) => Ok(Value::Str(s.clone())),
85        Node::Ident(name) => context.get(name).cloned().ok_or_else(|| Error::Unknown {
86            name: name.to_string(),
87        }),
88        Node::Member(target, name) => {
89            let value = eval_node(nodes, *target, context)?;
90            let map = value.as_map().ok_or_else(|| type_error(".", &value))?;
91
92            map.get(name.as_ref())
93                .cloned()
94                .ok_or_else(|| Error::Missing {
95                    name: name.to_string(),
96                })
97        }
98        Node::Index(target, key) => {
99            let container = eval_node(nodes, *target, context)?;
100            let key = eval_node(nodes, *key, context)?;
101
102            match (&container, &key) {
103                (Value::List(items), Value::Int(i)) => {
104                    let len = items.len();
105                    let position = usize::try_from(*i)
106                        .ok()
107                        .filter(|position| *position < len)
108                        .ok_or(Error::OutOfRange { index: *i, len })?;
109
110                    Ok(items[position].clone())
111                }
112                (Value::Map(map), Value::Str(name)) => {
113                    map.get(name.as_ref())
114                        .cloned()
115                        .ok_or_else(|| Error::Missing {
116                            name: name.to_string(),
117                        })
118                }
119                _ => Err(type_error("[]", &container)),
120            }
121        }
122        Node::Call(name, args) => {
123            let function = context.get(name).cloned().ok_or_else(|| Error::Unknown {
124                name: name.to_string(),
125            })?;
126            let Value::Function(function) = function else {
127                return Err(type_error("call", &function));
128            };
129
130            function(&arguments(nodes, args, context)?)
131        }
132        Node::Method(receiver, name, args) => {
133            let receiver = eval_node(nodes, *receiver, context)?;
134
135            // The object's own method first — that is what makes an injected
136            // object carry its methods — then a method of that name from the
137            // context, which is the `size(x)` / `x.size()` pairing, and last
138            // the built-in methods a value type carries of its own
139            // (`name.length()`, `items.contains(x)`).
140            let own = receiver
141                .as_map()
142                .and_then(|map| map.get(name.as_ref()))
143                .filter(|value| value.is_function())
144                .cloned();
145
146            let function = match own {
147                Some(function) => function,
148                None => match context.get(name).cloned() {
149                    Some(function) => function,
150                    None => {
151                        let args = arguments(nodes, args, context)?;
152
153                        return crate::methods::call(name, &receiver, &args)?.ok_or_else(|| {
154                            Error::UnknownMethod {
155                                name: name.to_string(),
156                            }
157                        });
158                    }
159                },
160            };
161            let Value::Function(function) = function else {
162                return Err(Error::UnknownMethod {
163                    name: name.to_string(),
164                });
165            };
166
167            let mut call_args = Vec::with_capacity(args.len() + 1);
168            call_args.push(receiver);
169            call_args.extend(arguments(nodes, args, context)?);
170
171            function(&call_args)
172        }
173        Node::Not(operand) => {
174            let value = eval_node(nodes, *operand, context)?;
175            let boolean = boolean_of(&value, "!")?;
176
177            Ok(Value::Bool(!boolean))
178        }
179        Node::Neg(operand) => {
180            let value = eval_node(nodes, *operand, context)?;
181
182            match value {
183                Value::Int(i) => i
184                    .checked_neg()
185                    .map(Value::Int)
186                    .ok_or(Error::Overflow { operation: "-" }),
187                Value::Float(f) => Ok(Value::Float(-f)),
188                other => Err(type_error("-", &other)),
189            }
190        }
191        Node::Binary(op, left, right) => binary(nodes, *op, *left, *right, context),
192    }
193}
194
195/// Evaluate `&&` and `||` with their short circuit, and everything else by
196/// evaluating both sides.
197fn binary(nodes: &[Node], op: BinOp, left: u32, right: u32, context: &Context) -> Result<Value> {
198    match op {
199        BinOp::And => {
200            let left = eval_node(nodes, left, context)?;
201            if !boolean_of(&left, op.symbol())? {
202                return Ok(Value::Bool(false));
203            }
204            let right = eval_node(nodes, right, context)?;
205
206            return Ok(Value::Bool(boolean_of(&right, op.symbol())?));
207        }
208        BinOp::Or => {
209            let left = eval_node(nodes, left, context)?;
210            if boolean_of(&left, op.symbol())? {
211                return Ok(Value::Bool(true));
212            }
213            let right = eval_node(nodes, right, context)?;
214
215            return Ok(Value::Bool(boolean_of(&right, op.symbol())?));
216        }
217        _ => {}
218    }
219
220    let left = eval_node(nodes, left, context)?;
221    let right = eval_node(nodes, right, context)?;
222
223    match op {
224        BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Rem => {
225            arithmetic(op, &left, &right)
226        }
227        BinOp::Eq => Ok(Value::Bool(left.equals(&right))),
228        BinOp::Ne => Ok(Value::Bool(!left.equals(&right))),
229        BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => {
230            let ordering = left
231                .compare(&right)
232                .ok_or_else(|| type_error(op.symbol(), blame(&left, &right)))?;
233
234            Ok(Value::Bool(match op {
235                BinOp::Lt => ordering.is_lt(),
236                BinOp::Le => ordering.is_le(),
237                BinOp::Gt => ordering.is_gt(),
238                _ => ordering.is_ge(),
239            }))
240        }
241        BinOp::And | BinOp::Or => unreachable!("handled above"),
242    }
243}
244
245/// `+`, `-`, `*`, `/` and `%`. Two ints stay an int and keep CEL's semantics
246/// (`/` truncates, `%` is the remainder, a result past `i64` overflows); a
247/// `uint` on either side is computed in 128 bits, exact, and narrowed back to
248/// whichever integer kind holds the result; a float either side makes it a
249/// float. The operator that reads in the source is the one the error names.
250fn arithmetic(op: BinOp, left: &Value, right: &Value) -> Result<Value> {
251    let symbol = op.symbol();
252
253    // Two ints stay an int, and a result past `i64` overflows rather than
254    // quietly becoming a wider type.
255    if let (Value::Int(a), Value::Int(b)) = (left, right) {
256        let (a, b) = (*a, *b);
257
258        let result = match op {
259            BinOp::Add => a.checked_add(b),
260            BinOp::Sub => a.checked_sub(b),
261            BinOp::Mul => a.checked_mul(b),
262            BinOp::Div | BinOp::Rem => {
263                if b == 0 {
264                    return Err(Error::DivideByZero { operation: symbol });
265                }
266                if op == BinOp::Div {
267                    a.checked_div(b)
268                } else {
269                    a.checked_rem(b)
270                }
271            }
272            _ => unreachable!("only arithmetic reaches here"),
273        };
274
275        return result
276            .map(Value::Int)
277            .ok_or(Error::Overflow { operation: symbol });
278    }
279
280    // A `uint` on either side: computed in 128 bits, exact, and narrowed back
281    // to the kind that holds the result.
282    if let (Some(a), Some(b)) = (left.as_i128(), right.as_i128()) {
283        let result = match op {
284            BinOp::Add => a.checked_add(b),
285            BinOp::Sub => a.checked_sub(b),
286            BinOp::Mul => a.checked_mul(b),
287            BinOp::Div | BinOp::Rem => {
288                if b == 0 {
289                    return Err(Error::DivideByZero { operation: symbol });
290                }
291                if op == BinOp::Div {
292                    a.checked_div(b)
293                } else {
294                    a.checked_rem(b)
295                }
296            }
297            _ => unreachable!("only arithmetic reaches here"),
298        };
299
300        let result = result.ok_or(Error::Overflow { operation: symbol })?;
301
302        return match (i64::try_from(result), u64::try_from(result)) {
303            (Ok(value), _) => Ok(Value::Int(value)),
304            (Err(_), Ok(value)) => Ok(Value::UInt(value)),
305            (Err(_), Err(_)) => Err(Error::Overflow { operation: symbol }),
306        };
307    }
308
309    let (Some(a), Some(b)) = (left.as_number(), right.as_number()) else {
310        // `+` is the one arithmetic operator strings define.
311        if op == BinOp::Add
312            && let (Value::Str(a), Value::Str(b)) = (left, right)
313        {
314            let mut text = String::with_capacity(a.len() + b.len());
315            text.push_str(a);
316            text.push_str(b);
317
318            return Ok(Value::Str(Arc::from(text)));
319        }
320
321        return Err(type_error(symbol, blame(left, right)));
322    };
323
324    if matches!(op, BinOp::Div | BinOp::Rem) && b == 0.0 {
325        return Err(Error::DivideByZero { operation: symbol });
326    }
327
328    Ok(Value::Float(match op {
329        BinOp::Add => a + b,
330        BinOp::Sub => a - b,
331        BinOp::Mul => a * b,
332        BinOp::Div => a / b,
333        BinOp::Rem => a % b,
334        _ => unreachable!("only arithmetic reaches here"),
335    }))
336}
337
338fn arguments(nodes: &[Node], args: &[u32], context: &Context) -> Result<Vec<Value>> {
339    args.iter()
340        .map(|arg| eval_node(nodes, *arg, context))
341        .collect()
342}
343
344fn boolean_of(value: &Value, operation: &'static str) -> Result<bool> {
345    value.as_bool().ok_or_else(|| type_error(operation, value))
346}
347
348/// The operand an error message blames: the one whose type has no overload,
349/// which is the right side whenever the left one is a number or a string.
350fn blame<'a>(left: &'a Value, right: &'a Value) -> &'a Value {
351    if left.is_number() || left.as_str().is_some() {
352        right
353    } else {
354        left
355    }
356}
357
358fn type_error(operation: &'static str, value: &Value) -> Error {
359    Error::Type {
360        operation,
361        got: value.type_name(),
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368    use crate::Expr;
369
370    fn eval(source: &str, context: &Context) -> Result<Value> {
371        Expr::compile(source).unwrap().eval(context)
372    }
373
374    fn error(source: &str, context: &Context) -> Error {
375        eval(source, context).expect_err(source)
376    }
377
378    #[test]
379    fn context_get_answers_both_ways() {
380        let mut context = Context::new();
381        assert_eq!(context.get("a"), None);
382        context.set("a", 1);
383        assert_eq!(context.get("a"), Some(&Value::Int(1)));
384    }
385
386    #[test]
387    fn index_errors_name_the_missing_key_and_the_wrong_key_kind() {
388        let mut context = Context::new();
389        context
390            .set("obj", Value::map([("k", 1)]))
391            .set("items", Value::list([1]));
392
393        assert_eq!(
394            error("obj['nope']", &context),
395            Error::Missing {
396                name: "nope".to_string()
397            }
398        );
399        assert_eq!(
400            error("items[true]", &context),
401            Error::Type {
402                operation: "[]",
403                got: "list"
404            }
405        );
406        assert_eq!(
407            error("obj[0]", &context),
408            Error::Type {
409                operation: "[]",
410                got: "map"
411            }
412        );
413        assert_eq!(
414            error("null[0]", &context),
415            Error::Type {
416                operation: "[]",
417                got: "null"
418            }
419        );
420        // The index expression itself is evaluated and can fail.
421        assert_eq!(
422            error("items[missing]", &context),
423            Error::Unknown {
424                name: "missing".to_string()
425            }
426        );
427    }
428
429    #[test]
430    fn member_access_requires_a_map() {
431        let mut context = Context::new();
432        context
433            .set("obj", Value::map([("k", 1)]))
434            .set("items", Value::list([1]));
435
436        assert_eq!(
437            error("items.k", &context),
438            Error::Type {
439                operation: ".",
440                got: "list"
441            }
442        );
443        assert_eq!(
444            error("obj.missing", &context),
445            Error::Missing {
446                name: "missing".to_string()
447            }
448        );
449    }
450
451    #[test]
452    fn calls_check_the_name_is_a_function() {
453        let mut context = Context::new();
454        context
455            .set("add", Value::function(|args| Ok(Value::from(args.len()))))
456            .set("value", 1);
457
458        assert_eq!(eval("add()", &context).unwrap(), Value::from(0));
459        assert_eq!(eval("add(1, 2)", &context).unwrap(), Value::from(2));
460        assert_eq!(
461            error("value()", &context),
462            Error::Type {
463                operation: "call",
464                got: "int"
465            }
466        );
467        assert_eq!(
468            error("missing()", &context),
469            Error::Unknown {
470                name: "missing".to_string()
471            }
472        );
473    }
474
475    #[test]
476    fn a_method_falls_through_to_unknown() {
477        let mut context = Context::new();
478        context.set("obj", Value::map([("k", 1)])).set("value", 2);
479
480        // A member that is not a function does not answer the call.
481        assert_eq!(
482            error("obj.k()", &context),
483            Error::UnknownMethod {
484                name: "k".to_string()
485            }
486        );
487        // A context name that is not a function does not answer it either.
488        assert_eq!(
489            error("obj.value()", &context),
490            Error::UnknownMethod {
491                name: "value".to_string()
492            }
493        );
494    }
495
496    #[test]
497    fn unary_operators_evaluate_both_types() {
498        assert_eq!(eval("-1", &Context::new()).unwrap(), Value::Int(-1));
499        assert_eq!(eval("-1.5", &Context::new()).unwrap(), Value::Float(-1.5));
500        assert_eq!(eval("!false", &Context::new()).unwrap(), Value::Bool(true));
501        assert_eq!(
502            error("-'s'", &Context::new()),
503            Error::Type {
504                operation: "-",
505                got: "string"
506            }
507        );
508        assert_eq!(
509            error("!'s'", &Context::new()),
510            Error::Type {
511                operation: "!",
512                got: "string"
513            }
514        );
515    }
516
517    #[test]
518    fn logic_evaluates_the_right_side_and_checks_both() {
519        let context = Context::new();
520        assert_eq!(eval("true && true", &context).unwrap(), Value::Bool(true));
521        assert_eq!(eval("true && false", &context).unwrap(), Value::Bool(false));
522        assert_eq!(eval("false || true", &context).unwrap(), Value::Bool(true));
523        assert_eq!(
524            eval("false || false", &context).unwrap(),
525            Value::Bool(false)
526        );
527
528        assert_eq!(
529            error("true && 1", &context),
530            Error::Type {
531                operation: "&&",
532                got: "int"
533            }
534        );
535        assert_eq!(
536            error("1 || true", &context),
537            Error::Type {
538                operation: "||",
539                got: "int"
540            }
541        );
542        assert_eq!(
543            error("false || 1", &context),
544            Error::Type {
545                operation: "||",
546                got: "int"
547            }
548        );
549    }
550
551    #[test]
552    fn arithmetic_handles_every_operator_on_uints() {
553        let mut context = Context::new();
554        context.set("big", u64::MAX);
555
556        assert_eq!(
557            eval("big + 1", &context),
558            Err(Error::Overflow { operation: "+" })
559        );
560        assert_eq!(
561            eval("big / 0", &context),
562            Err(Error::DivideByZero { operation: "/" })
563        );
564        assert_eq!(
565            eval("big % 0", &context),
566            Err(Error::DivideByZero { operation: "%" })
567        );
568        assert_eq!(
569            eval("big / 2", &context).unwrap(),
570            Value::UInt(u64::MAX / 2)
571        );
572        assert_eq!(eval("big - big", &context).unwrap(), Value::Int(0));
573        assert_eq!(
574            eval("big - 1.0", &context).unwrap(),
575            Value::Float(u64::MAX as f64 - 1.0)
576        );
577        assert_eq!(
578            error("big + true", &context),
579            Error::Type {
580                operation: "+",
581                got: "bool"
582            }
583        );
584    }
585
586    #[test]
587    fn integer_overflow_and_float_division_are_errors() {
588        let context = Context::new();
589        assert_eq!(
590            eval("9223372036854775807 + 1", &context),
591            Err(Error::Overflow { operation: "+" })
592        );
593        assert_eq!(
594            eval("1.0 / 0.0", &context),
595            Err(Error::DivideByZero { operation: "/" })
596        );
597        assert_eq!(
598            eval("1.0 % 0.0", &context),
599            Err(Error::DivideByZero { operation: "%" })
600        );
601    }
602
603    #[test]
604    fn comparison_blames_the_operand_without_an_overload() {
605        let context = Context::new();
606        assert_eq!(
607            error("true < 1", &context),
608            Error::Type {
609                operation: "<",
610                got: "bool"
611            }
612        );
613        assert_eq!(
614            error("'a' < 1", &context),
615            Error::Type {
616                operation: "<",
617                got: "int"
618            }
619        );
620        assert_eq!(eval("1 < 2.5", &context).unwrap(), Value::Bool(true));
621    }
622}