summaryrefslogtreecommitdiff
path: root/crates/typst-eval/src/ops.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-eval/src/ops.rs')
-rw-r--r--crates/typst-eval/src/ops.rs91
1 files changed, 91 insertions, 0 deletions
diff --git a/crates/typst-eval/src/ops.rs b/crates/typst-eval/src/ops.rs
new file mode 100644
index 00000000..ebbd6743
--- /dev/null
+++ b/crates/typst-eval/src/ops.rs
@@ -0,0 +1,91 @@
+use typst_library::diag::{At, HintedStrResult, SourceResult};
+use typst_library::foundations::{ops, IntoValue, Value};
+use typst_syntax::ast::{self, AstNode};
+
+use crate::{access_dict, Access, Eval, Vm};
+
+impl Eval for ast::Unary<'_> {
+ type Output = Value;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let value = self.expr().eval(vm)?;
+ let result = match self.op() {
+ ast::UnOp::Pos => ops::pos(value),
+ ast::UnOp::Neg => ops::neg(value),
+ ast::UnOp::Not => ops::not(value),
+ };
+ result.at(self.span())
+ }
+}
+
+impl Eval for ast::Binary<'_> {
+ type Output = Value;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ match self.op() {
+ ast::BinOp::Add => apply_binary(self, vm, ops::add),
+ ast::BinOp::Sub => apply_binary(self, vm, ops::sub),
+ ast::BinOp::Mul => apply_binary(self, vm, ops::mul),
+ ast::BinOp::Div => apply_binary(self, vm, ops::div),
+ ast::BinOp::And => apply_binary(self, vm, ops::and),
+ ast::BinOp::Or => apply_binary(self, vm, ops::or),
+ ast::BinOp::Eq => apply_binary(self, vm, ops::eq),
+ ast::BinOp::Neq => apply_binary(self, vm, ops::neq),
+ ast::BinOp::Lt => apply_binary(self, vm, ops::lt),
+ ast::BinOp::Leq => apply_binary(self, vm, ops::leq),
+ ast::BinOp::Gt => apply_binary(self, vm, ops::gt),
+ ast::BinOp::Geq => apply_binary(self, vm, ops::geq),
+ ast::BinOp::In => apply_binary(self, vm, ops::in_),
+ ast::BinOp::NotIn => apply_binary(self, vm, ops::not_in),
+ ast::BinOp::Assign => apply_assignment(self, vm, |_, b| Ok(b)),
+ ast::BinOp::AddAssign => apply_assignment(self, vm, ops::add),
+ ast::BinOp::SubAssign => apply_assignment(self, vm, ops::sub),
+ ast::BinOp::MulAssign => apply_assignment(self, vm, ops::mul),
+ ast::BinOp::DivAssign => apply_assignment(self, vm, ops::div),
+ }
+ }
+}
+
+/// Apply a basic binary operation.
+fn apply_binary(
+ binary: ast::Binary,
+ vm: &mut Vm,
+ op: fn(Value, Value) -> HintedStrResult<Value>,
+) -> SourceResult<Value> {
+ let lhs = binary.lhs().eval(vm)?;
+
+ // Short-circuit boolean operations.
+ if (binary.op() == ast::BinOp::And && lhs == false.into_value())
+ || (binary.op() == ast::BinOp::Or && lhs == true.into_value())
+ {
+ return Ok(lhs);
+ }
+
+ let rhs = binary.rhs().eval(vm)?;
+ op(lhs, rhs).at(binary.span())
+}
+
+/// Apply an assignment operation.
+fn apply_assignment(
+ binary: ast::Binary,
+ vm: &mut Vm,
+ op: fn(Value, Value) -> HintedStrResult<Value>,
+) -> SourceResult<Value> {
+ let rhs = binary.rhs().eval(vm)?;
+ let lhs = binary.lhs();
+
+ // An assignment to a dictionary field is different from a normal access
+ // since it can create the field instead of just modifying it.
+ if binary.op() == ast::BinOp::Assign {
+ if let ast::Expr::FieldAccess(access) = lhs {
+ let dict = access_dict(vm, access)?;
+ dict.insert(access.field().get().clone().into(), rhs);
+ return Ok(Value::None);
+ }
+ }
+
+ let location = binary.lhs().access(vm)?;
+ let lhs = std::mem::take(&mut *location);
+ *location = op(lhs, rhs).at(binary.span())?;
+ Ok(Value::None)
+}