typst_eval/
ops.rs

1use typst_library::diag::{At, HintedStrResult, SourceResult};
2use typst_library::foundations::{IntoValue, Value, ops};
3use typst_syntax::ast::{self, AstNode};
4
5use crate::{Access, Eval, Vm, access_dict};
6
7impl Eval for ast::Unary<'_> {
8    type Output = Value;
9
10    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
11        let value = self.expr().eval(vm)?;
12        let result = match self.op() {
13            ast::UnOp::Pos => ops::pos(value),
14            ast::UnOp::Neg => ops::neg(value),
15            ast::UnOp::Not => ops::not(value),
16        };
17        result.at(self.span())
18    }
19}
20
21impl Eval for ast::Binary<'_> {
22    type Output = Value;
23
24    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
25        match self.op() {
26            ast::BinOp::Add => apply_binary(self, vm, ops::add),
27            ast::BinOp::Sub => apply_binary(self, vm, ops::sub),
28            ast::BinOp::Mul => apply_binary(self, vm, ops::mul),
29            ast::BinOp::Div => apply_binary(self, vm, ops::div),
30            ast::BinOp::And => apply_binary(self, vm, ops::and),
31            ast::BinOp::Or => apply_binary(self, vm, ops::or),
32            ast::BinOp::Eq => apply_binary(self, vm, ops::eq),
33            ast::BinOp::Neq => apply_binary(self, vm, ops::neq),
34            ast::BinOp::Lt => apply_binary(self, vm, ops::lt),
35            ast::BinOp::Leq => apply_binary(self, vm, ops::leq),
36            ast::BinOp::Gt => apply_binary(self, vm, ops::gt),
37            ast::BinOp::Geq => apply_binary(self, vm, ops::geq),
38            ast::BinOp::In => apply_binary(self, vm, ops::in_),
39            ast::BinOp::NotIn => apply_binary(self, vm, ops::not_in),
40            ast::BinOp::Assign => apply_assignment(self, vm, |_, b| Ok(b)),
41            ast::BinOp::AddAssign => apply_assignment(self, vm, ops::add),
42            ast::BinOp::SubAssign => apply_assignment(self, vm, ops::sub),
43            ast::BinOp::MulAssign => apply_assignment(self, vm, ops::mul),
44            ast::BinOp::DivAssign => apply_assignment(self, vm, ops::div),
45        }
46    }
47}
48
49/// Apply a basic binary operation.
50fn apply_binary(
51    binary: ast::Binary,
52    vm: &mut Vm,
53    op: fn(Value, Value) -> HintedStrResult<Value>,
54) -> SourceResult<Value> {
55    let lhs = binary.lhs().eval(vm)?;
56
57    // Short-circuit boolean operations.
58    if (binary.op() == ast::BinOp::And && lhs == false.into_value())
59        || (binary.op() == ast::BinOp::Or && lhs == true.into_value())
60    {
61        return Ok(lhs);
62    }
63
64    let rhs = binary.rhs().eval(vm)?;
65    op(lhs, rhs).at(binary.span())
66}
67
68/// Apply an assignment operation.
69fn apply_assignment(
70    binary: ast::Binary,
71    vm: &mut Vm,
72    op: fn(Value, Value) -> HintedStrResult<Value>,
73) -> SourceResult<Value> {
74    let rhs = binary.rhs().eval(vm)?;
75    let lhs = binary.lhs();
76
77    // An assignment to a dictionary field is different from a normal access
78    // since it can create the field instead of just modifying it.
79    if binary.op() == ast::BinOp::Assign
80        && let ast::Expr::FieldAccess(access) = lhs
81    {
82        let dict = access_dict(vm, access)?;
83        dict.insert(access.field().get().clone().into(), rhs);
84        return Ok(Value::None);
85    }
86
87    let location = binary.lhs().access(vm)?;
88    let lhs = std::mem::take(&mut *location);
89    *location = op(lhs, rhs).at(binary.span())?;
90    Ok(Value::None)
91}