compose_eval/expression/
binary.rs

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