summaryrefslogtreecommitdiff
path: root/src/model/eval.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-10-17 19:26:24 +0200
committerLaurenz <laurmaedje@gmail.com>2022-10-17 20:04:22 +0200
commite21822665591dc19766275da1e185215a6b945ef (patch)
tree7788e211c3c33c8b5a8ad7d5eb7574e33631eb16 /src/model/eval.rs
parent4fd031a256b2ecfe524859d5599fafb386395572 (diff)
Merge some modules
Diffstat (limited to 'src/model/eval.rs')
-rw-r--r--src/model/eval.rs1212
1 files changed, 1212 insertions, 0 deletions
diff --git a/src/model/eval.rs b/src/model/eval.rs
new file mode 100644
index 00000000..aa5f0378
--- /dev/null
+++ b/src/model/eval.rs
@@ -0,0 +1,1212 @@
+//! Evaluation of markup into modules.
+
+use std::collections::BTreeMap;
+use std::sync::Arc;
+
+use comemo::{Track, Tracked};
+use unicode_segmentation::UnicodeSegmentation;
+
+use super::{
+ methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func,
+ Pattern, Recipe, Scope, Scopes, StyleEntry, StyleMap, Value, Vm,
+};
+use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint};
+use crate::geom::{Angle, Em, Fraction, Length, Ratio};
+use crate::library;
+use crate::syntax::ast::TypedNode;
+use crate::syntax::{ast, SourceId, Span, Spanned, Unit};
+use crate::util::EcoString;
+use crate::World;
+
+/// Evaluate a source file and return the resulting module.
+///
+/// Returns either a module containing a scope with top-level bindings and
+/// layoutable contents or diagnostics in the form of a vector of error
+/// messages with file and span information.
+#[comemo::memoize]
+pub fn eval(
+ world: Tracked<dyn World>,
+ route: Tracked<Route>,
+ id: SourceId,
+) -> SourceResult<Module> {
+ // Prevent cyclic evaluation.
+ if route.contains(id) {
+ let path = world.source(id).path().display();
+ panic!("Tried to cyclicly evaluate {}", path);
+ }
+
+ // Evaluate the module.
+ let route = unsafe { Route::insert(route, id) };
+ let ast = world.source(id).ast()?;
+ let std = &world.config().std;
+ let scopes = Scopes::new(Some(std));
+ let mut vm = Vm::new(world, route.track(), Some(id), scopes);
+ let result = ast.eval(&mut vm);
+
+ // Handle control flow.
+ if let Some(flow) = vm.flow {
+ bail!(flow.forbidden());
+ }
+
+ // Assemble the module.
+ Ok(Module { scope: vm.scopes.top, content: result? })
+}
+
+/// A route of source ids.
+#[derive(Default)]
+pub struct Route {
+ parent: Option<Tracked<'static, Self>>,
+ id: Option<SourceId>,
+}
+
+impl Route {
+ /// Create a new, empty route.
+ pub fn new(id: Option<SourceId>) -> Self {
+ Self { id, parent: None }
+ }
+
+ /// Insert a new id into the route.
+ ///
+ /// You must guarantee that `outer` lives longer than the resulting
+ /// route is ever used.
+ unsafe fn insert(outer: Tracked<Route>, id: SourceId) -> Route {
+ Route {
+ parent: Some(std::mem::transmute(outer)),
+ id: Some(id),
+ }
+ }
+}
+
+#[comemo::track]
+impl Route {
+ /// Whether the given id is part of the route.
+ fn contains(&self, id: SourceId) -> bool {
+ self.id == Some(id) || self.parent.map_or(false, |parent| parent.contains(id))
+ }
+}
+
+/// An evaluated module, ready for importing or layouting.
+#[derive(Debug, Clone)]
+pub struct Module {
+ /// The top-level definitions that were bound in this module.
+ pub scope: Scope,
+ /// The module's layoutable contents.
+ pub content: Content,
+}
+
+/// Evaluate an expression.
+pub trait Eval {
+ /// The output of evaluating the expression.
+ type Output;
+
+ /// Evaluate the expression to the output value.
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output>;
+}
+
+impl Eval for ast::Markup {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ eval_markup(vm, &mut self.children())
+ }
+}
+
+/// Evaluate a stream of markup nodes.
+fn eval_markup(
+ vm: &mut Vm,
+ nodes: &mut impl Iterator<Item = ast::MarkupNode>,
+) -> SourceResult<Content> {
+ let flow = vm.flow.take();
+ let mut seq = Vec::with_capacity(nodes.size_hint().1.unwrap_or_default());
+
+ while let Some(node) = nodes.next() {
+ seq.push(match node {
+ ast::MarkupNode::Expr(ast::Expr::Set(set)) => {
+ let styles = set.eval(vm)?;
+ if vm.flow.is_some() {
+ break;
+ }
+
+ eval_markup(vm, nodes)?.styled_with_map(styles)
+ }
+ ast::MarkupNode::Expr(ast::Expr::Show(show)) => {
+ let recipe = show.eval(vm)?;
+ if vm.flow.is_some() {
+ break;
+ }
+
+ eval_markup(vm, nodes)?
+ .styled_with_entry(StyleEntry::Recipe(recipe).into())
+ }
+ ast::MarkupNode::Expr(ast::Expr::Wrap(wrap)) => {
+ let tail = eval_markup(vm, nodes)?;
+ vm.scopes.top.define(wrap.binding().take(), tail);
+ wrap.body().eval(vm)?.display()
+ }
+
+ _ => node.eval(vm)?,
+ });
+
+ if vm.flow.is_some() {
+ break;
+ }
+ }
+
+ if flow.is_some() {
+ vm.flow = flow;
+ }
+
+ Ok(Content::sequence(seq))
+}
+
+impl Eval for ast::MarkupNode {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ match self {
+ Self::Space(v) => v.eval(vm),
+ Self::Linebreak(v) => v.eval(vm),
+ Self::Text(v) => v.eval(vm),
+ Self::Escape(v) => v.eval(vm),
+ Self::Shorthand(v) => v.eval(vm),
+ Self::SmartQuote(v) => v.eval(vm),
+ Self::Strong(v) => v.eval(vm),
+ Self::Emph(v) => v.eval(vm),
+ Self::Link(v) => v.eval(vm),
+ Self::Raw(v) => v.eval(vm),
+ Self::Math(v) => v.eval(vm),
+ Self::Heading(v) => v.eval(vm),
+ Self::List(v) => v.eval(vm),
+ Self::Enum(v) => v.eval(vm),
+ Self::Desc(v) => v.eval(vm),
+ Self::Label(v) => v.eval(vm),
+ Self::Ref(v) => v.eval(vm),
+ Self::Expr(v) => v.eval(vm).map(Value::display),
+ }
+ }
+}
+
+impl Eval for ast::Space {
+ type Output = Content;
+
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(if self.newlines() < 2 {
+ Content::Space
+ } else {
+ Content::Parbreak
+ })
+ }
+}
+
+impl Eval for ast::Linebreak {
+ type Output = Content;
+
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Content::Linebreak { justify: false })
+ }
+}
+
+impl Eval for ast::Text {
+ type Output = Content;
+
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Content::Text(self.get().clone()))
+ }
+}
+
+impl Eval for ast::Escape {
+ type Output = Content;
+
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Content::Text(self.get().into()))
+ }
+}
+
+impl Eval for ast::Shorthand {
+ type Output = Content;
+
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Content::Text(self.get().into()))
+ }
+}
+
+impl Eval for ast::SmartQuote {
+ type Output = Content;
+
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Content::Quote { double: self.double() })
+ }
+}
+
+impl Eval for ast::Strong {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Content::show(library::text::StrongNode(
+ self.body().eval(vm)?,
+ )))
+ }
+}
+
+impl Eval for ast::Emph {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Content::show(library::text::EmphNode(
+ self.body().eval(vm)?,
+ )))
+ }
+}
+
+impl Eval for ast::Link {
+ type Output = Content;
+
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Content::show(library::text::LinkNode::from_url(
+ self.url().clone(),
+ )))
+ }
+}
+
+impl Eval for ast::Raw {
+ type Output = Content;
+
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ let content = Content::show(library::text::RawNode {
+ text: self.text().clone(),
+ block: self.block(),
+ });
+ Ok(match self.lang() {
+ Some(_) => content.styled(library::text::RawNode::LANG, self.lang().cloned()),
+ None => content,
+ })
+ }
+}
+
+impl Eval for ast::Math {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let nodes = self
+ .children()
+ .map(|node| node.eval(vm))
+ .collect::<SourceResult<_>>()?;
+ Ok(Content::show(library::math::MathNode::Row(
+ Arc::new(nodes),
+ self.span(),
+ )))
+ }
+}
+
+impl Eval for ast::MathNode {
+ type Output = library::math::MathNode;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(match self {
+ Self::Space(_) => library::math::MathNode::Space,
+ Self::Linebreak(_) => library::math::MathNode::Linebreak,
+ Self::Escape(c) => library::math::MathNode::Atom(c.get().into()),
+ Self::Atom(atom) => library::math::MathNode::Atom(atom.get().clone()),
+ Self::Script(node) => node.eval(vm)?,
+ Self::Frac(node) => node.eval(vm)?,
+ Self::Align(node) => node.eval(vm)?,
+ Self::Group(node) => library::math::MathNode::Row(
+ Arc::new(
+ node.children()
+ .map(|node| node.eval(vm))
+ .collect::<SourceResult<_>>()?,
+ ),
+ node.span(),
+ ),
+ Self::Expr(expr) => match expr.eval(vm)?.display() {
+ Content::Text(text) => library::math::MathNode::Atom(text),
+ _ => bail!(expr.span(), "expected text"),
+ },
+ })
+ }
+}
+
+impl Eval for ast::Script {
+ type Output = library::math::MathNode;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(library::math::MathNode::Script(Arc::new(
+ library::math::ScriptNode {
+ base: self.base().eval(vm)?,
+ sub: self
+ .sub()
+ .map(|node| node.eval(vm))
+ .transpose()?
+ .map(|node| node.unparen()),
+ sup: self
+ .sup()
+ .map(|node| node.eval(vm))
+ .transpose()?
+ .map(|node| node.unparen()),
+ },
+ )))
+ }
+}
+
+impl Eval for ast::Frac {
+ type Output = library::math::MathNode;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(library::math::MathNode::Frac(Arc::new(
+ library::math::FracNode {
+ num: self.num().eval(vm)?.unparen(),
+ denom: self.denom().eval(vm)?.unparen(),
+ },
+ )))
+ }
+}
+
+impl Eval for ast::Align {
+ type Output = library::math::MathNode;
+
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(library::math::MathNode::Align(self.count()))
+ }
+}
+
+impl Eval for ast::Heading {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Content::show(library::structure::HeadingNode {
+ body: self.body().eval(vm)?,
+ level: self.level(),
+ }))
+ }
+}
+
+impl Eval for ast::ListItem {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let body = Box::new(self.body().eval(vm)?);
+ Ok(Content::Item(library::structure::ListItem::List(body)))
+ }
+}
+
+impl Eval for ast::EnumItem {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let number = self.number();
+ let body = Box::new(self.body().eval(vm)?);
+ Ok(Content::Item(library::structure::ListItem::Enum(
+ number, body,
+ )))
+ }
+}
+
+impl Eval for ast::DescItem {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let term = self.term().eval(vm)?;
+ let body = self.body().eval(vm)?;
+ Ok(Content::Item(library::structure::ListItem::Desc(Box::new(
+ library::structure::DescItem { term, body },
+ ))))
+ }
+}
+
+impl Eval for ast::Label {
+ type Output = Content;
+
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Content::Empty)
+ }
+}
+
+impl Eval for ast::Ref {
+ type Output = Content;
+
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Content::show(library::structure::RefNode(
+ self.get().clone(),
+ )))
+ }
+}
+
+impl Eval for ast::Expr {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let forbidden = |name| {
+ error!(
+ self.span(),
+ "{} is only allowed directly in code and content blocks", name
+ )
+ };
+
+ match self {
+ Self::Lit(v) => v.eval(vm),
+ Self::Ident(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::MethodCall(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::Set(_) => bail!(forbidden("set")),
+ Self::Show(_) => bail!(forbidden("show")),
+ Self::Wrap(_) => bail!(forbidden("wrap")),
+ 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),
+ }
+ }
+}
+
+impl Eval for ast::Lit {
+ type Output = Value;
+
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(match self.kind() {
+ ast::LitKind::None => Value::None,
+ ast::LitKind::Auto => Value::Auto,
+ ast::LitKind::Bool(v) => Value::Bool(v),
+ ast::LitKind::Int(v) => Value::Int(v),
+ ast::LitKind::Float(v) => Value::Float(v),
+ ast::LitKind::Numeric(v, unit) => match unit {
+ Unit::Length(unit) => Length::with_unit(v, unit).into(),
+ Unit::Angle(unit) => Angle::with_unit(v, unit).into(),
+ Unit::Em => Em::new(v).into(),
+ Unit::Fr => Fraction::new(v).into(),
+ Unit::Percent => Ratio::new(v / 100.0).into(),
+ },
+ ast::LitKind::Str(v) => Value::Str(v.into()),
+ })
+ }
+}
+
+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::CodeBlock {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ vm.scopes.enter();
+ let output = eval_code(vm, &mut self.exprs())?;
+ vm.scopes.exit();
+ Ok(output)
+ }
+}
+
+/// Evaluate a stream of expressions.
+fn eval_code(
+ vm: &mut Vm,
+ exprs: &mut impl Iterator<Item = ast::Expr>,
+) -> 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)?;
+ let entry = StyleEntry::Recipe(recipe).into();
+ if vm.flow.is_some() {
+ break;
+ }
+
+ let tail = eval_code(vm, exprs)?.display();
+ Value::Content(tail.styled_with_entry(entry))
+ }
+ ast::Expr::Wrap(wrap) => {
+ let tail = eval_code(vm, exprs)?;
+ vm.scopes.top.define(wrap.binding().take(), tail);
+ wrap.body().eval(vm)?
+ }
+
+ _ => 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::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::Array {
+ type Output = Array;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let items = self.items();
+
+ let mut vec = Vec::with_capacity(items.size_hint().0);
+ for item in items {
+ match item {
+ ast::ArrayItem::Pos(expr) => vec.push(expr.eval(vm)?),
+ ast::ArrayItem::Spread(expr) => match expr.eval(vm)? {
+ Value::None => {}
+ Value::Array(array) => vec.extend(array.into_iter()),
+ v => bail!(expr.span(), "cannot spread {} into array", v.type_name()),
+ },
+ }
+ }
+
+ Ok(Array::from_vec(vec))
+ }
+}
+
+impl Eval for ast::Dict {
+ type Output = Dict;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let mut map = BTreeMap::new();
+
+ for item in self.items() {
+ match item {
+ ast::DictItem::Named(named) => {
+ map.insert(named.name().take().into(), named.expr().eval(vm)?);
+ }
+ ast::DictItem::Keyed(keyed) => {
+ map.insert(keyed.key().into(), keyed.expr().eval(vm)?);
+ }
+ ast::DictItem::Spread(expr) => match expr.eval(vm)? {
+ Value::None => {}
+ Value::Dict(dict) => map.extend(dict.into_iter()),
+ v => bail!(
+ expr.span(),
+ "cannot spread {} into dictionary",
+ v.type_name()
+ ),
+ },
+ }
+ }
+
+ Ok(Dict::from_map(map))
+ }
+}
+
+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),
+ };
+ Ok(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 => self.apply(vm, ops::add),
+ ast::BinOp::Sub => self.apply(vm, ops::sub),
+ ast::BinOp::Mul => self.apply(vm, ops::mul),
+ ast::BinOp::Div => self.apply(vm, ops::div),
+ ast::BinOp::And => self.apply(vm, ops::and),
+ ast::BinOp::Or => self.apply(vm, ops::or),
+ ast::BinOp::Eq => self.apply(vm, ops::eq),
+ ast::BinOp::Neq => self.apply(vm, ops::neq),
+ ast::BinOp::Lt => self.apply(vm, ops::lt),
+ ast::BinOp::Leq => self.apply(vm, ops::leq),
+ ast::BinOp::Gt => self.apply(vm, ops::gt),
+ ast::BinOp::Geq => self.apply(vm, ops::geq),
+ ast::BinOp::In => self.apply(vm, ops::in_),
+ ast::BinOp::NotIn => self.apply(vm, ops::not_in),
+ ast::BinOp::Assign => self.assign(vm, |_, b| Ok(b)),
+ ast::BinOp::AddAssign => self.assign(vm, ops::add),
+ ast::BinOp::SubAssign => self.assign(vm, ops::sub),
+ ast::BinOp::MulAssign => self.assign(vm, ops::mul),
+ ast::BinOp::DivAssign => self.assign(vm, ops::div),
+ }
+ }
+}
+
+impl ast::Binary {
+ /// Apply a basic binary operation.
+ fn apply(
+ &self,
+ vm: &mut Vm,
+ op: fn(Value, Value) -> StrResult<Value>,
+ ) -> SourceResult<Value> {
+ let lhs = self.lhs().eval(vm)?;
+
+ // Short-circuit boolean operations.
+ if (self.op() == ast::BinOp::And && lhs == Value::Bool(false))
+ || (self.op() == ast::BinOp::Or && lhs == Value::Bool(true))
+ {
+ return Ok(lhs);
+ }
+
+ let rhs = self.rhs().eval(vm)?;
+ Ok(op(lhs, rhs).at(self.span())?)
+ }
+
+ /// Apply an assignment operation.
+ fn assign(
+ &self,
+ vm: &mut Vm,
+ op: fn(Value, Value) -> StrResult<Value>,
+ ) -> SourceResult<Value> {
+ let rhs = self.rhs().eval(vm)?;
+ let location = self.lhs().access(vm)?;
+ let lhs = std::mem::take(&mut *location);
+ *location = op(lhs, rhs).at(self.span())?;
+ Ok(Value::None)
+ }
+}
+
+impl Eval for ast::FieldAccess {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let object = self.target().eval(vm)?;
+ let span = self.field().span();
+ let field = self.field().take();
+
+ Ok(match object {
+ Value::Dict(dict) => dict.get(&field).at(span)?.clone(),
+
+ Value::Content(Content::Show(_, Some(dict))) => dict
+ .get(&field)
+ .map_err(|_| format!("unknown field {field:?}"))
+ .at(span)?
+ .clone(),
+
+ v => bail!(
+ self.target().span(),
+ "cannot access field on {}",
+ v.type_name()
+ ),
+ })
+ }
+}
+
+impl Eval for ast::FuncCall {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let callee = self.callee().eval(vm)?;
+ let args = self.args().eval(vm)?;
+
+ Ok(match callee {
+ Value::Array(array) => array.get(args.into_index()?).at(self.span())?.clone(),
+ Value::Dict(dict) => dict.get(&args.into_key()?).at(self.span())?.clone(),
+ Value::Func(func) => {
+ let point = || Tracepoint::Call(func.name().map(Into::into));
+ func.call(vm, args).trace(vm.world, point, self.span())?
+ }
+
+ v => bail!(
+ self.callee().span(),
+ "expected callable or collection, found {}",
+ v.type_name(),
+ ),
+ })
+ }
+}
+
+impl Eval for ast::MethodCall {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let span = self.span();
+ let method = self.method().take();
+ let point = || Tracepoint::Call(Some(method.clone()));
+
+ Ok(if methods::is_mutating(&method) {
+ let args = self.args().eval(vm)?;
+ let mut value = self.target().access(vm)?;
+ methods::call_mut(&mut value, &method, args, span)
+ .trace(vm.world, point, span)?;
+ Value::None
+ } else {
+ let value = self.target().eval(vm)?;
+ let args = self.args().eval(vm)?;
+ methods::call(vm, value, &method, args, span).trace(vm.world, point, span)?
+ })
+ }
+}
+
+impl Eval for ast::Args {
+ type Output = Args;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let mut items = Vec::new();
+
+ for arg in self.items() {
+ let span = arg.span();
+ match arg {
+ ast::Arg::Pos(expr) => {
+ items.push(Arg {
+ span,
+ name: None,
+ value: Spanned::new(expr.eval(vm)?, expr.span()),
+ });
+ }
+ ast::Arg::Named(named) => {
+ items.push(Arg {
+ span,
+ name: Some(named.name().take().into()),
+ value: Spanned::new(named.expr().eval(vm)?, named.expr().span()),
+ });
+ }
+ ast::Arg::Spread(expr) => match expr.eval(vm)? {
+ Value::None => {}
+ Value::Array(array) => {
+ items.extend(array.into_iter().map(|value| Arg {
+ span,
+ name: None,
+ value: Spanned::new(value, span),
+ }));
+ }
+ Value::Dict(dict) => {
+ items.extend(dict.into_iter().map(|(key, value)| Arg {
+ span,
+ name: Some(key),
+ value: Spanned::new(value, span),
+ }));
+ }
+ Value::Args(args) => items.extend(args.items),
+ v => bail!(expr.span(), "cannot spread {}", v.type_name()),
+ },
+ }
+ }
+
+ Ok(Args { span: self.span(), items })
+ }
+}
+
+impl Eval for ast::Closure {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ // The closure's name is defined by its let binding if there's one.
+ let name = self.name().map(ast::Ident::take);
+
+ // Collect captured variables.
+ let captured = {
+ let mut visitor = CapturesVisitor::new(&vm.scopes);
+ visitor.visit(self.as_untyped());
+ visitor.finish()
+ };
+
+ let mut params = Vec::new();
+ let mut sink = None;
+
+ // Collect parameters and an optional sink parameter.
+ for param in self.params() {
+ match param {
+ ast::Param::Pos(name) => {
+ params.push((name.take(), None));
+ }
+ ast::Param::Named(named) => {
+ params.push((named.name().take(), Some(named.expr().eval(vm)?)));
+ }
+ ast::Param::Sink(name) => {
+ if sink.is_some() {
+ bail!(name.span(), "only one argument sink is allowed");
+ }
+ sink = Some(name.take());
+ }
+ }
+ }
+
+ // Define the actual function.
+ Ok(Value::Func(Func::from_closure(Closure {
+ location: vm.location,
+ name,
+ captured,
+ params,
+ sink,
+ body: self.body(),
+ })))
+ }
+}
+
+impl Eval for ast::LetBinding {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let value = match self.init() {
+ Some(expr) => expr.eval(vm)?,
+ None => Value::None,
+ };
+ vm.scopes.top.define(self.binding().take(), value);
+ Ok(Value::None)
+ }
+}
+
+impl Eval for ast::SetRule {
+ type Output = StyleMap;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let target = self.target();
+ let target = target.eval(vm)?.cast::<Func>().at(target.span())?;
+ let args = self.args().eval(vm)?;
+ Ok(target.set(args)?)
+ }
+}
+
+impl Eval for ast::ShowRule {
+ type Output = Recipe;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ // Evaluate the target function.
+ let pattern = self.pattern();
+ let pattern = pattern.eval(vm)?.cast::<Pattern>().at(pattern.span())?;
+
+ // Collect captured variables.
+ let captured = {
+ let mut visitor = CapturesVisitor::new(&vm.scopes);
+ visitor.visit(self.as_untyped());
+ visitor.finish()
+ };
+
+ // Define parameters.
+ let mut params = vec![];
+ if let Some(binding) = self.binding() {
+ params.push((binding.take(), None));
+ }
+
+ // Define the recipe function.
+ let body = self.body();
+ let span = body.span();
+ let func = Func::from_closure(Closure {
+ location: vm.location,
+ name: None,
+ captured,
+ params,
+ sink: None,
+ body,
+ });
+
+ Ok(Recipe { pattern, func: Spanned::new(func, span) })
+ }
+}
+
+impl Eval for ast::Conditional {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let condition = self.condition();
+ if condition.eval(vm)?.cast::<bool>().at(condition.span())? {
+ self.if_body().eval(vm)
+ } else if let Some(else_body) = self.else_body() {
+ else_body.eval(vm)
+ } else {
+ Ok(Value::None)
+ }
+ }
+}
+
+impl Eval for ast::WhileLoop {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let flow = vm.flow.take();
+ let mut output = Value::None;
+
+ let condition = self.condition();
+ while condition.eval(vm)?.cast::<bool>().at(condition.span())? {
+ let body = self.body();
+ let value = body.eval(vm)?;
+ output = ops::join(output, value).at(body.span())?;
+
+ match vm.flow {
+ Some(Flow::Break(_)) => {
+ vm.flow = None;
+ break;
+ }
+ Some(Flow::Continue(_)) => vm.flow = None,
+ Some(Flow::Return(..)) => break,
+ None => {}
+ }
+ }
+
+ if flow.is_some() {
+ vm.flow = flow;
+ }
+
+ Ok(output)
+ }
+}
+
+impl Eval for ast::ForLoop {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let flow = vm.flow.take();
+ let mut output = Value::None;
+ vm.scopes.enter();
+
+ macro_rules! iter {
+ (for ($($binding:ident => $value:ident),*) in $iter:expr) => {{
+ #[allow(unused_parens)]
+ for ($($value),*) in $iter {
+ $(vm.scopes.top.define($binding.clone(), $value);)*
+
+ let body = self.body();
+ let value = body.eval(vm)?;
+ output = ops::join(output, value).at(body.span())?;
+
+ match vm.flow {
+ Some(Flow::Break(_)) => {
+ vm.flow = None;
+ break;
+ }
+ Some(Flow::Continue(_)) => vm.flow = None,
+ Some(Flow::Return(..)) => break,
+ None => {}
+ }
+ }
+
+ }};
+ }
+
+ let iter = self.iter().eval(vm)?;
+ let pattern = self.pattern();
+ let key = pattern.key().map(ast::Ident::take);
+ let value = pattern.value().take();
+
+ match (key, value, iter) {
+ (None, v, Value::Str(string)) => {
+ iter!(for (v => value) in string.as_str().graphemes(true));
+ }
+ (None, v, Value::Array(array)) => {
+ iter!(for (v => value) in array.into_iter());
+ }
+ (Some(i), v, Value::Array(array)) => {
+ iter!(for (i => idx, v => value) in array.into_iter().enumerate());
+ }
+ (None, v, Value::Dict(dict)) => {
+ iter!(for (v => value) in dict.into_iter().map(|p| p.1));
+ }
+ (Some(k), v, Value::Dict(dict)) => {
+ iter!(for (k => key, v => value) in dict.into_iter());
+ }
+ (None, v, Value::Args(args)) => {
+ iter!(for (v => value) in args.items.into_iter()
+ .filter(|arg| arg.name.is_none())
+ .map(|arg| arg.value.v));
+ }
+ (Some(k), v, Value::Args(args)) => {
+ iter!(for (k => key, v => value) in args.items.into_iter()
+ .map(|arg| (arg.name.map_or(Value::None, Value::Str), arg.value.v)));
+ }
+ (_, _, Value::Str(_)) => {
+ bail!(pattern.span(), "mismatched pattern");
+ }
+ (_, _, iter) => {
+ bail!(self.iter().span(), "cannot loop over {}", iter.type_name());
+ }
+ }
+
+ if flow.is_some() {
+ vm.flow = flow;
+ }
+
+ vm.scopes.exit();
+ Ok(output)
+ }
+}
+
+impl Eval for ast::ModuleImport {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let span = self.path().span();
+ let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?;
+ let module = import(vm, &path, span)?;
+
+ match self.imports() {
+ ast::Imports::Wildcard => {
+ for (var, value) in module.scope.iter() {
+ vm.scopes.top.define(var, value.clone());
+ }
+ }
+ ast::Imports::Items(idents) => {
+ for ident in idents {
+ if let Some(value) = module.scope.get(&ident) {
+ vm.scopes.top.define(ident.take(), value.clone());
+ } else {
+ bail!(ident.span(), "unresolved import");
+ }
+ }
+ }
+ }
+
+ Ok(Value::None)
+ }
+}
+
+impl Eval for ast::ModuleInclude {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let span = self.path().span();
+ let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?;
+ let module = import(vm, &path, span)?;
+ Ok(module.content.clone())
+ }
+}
+
+/// Process an import of a module relative to the current location.
+fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> {
+ // Load the source file.
+ let full = vm.locate(&path).at(span)?;
+ let id = vm.world.resolve(&full).at(span)?;
+
+ // Prevent cyclic importing.
+ if vm.route.contains(id) {
+ bail!(span, "cyclic import");
+ }
+
+ // Evaluate the file.
+ let module =
+ eval(vm.world, vm.route, id).trace(vm.world, || Tracepoint::Import, span)?;
+
+ Ok(module)
+}
+
+impl Eval for ast::BreakStmt {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ if vm.flow.is_none() {
+ vm.flow = Some(Flow::Break(self.span()));
+ }
+ Ok(Value::None)
+ }
+}
+
+impl Eval for ast::ContinueStmt {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ if vm.flow.is_none() {
+ vm.flow = Some(Flow::Continue(self.span()));
+ }
+ Ok(Value::None)
+ }
+}
+
+impl Eval for ast::ReturnStmt {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let value = self.body().map(|body| body.eval(vm)).transpose()?;
+ if vm.flow.is_none() {
+ vm.flow = Some(Flow::Return(self.span(), value));
+ }
+ Ok(Value::None)
+ }
+}
+
+/// Access an expression mutably.
+pub trait Access {
+ /// Access the value.
+ fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value>;
+}
+
+impl Access for ast::Expr {
+ fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
+ match self {
+ Self::Ident(v) => v.access(vm),
+ Self::FieldAccess(v) => v.access(vm),
+ Self::FuncCall(v) => v.access(vm),
+ _ => bail!(self.span(), "cannot mutate a temporary value"),
+ }
+ }
+}
+
+impl Access for ast::Ident {
+ fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
+ vm.scopes.get_mut(self).at(self.span())
+ }
+}
+
+impl Access for ast::FieldAccess {
+ fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
+ Ok(match self.target().access(vm)? {
+ Value::Dict(dict) => dict.get_mut(self.field().take().into()),
+ v => bail!(
+ self.target().span(),
+ "expected dictionary, found {}",
+ v.type_name(),
+ ),
+ })
+ }
+}
+
+impl Access for ast::FuncCall {
+ fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
+ let args = self.args().eval(vm)?;
+ Ok(match self.callee().access(vm)? {
+ Value::Array(array) => array.get_mut(args.into_index()?).at(self.span())?,
+ Value::Dict(dict) => dict.get_mut(args.into_key()?),
+ v => bail!(
+ self.callee().span(),
+ "expected collection, found {}",
+ v.type_name(),
+ ),
+ })
+ }
+}