compose_eval/expression/
binary.rs

1use crate::vm::Machine;
2use crate::{Eval, Evaluated, ValueEvaluatedExtensions};
3use compose_library::diag::{bail, At, SourceResult};
4use compose_library::{ops, Value};
5use compose_syntax::ast;
6use compose_syntax::ast::{AstNode, BinOp};
7
8impl Eval for ast::Binary<'_> {
9    fn eval(self, vm: &mut Machine) -> SourceResult<Evaluated> {
10        let op = match self.op() {
11            BinOp::Add => |l, r, _heap| ops::add(l, r),
12            BinOp::Sub => |l, r, _heap| ops::sub(l, r),
13            BinOp::Mul => |l, r, _heap| ops::mul(l, r),
14            BinOp::Lt => |l, r, _heap| ops::lt(l, r),
15            BinOp::Gt => |l, r, _heap| ops::gt(l, r),
16            BinOp::Gte => |l, r, _heap| ops::gte(l, r),
17            BinOp::Eq => |l, r, heap| ops::eq(l, r, heap),
18            BinOp::Neq => |l, r, heap| ops::neq(l, r, heap),
19            BinOp::Mod => |l, r, _heap| ops::mod_(l, r),
20            BinOp::And => |l, r, _heap| ops::logical_and(l, r),
21            BinOp::Or => |l, r, _heap| ops::logical_or(l, r),
22            other => bail!(
23                self.span(),
24                "unsupported binary operator `{}`",
25                other.descriptive_name()
26            ),
27        };
28
29        let l = self.lhs();
30        let lhs = l.eval(vm)?;
31
32        // make sure we dont evaluate rhs when short-circuiting
33        if self.op().short_circuits(&lhs.value) {
34            return Ok(lhs.value.mutable());
35        }
36
37        let r = self.rhs();
38        let rhs = r.eval(vm)?;
39
40        Ok(op(&lhs.value, &rhs.value, &vm.heap).at(self.span())?.mutable())
41    }
42}
43
44trait ShortCircuits {
45    fn short_circuits(&self, val: &Value) -> bool;
46}
47
48impl ShortCircuits for BinOp {
49    fn short_circuits(&self, val: &Value) -> bool {
50        #[allow(clippy::match_like_matches_macro)]
51        match (self, val) {
52            (BinOp::And, Value::Bool(false)) => true,
53            (BinOp::Or, Value::Bool(true)) => true,
54            _ => false,
55        }
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use crate::test::assert_eval;
62    use compose_library::Value;
63
64    #[test]
65    fn test_addition() {
66        assert_eq!(assert_eval("2 + 4"), Value::Int(6));
67        assert_eq!(assert_eval("2 + 4 + 6"), Value::Int(12));
68        assert_eq!(assert_eval("0 + 9884 + 2171"), Value::Int(12055));
69    }
70
71    #[test]
72    fn test_multiplication() {
73        assert_eq!(assert_eval("2 * 4"), Value::Int(8));
74        assert_eq!(assert_eval("2 * 4 * 6"), Value::Int(48));
75        assert_eq!(assert_eval("0 * 9884 * 2171"), Value::Int(0));
76    }
77
78    #[test]
79    fn test_mixed() {
80        assert_eq!(assert_eval("2 + 4 * 6"), Value::Int(26));
81        assert_eq!(assert_eval("2 * 4 + 6"), Value::Int(14));
82    }
83
84    #[test]
85    fn test_assignment() {
86        assert_eq!(assert_eval("let x; x = 6; x"), Value::Int(6));
87    }
88}