summaryrefslogtreecommitdiff
path: root/crates/typst-eval/src/code.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-eval/src/code.rs')
-rw-r--r--crates/typst-eval/src/code.rs360
1 files changed, 360 insertions, 0 deletions
diff --git a/crates/typst-eval/src/code.rs b/crates/typst-eval/src/code.rs
new file mode 100644
index 00000000..918d9d2a
--- /dev/null
+++ b/crates/typst-eval/src/code.rs
@@ -0,0 +1,360 @@
+use ecow::{eco_vec, EcoVec};
+use typst_library::diag::{bail, error, At, SourceResult};
+use typst_library::foundations::{
+ ops, Array, Capturer, Closure, Content, ContextElem, Dict, Func, NativeElement, Str,
+ Value,
+};
+use typst_syntax::ast::{self, AstNode};
+
+use crate::{CapturesVisitor, Eval, Vm};
+
+impl Eval for ast::Code<'_> {
+ type Output = Value;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ eval_code(vm, &mut self.exprs())
+ }
+}
+
+/// Evaluate a stream of expressions.
+fn eval_code<'a>(
+ vm: &mut Vm,
+ exprs: &mut impl Iterator<Item = ast::Expr<'a>>,
+) -> SourceResult<Value> {
+ let flow = vm.flow.take();
+ let mut output = Value::None;
+
+ while let Some(expr) = exprs.next() {
+ let span = expr.span();
+ let value = match expr {
+ ast::Expr::Set(set) => {
+ let styles = set.eval(vm)?;
+ if vm.flow.is_some() {
+ break;
+ }
+
+ let tail = eval_code(vm, exprs)?.display();
+ Value::Content(tail.styled_with_map(styles))
+ }
+ ast::Expr::Show(show) => {
+ let recipe = show.eval(vm)?;
+ if vm.flow.is_some() {
+ break;
+ }
+
+ let tail = eval_code(vm, exprs)?.display();
+ Value::Content(tail.styled_with_recipe(
+ &mut vm.engine,
+ vm.context,
+ recipe,
+ )?)
+ }
+ _ => expr.eval(vm)?,
+ };
+
+ output = ops::join(output, value).at(span)?;
+
+ if vm.flow.is_some() {
+ break;
+ }
+ }
+
+ if flow.is_some() {
+ vm.flow = flow;
+ }
+
+ Ok(output)
+}
+
+impl Eval for ast::Expr<'_> {
+ type Output = Value;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let span = self.span();
+ let forbidden = |name| {
+ error!(span, "{} is only allowed directly in code and content blocks", name)
+ };
+
+ let v = match self {
+ Self::Text(v) => v.eval(vm).map(Value::Content),
+ Self::Space(v) => v.eval(vm).map(Value::Content),
+ Self::Linebreak(v) => v.eval(vm).map(Value::Content),
+ Self::Parbreak(v) => v.eval(vm).map(Value::Content),
+ Self::Escape(v) => v.eval(vm),
+ Self::Shorthand(v) => v.eval(vm),
+ Self::SmartQuote(v) => v.eval(vm).map(Value::Content),
+ Self::Strong(v) => v.eval(vm).map(Value::Content),
+ Self::Emph(v) => v.eval(vm).map(Value::Content),
+ Self::Raw(v) => v.eval(vm).map(Value::Content),
+ Self::Link(v) => v.eval(vm).map(Value::Content),
+ Self::Label(v) => v.eval(vm),
+ Self::Ref(v) => v.eval(vm).map(Value::Content),
+ Self::Heading(v) => v.eval(vm).map(Value::Content),
+ Self::List(v) => v.eval(vm).map(Value::Content),
+ Self::Enum(v) => v.eval(vm).map(Value::Content),
+ Self::Term(v) => v.eval(vm).map(Value::Content),
+ Self::Equation(v) => v.eval(vm).map(Value::Content),
+ Self::Math(v) => v.eval(vm).map(Value::Content),
+ Self::MathIdent(v) => v.eval(vm),
+ Self::MathShorthand(v) => v.eval(vm),
+ Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content),
+ Self::MathDelimited(v) => v.eval(vm).map(Value::Content),
+ Self::MathAttach(v) => v.eval(vm).map(Value::Content),
+ Self::MathPrimes(v) => v.eval(vm).map(Value::Content),
+ Self::MathFrac(v) => v.eval(vm).map(Value::Content),
+ Self::MathRoot(v) => v.eval(vm).map(Value::Content),
+ Self::Ident(v) => v.eval(vm),
+ Self::None(v) => v.eval(vm),
+ Self::Auto(v) => v.eval(vm),
+ Self::Bool(v) => v.eval(vm),
+ Self::Int(v) => v.eval(vm),
+ Self::Float(v) => v.eval(vm),
+ Self::Numeric(v) => v.eval(vm),
+ Self::Str(v) => v.eval(vm),
+ Self::Code(v) => v.eval(vm),
+ Self::Content(v) => v.eval(vm).map(Value::Content),
+ Self::Array(v) => v.eval(vm).map(Value::Array),
+ Self::Dict(v) => v.eval(vm).map(Value::Dict),
+ Self::Parenthesized(v) => v.eval(vm),
+ Self::FieldAccess(v) => v.eval(vm),
+ Self::FuncCall(v) => v.eval(vm),
+ Self::Closure(v) => v.eval(vm),
+ Self::Unary(v) => v.eval(vm),
+ Self::Binary(v) => v.eval(vm),
+ Self::Let(v) => v.eval(vm),
+ Self::DestructAssign(v) => v.eval(vm),
+ Self::Set(_) => bail!(forbidden("set")),
+ Self::Show(_) => bail!(forbidden("show")),
+ Self::Contextual(v) => v.eval(vm).map(Value::Content),
+ Self::Conditional(v) => v.eval(vm),
+ Self::While(v) => v.eval(vm),
+ Self::For(v) => v.eval(vm),
+ Self::Import(v) => v.eval(vm),
+ Self::Include(v) => v.eval(vm).map(Value::Content),
+ Self::Break(v) => v.eval(vm),
+ Self::Continue(v) => v.eval(vm),
+ Self::Return(v) => v.eval(vm),
+ }?
+ .spanned(span);
+
+ if vm.inspected == Some(span) {
+ vm.trace(v.clone());
+ }
+
+ Ok(v)
+ }
+}
+
+impl Eval for ast::Ident<'_> {
+ type Output = Value;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ vm.scopes.get(&self).cloned().at(self.span())
+ }
+}
+
+impl Eval for ast::None<'_> {
+ type Output = Value;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Value::None)
+ }
+}
+
+impl Eval for ast::Auto<'_> {
+ type Output = Value;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Value::Auto)
+ }
+}
+
+impl Eval for ast::Bool<'_> {
+ type Output = Value;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Value::Bool(self.get()))
+ }
+}
+
+impl Eval for ast::Int<'_> {
+ type Output = Value;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Value::Int(self.get()))
+ }
+}
+
+impl Eval for ast::Float<'_> {
+ type Output = Value;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Value::Float(self.get()))
+ }
+}
+
+impl Eval for ast::Numeric<'_> {
+ type Output = Value;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Value::numeric(self.get()))
+ }
+}
+
+impl Eval for ast::Str<'_> {
+ type Output = Value;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Value::Str(self.get().into()))
+ }
+}
+
+impl Eval for ast::Array<'_> {
+ type Output = Array;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let items = self.items();
+
+ let mut vec = EcoVec::with_capacity(items.size_hint().0);
+ for item in items {
+ match item {
+ ast::ArrayItem::Pos(expr) => vec.push(expr.eval(vm)?),
+ ast::ArrayItem::Spread(spread) => match spread.expr().eval(vm)? {
+ Value::None => {}
+ Value::Array(array) => vec.extend(array.into_iter()),
+ v => bail!(spread.span(), "cannot spread {} into array", v.ty()),
+ },
+ }
+ }
+
+ Ok(vec.into())
+ }
+}
+
+impl Eval for ast::Dict<'_> {
+ type Output = Dict;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let mut map = indexmap::IndexMap::new();
+ let mut invalid_keys = eco_vec![];
+
+ for item in self.items() {
+ match item {
+ ast::DictItem::Named(named) => {
+ map.insert(named.name().get().clone().into(), named.expr().eval(vm)?);
+ }
+ ast::DictItem::Keyed(keyed) => {
+ let raw_key = keyed.key();
+ let key = raw_key.eval(vm)?;
+ let key =
+ key.cast::<Str>().at(raw_key.span()).unwrap_or_else(|errors| {
+ invalid_keys.extend(errors);
+ Str::default()
+ });
+ map.insert(key, keyed.expr().eval(vm)?);
+ }
+ ast::DictItem::Spread(spread) => match spread.expr().eval(vm)? {
+ Value::None => {}
+ Value::Dict(dict) => map.extend(dict.into_iter()),
+ v => bail!(spread.span(), "cannot spread {} into dictionary", v.ty()),
+ },
+ }
+ }
+
+ if !invalid_keys.is_empty() {
+ return Err(invalid_keys);
+ }
+
+ Ok(map.into())
+ }
+}
+
+impl Eval for ast::CodeBlock<'_> {
+ type Output = Value;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ vm.scopes.enter();
+ let output = self.body().eval(vm)?;
+ vm.scopes.exit();
+ Ok(output)
+ }
+}
+
+impl Eval for ast::ContentBlock<'_> {
+ type Output = Content;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ vm.scopes.enter();
+ let content = self.body().eval(vm)?;
+ vm.scopes.exit();
+ Ok(content)
+ }
+}
+
+impl Eval for ast::Parenthesized<'_> {
+ type Output = Value;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ self.expr().eval(vm)
+ }
+}
+
+impl Eval for ast::FieldAccess<'_> {
+ type Output = Value;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let value = self.target().eval(vm)?;
+ let field = self.field();
+
+ let err = match value.field(&field).at(field.span()) {
+ Ok(value) => return Ok(value),
+ Err(err) => err,
+ };
+
+ // Check whether this is a get rule field access.
+ if_chain::if_chain! {
+ if let Value::Func(func) = &value;
+ if let Some(element) = func.element();
+ if let Some(id) = element.field_id(&field);
+ let styles = vm.context.styles().at(field.span());
+ if let Ok(value) = element.field_from_styles(
+ id,
+ styles.as_ref().map(|&s| s).unwrap_or_default(),
+ );
+ then {
+ // Only validate the context once we know that this is indeed
+ // a field from the style chain.
+ let _ = styles?;
+ return Ok(value);
+ }
+ }
+
+ Err(err)
+ }
+}
+
+impl Eval for ast::Contextual<'_> {
+ type Output = Content;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let body = self.body();
+
+ // Collect captured variables.
+ let captured = {
+ let mut visitor = CapturesVisitor::new(Some(&vm.scopes), Capturer::Context);
+ visitor.visit(body.to_untyped());
+ visitor.finish()
+ };
+
+ // Define the closure.
+ let closure = Closure {
+ node: self.body().to_untyped().clone(),
+ defaults: vec![],
+ captured,
+ num_pos_params: 0,
+ };
+
+ let func = Func::from(closure).spanned(body.span());
+ Ok(ContextElem::new(func).pack())
+ }
+}