From 06ca740d01b428f12f6bd327257cd05dce737b03 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 9 Feb 2021 19:46:57 +0100 Subject: [PATCH] =?UTF-8?q?Split=20evaluation=20and=20execution=20?= =?UTF-8?q?=F0=9F=94=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bench/src/bench.rs | 15 +- src/diag.rs | 6 - src/eval/call.rs | 22 +- src/eval/capture.rs | 8 +- src/eval/mod.rs | 380 ++++++++++++++------------------ src/eval/scope.rs | 19 +- src/eval/value.rs | 133 ++++++++--- src/{eval => exec}/context.rs | 54 +++-- src/exec/mod.rs | 161 ++++++++++++++ src/{eval => exec}/state.rs | 0 src/geom/relative.rs | 2 +- src/layout/mod.rs | 2 +- src/layout/spacing.rs | 2 +- src/lib.rs | 35 ++- src/library/insert.rs | 32 +-- src/library/layout.rs | 366 ++++++++++++++++--------------- src/library/style.rs | 111 +++++----- src/main.rs | 4 +- src/parse/collection.rs | 69 +++--- src/parse/mod.rs | 260 +++++++++++----------- src/parse/parser.rs | 139 ++++++------ src/parse/resolve.rs | 13 +- src/prelude.rs | 6 +- src/syntax/expr.rs | 401 ++++++++++++++++++++++------------ src/syntax/ident.rs | 35 +-- src/syntax/mod.rs | 7 +- src/syntax/node.rs | 8 +- src/syntax/span.rs | 17 +- src/syntax/token.rs | 2 +- src/syntax/visit.rs | 108 +++++---- tests/typeset.rs | 5 +- 31 files changed, 1403 insertions(+), 1019 deletions(-) rename src/{eval => exec}/context.rs (92%) create mode 100644 src/exec/mod.rs rename src/{eval => exec}/state.rs (100%) diff --git a/bench/src/bench.rs b/bench/src/bench.rs index 2d3230db8..88e2388d9 100644 --- a/bench/src/bench.rs +++ b/bench/src/bench.rs @@ -2,7 +2,8 @@ use criterion::{criterion_group, criterion_main, Criterion}; use fontdock::fs::FsIndex; use typst::env::{Env, ResourceLoader}; -use typst::eval::{eval, State}; +use typst::eval::eval; +use typst::exec::{exec, State}; use typst::export::pdf; use typst::font::FsIndexExt; use typst::layout::layout; @@ -33,14 +34,16 @@ fn benchmarks(c: &mut Criterion) { // Prepare intermediate results and run warm. let syntax_tree = parse(COMA).output; - let layout_tree = eval(&syntax_tree, &mut env, &scope, state.clone()).output; - let frames = layout(&layout_tree, &mut env); + let expr_map = eval(&mut env, &syntax_tree, &scope).output; + let layout_tree = exec(&mut env, &syntax_tree, &expr_map, state.clone()).output; + let frames = layout(&mut env, &layout_tree); // Bench! bench!("parse-coma": parse(COMA)); - bench!("eval-coma": eval(&syntax_tree, &mut env, &scope, state.clone())); - bench!("layout-coma": layout(&layout_tree, &mut env)); - bench!("typeset-coma": typeset(COMA, &mut env, &scope, state.clone())); + bench!("eval-coma": eval(&mut env, &syntax_tree, &scope)); + bench!("exec-coma": exec(&mut env, &syntax_tree, &expr_map, state.clone())); + bench!("layout-coma": layout(&mut env, &layout_tree)); + bench!("typeset-coma": typeset(&mut env, COMA, &scope, state.clone())); bench!("export-pdf-coma": pdf::export(&frames, &env)); } diff --git a/src/diag.rs b/src/diag.rs index fca905da2..07fd7b509 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -36,12 +36,6 @@ impl Feedback { Self { diags: vec![], decos: vec![] } } - /// Merge two feedbacks into one. - pub fn join(mut a: Self, b: Self) -> Self { - a.extend(b); - a - } - /// Add other feedback data to this feedback. pub fn extend(&mut self, more: Self) { self.diags.extend(more.diags); diff --git a/src/eval/call.rs b/src/eval/call.rs index 7b45c09a9..1a80e15a3 100644 --- a/src/eval/call.rs +++ b/src/eval/call.rs @@ -1,21 +1,21 @@ use super::*; -impl Eval for Spanned<&ExprCall> { +impl Eval for ExprCall { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let callee = self.v.callee.eval(ctx); + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let callee = self.callee.eval(ctx); if let Value::Func(func) = callee { let func = func.clone(); - let mut args = self.v.args.as_ref().eval(ctx); + let mut args = self.args.eval(ctx); let returned = func(ctx, &mut args); args.finish(ctx); return returned; } else if callee != Value::Error { ctx.diag(error!( - self.v.callee.span, + self.callee.span(), "expected function, found {}", callee.type_name(), )); @@ -25,22 +25,22 @@ impl Eval for Spanned<&ExprCall> { } } -impl Eval for Spanned<&ExprArgs> { +impl Eval for ExprArgs { type Output = Args; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { let mut pos = vec![]; let mut named = vec![]; - for arg in self.v { + for arg in &self.items { match arg { Argument::Pos(expr) => { - pos.push(expr.as_ref().eval(ctx).with_span(expr.span)); + pos.push(expr.eval(ctx).with_span(expr.span())); } Argument::Named(Named { name, expr }) => { named.push(( - name.as_ref().map(|id| id.0.clone()), - expr.as_ref().eval(ctx).with_span(expr.span), + name.string.clone().with_span(name.span), + expr.eval(ctx).with_span(expr.span()), )); } } diff --git a/src/eval/capture.rs b/src/eval/capture.rs index 9ef55fb24..bbabc5031 100644 --- a/src/eval/capture.rs +++ b/src/eval/capture.rs @@ -50,11 +50,11 @@ impl<'ast> Visit<'ast> for CapturesVisitor<'_> { fn visit_block(&mut self, item: &'ast ExprBlock) { // Blocks create a scope except if directly in a template. - if item.scopes { + if item.scoping { self.internal.push(); } visit_block(self, item); - if item.scopes { + if item.scoping { self.internal.pop(); } } @@ -67,12 +67,12 @@ impl<'ast> Visit<'ast> for CapturesVisitor<'_> { } fn visit_let(&mut self, item: &'ast ExprLet) { - self.define(&item.pat.v); + self.define(&item.binding); visit_let(self, item); } fn visit_for(&mut self, item: &'ast ExprFor) { - match &item.pat.v { + match &item.pattern { ForPattern::Value(value) => self.define(value), ForPattern::KeyValue(key, value) => { self.define(key); diff --git a/src/eval/mod.rs b/src/eval/mod.rs index e49f7779c..2390a84f8 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1,247 +1,199 @@ -//! Evaluation of syntax trees into layout trees. +//! Evaluation of syntax trees. #[macro_use] mod value; mod call; mod capture; -mod context; mod ops; mod scope; -mod state; pub use call::*; pub use capture::*; -pub use context::*; pub use scope::*; -pub use state::*; pub use value::*; +use std::collections::HashMap; use std::rc::Rc; +use super::*; use crate::color::Color; -use crate::diag::Pass; -use crate::env::Env; -use crate::geom::{Angle, Length, Relative, Spec}; -use crate::layout::{self, Expansion, NodeSpacing, NodeStack}; +use crate::diag::{Diag, Feedback}; +use crate::geom::{Angle, Length, Relative}; use crate::syntax::visit::Visit; use crate::syntax::*; -/// Evaluate a syntax tree into a layout tree. +/// Evaluate all expressions in a syntax tree. /// -/// The `state` is the base state that may be updated over the course of -/// evaluation. The `scope` similarly consists of the base definitions that are -/// present from the beginning (typically, the standard library). -pub fn eval( - tree: &Tree, - env: &mut Env, - scope: &Scope, - state: State, -) -> Pass { - let mut ctx = EvalContext::new(env, scope, state); - ctx.start_page_group(Softness::Hard); - tree.eval(&mut ctx); - ctx.end_page_group(|s| s == Softness::Hard); - ctx.finish() +/// The `scope` consists of the base definitions that are present from the +/// beginning (typically, the standard library). +pub fn eval(env: &mut Env, tree: &Tree, scope: &Scope) -> Pass { + let mut ctx = EvalContext::new(env, scope); + let map = tree.eval(&mut ctx); + Pass::new(map, ctx.feedback) } -/// Evaluate an item. +/// A map from expression to values to evaluated to. /// -/// _Note_: Evaluation is not necessarily pure, it may change the active state. +/// The raw pointers point into the expressions contained in `tree`. Since +/// the lifetime is erased, `tree` could go out of scope while the hash map +/// still lives. While this could lead to lookup panics, it is not unsafe +/// since the pointers are never dereferenced. +pub type ExprMap = HashMap<*const Expr, Value>; + +/// The context for evaluation. +#[derive(Debug)] +pub struct EvalContext<'a> { + /// The environment from which resources are gathered. + pub env: &'a mut Env, + /// The active scopes. + pub scopes: Scopes<'a>, + /// The accumulated feedback. + feedback: Feedback, +} + +impl<'a> EvalContext<'a> { + /// Create a new execution context with a base scope. + pub fn new(env: &'a mut Env, scope: &'a Scope) -> Self { + Self { + env, + scopes: Scopes::with_base(scope), + feedback: Feedback::new(), + } + } + + /// Add a diagnostic to the feedback. + pub fn diag(&mut self, diag: Spanned) { + self.feedback.diags.push(diag); + } +} + +/// Evaluate an expression. pub trait Eval { - /// The output of evaluating the item. + /// The output of evaluating the expression. type Output; - /// Evaluate the item to the output value. - fn eval(self, ctx: &mut EvalContext) -> Self::Output; + /// Evaluate the expression to the output value. + fn eval(&self, ctx: &mut EvalContext) -> Self::Output; } -impl<'a, T> Eval for &'a Spanned -where - Spanned<&'a T>: Eval, -{ - type Output = as Eval>::Output; +impl Eval for Tree { + type Output = ExprMap; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - self.as_ref().eval(ctx) - } -} - -impl Eval for &[Spanned] { - type Output = (); - - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - for node in self { - node.eval(ctx); + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + struct ExprVisitor<'a, 'b> { + map: ExprMap, + ctx: &'a mut EvalContext<'b>, } - } -} -impl Eval for Spanned<&Node> { - type Output = (); - - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - match self.v { - Node::Text(text) => { - let node = ctx.make_text_node(text.clone()); - ctx.push(node); - } - Node::Space => { - let em = ctx.state.font.font_size(); - ctx.push(NodeSpacing { - amount: ctx.state.par.word_spacing.resolve(em), - softness: Softness::Soft, - }); - } - Node::Linebreak => ctx.apply_linebreak(), - Node::Parbreak => ctx.apply_parbreak(), - Node::Strong => ctx.state.font.strong ^= true, - Node::Emph => ctx.state.font.emph ^= true, - Node::Heading(heading) => heading.with_span(self.span).eval(ctx), - Node::Raw(raw) => raw.with_span(self.span).eval(ctx), - Node::Expr(expr) => { - let value = expr.with_span(self.span).eval(ctx); - value.eval(ctx) + impl<'ast> Visit<'ast> for ExprVisitor<'_, '_> { + fn visit_expr(&mut self, item: &'ast Expr) { + self.map.insert(item as *const _, item.eval(self.ctx)); } } + + let mut visitor = ExprVisitor { map: HashMap::new(), ctx }; + visitor.visit_tree(self); + visitor.map } } -impl Eval for Spanned<&NodeHeading> { - type Output = (); - - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let prev = ctx.state.clone(); - let upscale = 1.5 - 0.1 * self.v.level.v as f64; - ctx.state.font.scale *= upscale; - ctx.state.font.strong = true; - - self.v.contents.eval(ctx); - ctx.apply_parbreak(); - - ctx.state = prev; - } -} - -impl Eval for Spanned<&NodeRaw> { - type Output = (); - - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let prev = Rc::clone(&ctx.state.font.families); - let families = ctx.state.font.families_mut(); - families.list.insert(0, "monospace".to_string()); - families.flatten(); - - let em = ctx.state.font.font_size(); - let line_spacing = ctx.state.par.line_spacing.resolve(em); - - let mut children = vec![]; - for line in &self.v.lines { - children.push(layout::Node::Text(ctx.make_text_node(line.clone()))); - children.push(layout::Node::Spacing(NodeSpacing { - amount: line_spacing, - softness: Softness::Hard, - })); - } - - if self.v.block { - ctx.apply_parbreak(); - } - - ctx.push(NodeStack { - dirs: ctx.state.dirs, - align: ctx.state.align, - expand: Spec::uniform(Expansion::Fit), - children, - }); - - if self.v.block { - ctx.apply_parbreak(); - } - - ctx.state.font.families = prev; - } -} - -impl Eval for Spanned<&Expr> { +impl Eval for Expr { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - match self.v { - Expr::None => Value::None, - Expr::Ident(v) => match ctx.scopes.get(v) { + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + match self { + Self::Lit(lit) => lit.eval(ctx), + Self::Ident(v) => match ctx.scopes.get(&v) { Some(slot) => slot.borrow().clone(), None => { - ctx.diag(error!(self.span, "unknown variable")); + ctx.diag(error!(v.span, "unknown variable")); Value::Error } }, - &Expr::Bool(v) => Value::Bool(v), - &Expr::Int(v) => Value::Int(v), - &Expr::Float(v) => Value::Float(v), - &Expr::Length(v, unit) => Value::Length(Length::with_unit(v, unit)), - &Expr::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)), - &Expr::Percent(v) => Value::Relative(Relative::new(v / 100.0)), - &Expr::Color(v) => Value::Color(Color::Rgba(v)), - Expr::Str(v) => Value::Str(v.clone()), - Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)), - Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)), - Expr::Template(v) => v.with_span(self.span).eval(ctx), - Expr::Group(v) => v.eval(ctx), - Expr::Block(v) => v.with_span(self.span).eval(ctx), - Expr::Call(v) => v.with_span(self.span).eval(ctx), - Expr::Unary(v) => v.with_span(self.span).eval(ctx), - Expr::Binary(v) => v.with_span(self.span).eval(ctx), - Expr::Let(v) => v.with_span(self.span).eval(ctx), - Expr::If(v) => v.with_span(self.span).eval(ctx), - Expr::For(v) => v.with_span(self.span).eval(ctx), + Self::Array(v) => Value::Array(v.eval(ctx)), + Self::Dict(v) => Value::Dict(v.eval(ctx)), + Self::Template(v) => Value::Template(vec![v.eval(ctx)]), + Self::Group(v) => v.eval(ctx), + Self::Block(v) => v.eval(ctx), + Self::Call(v) => v.eval(ctx), + Self::Unary(v) => v.eval(ctx), + Self::Binary(v) => v.eval(ctx), + Self::Let(v) => v.eval(ctx), + Self::If(v) => v.eval(ctx), + Self::For(v) => v.eval(ctx), } } } -impl Eval for Spanned<&ExprArray> { - type Output = ValueArray; +impl Eval for Lit { + type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - self.v.iter().map(|expr| expr.eval(ctx)).collect() + fn eval(&self, _: &mut EvalContext) -> Self::Output { + match self.kind { + LitKind::None => Value::None, + LitKind::Bool(v) => Value::Bool(v), + LitKind::Int(v) => Value::Int(v), + LitKind::Float(v) => Value::Float(v), + LitKind::Length(v, unit) => Value::Length(Length::with_unit(v, unit)), + LitKind::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)), + LitKind::Percent(v) => Value::Relative(Relative::new(v / 100.0)), + LitKind::Color(v) => Value::Color(Color::Rgba(v)), + LitKind::Str(ref v) => Value::Str(v.clone()), + } } } -impl Eval for Spanned<&ExprDict> { +impl Eval for ExprArray { + type Output = ValueArray; + + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + self.items.iter().map(|expr| expr.eval(ctx)).collect() + } +} + +impl Eval for ExprDict { type Output = ValueDict; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - self.v + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + self.items .iter() - .map(|Named { name, expr }| (name.v.0.clone(), expr.eval(ctx))) + .map(|Named { name, expr }| (name.string.clone(), expr.eval(ctx))) .collect() } } -impl Eval for Spanned<&ExprTemplate> { - type Output = Value; +impl Eval for ExprTemplate { + type Output = TemplateNode; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let mut template = self.v.clone(); - let mut visitor = CapturesVisitor::new(&ctx.scopes); - visitor.visit_template(&mut template); - Value::Template(template) + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let tree = Rc::clone(&self.tree); + let map = self.tree.eval(ctx); + TemplateNode::Tree { tree, map } } } -impl Eval for Spanned<&ExprBlock> { +impl Eval for ExprGroup { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - if self.v.scopes { + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + self.expr.eval(ctx) + } +} + +impl Eval for ExprBlock { + type Output = Value; + + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + if self.scoping { ctx.scopes.push(); } let mut output = Value::None; - for expr in &self.v.exprs { + for expr in &self.exprs { output = expr.eval(ctx); } - if self.v.scopes { + if self.scoping { ctx.scopes.pop(); } @@ -249,17 +201,17 @@ impl Eval for Spanned<&ExprBlock> { } } -impl Eval for Spanned<&ExprUnary> { +impl Eval for ExprUnary { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let value = self.v.expr.eval(ctx); + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let value = self.expr.eval(ctx); if value == Value::Error { return Value::Error; } let ty = value.type_name(); - let out = match self.v.op.v { + let out = match self.op { UnOp::Pos => ops::pos(value), UnOp::Neg => ops::neg(value), UnOp::Not => ops::not(value), @@ -269,7 +221,7 @@ impl Eval for Spanned<&ExprUnary> { ctx.diag(error!( self.span, "cannot apply '{}' to {}", - self.v.op.v.as_str(), + self.op.as_str(), ty, )); } @@ -278,11 +230,11 @@ impl Eval for Spanned<&ExprUnary> { } } -impl Eval for Spanned<&ExprBinary> { +impl Eval for ExprBinary { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - match self.v.op.v { + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + match self.op { BinOp::Add => self.apply(ctx, ops::add), BinOp::Sub => self.apply(ctx, ops::sub), BinOp::Mul => self.apply(ctx, ops::mul), @@ -304,22 +256,22 @@ impl Eval for Spanned<&ExprBinary> { } } -impl Spanned<&ExprBinary> { +impl ExprBinary { /// Apply a basic binary operation. - fn apply(self, ctx: &mut EvalContext, op: F) -> Value + fn apply(&self, ctx: &mut EvalContext, op: F) -> Value where F: FnOnce(Value, Value) -> Value, { - let lhs = self.v.lhs.eval(ctx); + let lhs = self.lhs.eval(ctx); // Short-circuit boolean operations. - match (self.v.op.v, &lhs) { + match (self.op, &lhs) { (BinOp::And, Value::Bool(false)) => return lhs, (BinOp::Or, Value::Bool(true)) => return lhs, _ => {} } - let rhs = self.v.rhs.eval(ctx); + let rhs = self.rhs.eval(ctx); if lhs == Value::Error || rhs == Value::Error { return Value::Error; @@ -336,23 +288,23 @@ impl Spanned<&ExprBinary> { } /// Apply an assignment operation. - fn assign(self, ctx: &mut EvalContext, op: F) -> Value + fn assign(&self, ctx: &mut EvalContext, op: F) -> Value where F: FnOnce(Value, Value) -> Value, { - let rhs = self.v.rhs.eval(ctx); - let span = self.v.lhs.span; + let rhs = self.rhs.eval(ctx); - let slot = if let Expr::Ident(id) = &self.v.lhs.v { + let lhs_span = self.lhs.span(); + let slot = if let Expr::Ident(id) = self.lhs.as_ref() { match ctx.scopes.get(id) { Some(slot) => slot, None => { - ctx.diag(error!(span, "unknown variable")); + ctx.diag(error!(lhs_span, "unknown variable")); return Value::Error; } } } else { - ctx.diag(error!(span, "cannot assign to this expression")); + ctx.diag(error!(lhs_span, "cannot assign to this expression")); return Value::Error; }; @@ -371,7 +323,7 @@ impl Spanned<&ExprBinary> { }; if constant { - ctx.diag(error!(span, "cannot assign to a constant")); + ctx.diag(error!(lhs_span, "cannot assign to a constant")); } if let Some((l, r)) = err { @@ -382,47 +334,45 @@ impl Spanned<&ExprBinary> { } fn error(&self, ctx: &mut EvalContext, l: &str, r: &str) { - let op = self.v.op.v.as_str(); - let message = match self.v.op.v { + ctx.diag(error!(self.span, "{}", match self.op { BinOp::Add => format!("cannot add {} and {}", l, r), BinOp::Sub => format!("cannot subtract {1} from {0}", l, r), BinOp::Mul => format!("cannot multiply {} with {}", l, r), BinOp::Div => format!("cannot divide {} by {}", l, r), - _ => format!("cannot apply '{}' to {} and {}", op, l, r), - }; - ctx.diag(error!(self.span, "{}", message)); + _ => format!("cannot apply '{}' to {} and {}", self.op.as_str(), l, r), + })); } } -impl Eval for Spanned<&ExprLet> { +impl Eval for ExprLet { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let value = match &self.v.init { + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let value = match &self.init { Some(expr) => expr.eval(ctx), None => Value::None, }; - ctx.scopes.def_mut(self.v.pat.v.as_str(), value); + ctx.scopes.def_mut(self.binding.as_str(), value); Value::None } } -impl Eval for Spanned<&ExprIf> { +impl Eval for ExprIf { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let condition = self.v.condition.eval(ctx); + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let condition = self.condition.eval(ctx); if let Value::Bool(boolean) = condition { return if boolean { - self.v.if_body.eval(ctx) - } else if let Some(expr) = &self.v.else_body { + self.if_body.eval(ctx) + } else if let Some(expr) = &self.else_body { expr.eval(ctx) } else { Value::None }; } else if condition != Value::Error { ctx.diag(error!( - self.v.condition.span, + self.condition.span(), "expected boolean, found {}", condition.type_name(), )); @@ -432,10 +382,10 @@ impl Eval for Spanned<&ExprIf> { } } -impl Eval for Spanned<&ExprFor> { +impl Eval for ExprFor { type Output = Value; - fn eval(self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { macro_rules! iterate { (for ($($binding:ident => $value:ident),*) in $iter:expr) => {{ let mut output = vec![]; @@ -444,7 +394,7 @@ impl Eval for Spanned<&ExprFor> { for ($($value),*) in $iter { $(ctx.scopes.def_mut($binding.as_str(), $value);)* - if let Value::Template(new) = self.v.body.eval(ctx) { + if let Value::Template(new) = self.body.eval(ctx) { output.extend(new); } } @@ -455,8 +405,8 @@ impl Eval for Spanned<&ExprFor> { ctx.scopes.push(); - let iter = self.v.iter.eval(ctx); - let value = match (self.v.pat.v.clone(), iter) { + let iter = self.iter.eval(ctx); + let value = match (self.pattern.clone(), iter) { (ForPattern::Value(v), Value::Str(string)) => { iterate!(for (v => value) in string.chars().map(|c| Value::Str(c.into()))) } @@ -472,14 +422,14 @@ impl Eval for Spanned<&ExprFor> { (ForPattern::KeyValue(_, _), Value::Str(_)) | (ForPattern::KeyValue(_, _), Value::Array(_)) => { - ctx.diag(error!(self.v.pat.span, "mismatched pattern")); + ctx.diag(error!(self.pattern.span(), "mismatched pattern")); Value::Error } (_, Value::Error) => Value::Error, (_, iter) => { ctx.diag(error!( - self.v.iter.span, + self.iter.span(), "cannot loop over {}", iter.type_name(), )); diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 8f2bd1d52..0991564fc 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -21,9 +21,22 @@ pub struct Scopes<'a> { } impl<'a> Scopes<'a> { - /// Create a new hierarchy of scopes. - pub fn new(base: Option<&'a Scope>) -> Self { - Self { top: Scope::new(), scopes: vec![], base } + /// Create a new, empty hierarchy of scopes. + pub fn new() -> Self { + Self { + top: Scope::new(), + scopes: vec![], + base: None, + } + } + + /// Create a new hierarchy of scopes with a base scope. + pub fn with_base(base: &'a Scope) -> Self { + Self { + top: Scope::new(), + scopes: vec![], + base: Some(base), + } } /// Push a new scope. diff --git a/src/eval/value.rs b/src/eval/value.rs index 119a2f1b0..dd1221e5b 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -4,11 +4,12 @@ use std::fmt::{self, Debug, Display, Formatter}; use std::ops::Deref; use std::rc::Rc; -use super::{Args, Eval, EvalContext}; +use super::*; use crate::color::Color; +use crate::exec::ExecContext; use crate::geom::{Angle, Length, Linear, Relative}; -use crate::pretty::{pretty, Pretty, Printer}; -use crate::syntax::{pretty_template, Spanned, Tree, WithSpan}; +use crate::pretty::{Pretty, Printer}; +use crate::syntax::Tree; /// A computational value. #[derive(Debug, Clone, PartialEq)] @@ -48,12 +49,12 @@ pub enum Value { } impl Value { - /// Try to cast the value into a specific type. - pub fn cast(self) -> CastResult + /// Create a new template value consisting of a single dynamic node. + pub fn template(f: F) -> Self where - T: Cast, + F: Fn(&mut ExecContext) + 'static, { - T::cast(self) + Self::Template(vec![TemplateNode::Any(TemplateAny::new(f))]) } /// The name of the stored value's type. @@ -77,26 +78,13 @@ impl Value { Self::Error => "error", } } -} -impl Eval for &Value { - type Output = (); - - /// Evaluate everything contained in this value. - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - ctx.push(ctx.make_text_node(match self { - Value::None => return, - Value::Str(s) => s.clone(), - Value::Template(tree) => { - // We do not want to allow the template access to the current - // scopes. - let prev = std::mem::take(&mut ctx.scopes); - tree.eval(ctx); - ctx.scopes = prev; - return; - } - other => pretty(other), - })); + /// Try to cast the value into a specific type. + pub fn cast(self) -> CastResult + where + T: Cast, + { + T::cast(self) } } @@ -121,7 +109,7 @@ impl Pretty for Value { Value::Str(v) => v.pretty(p), Value::Array(v) => v.pretty(p), Value::Dict(v) => v.pretty(p), - Value::Template(v) => pretty_template(v, p), + Value::Template(v) => v.pretty(p), Value::Func(v) => v.pretty(p), Value::Any(v) => v.pretty(p), Value::Error => p.push_str("(error)"), @@ -163,7 +151,88 @@ impl Pretty for ValueDict { } /// A template value: `[*Hi* there]`. -pub type ValueTemplate = Tree; +pub type ValueTemplate = Vec; + +impl Pretty for ValueTemplate { + fn pretty(&self, p: &mut Printer) { + p.push('['); + for part in self { + part.pretty(p); + } + p.push(']'); + } +} + +/// One chunk of a template. +/// +/// Evaluating a template expression creates only a single chunk. Adding two +/// such templates yields a two-chunk template. +#[derive(Debug, Clone, PartialEq)] +pub enum TemplateNode { + /// A template that consists of a syntax tree plus already evaluated + /// expression. + Tree { + /// The tree of this template part. + tree: Rc, + /// The evaluated expressions. + map: ExprMap, + }, + /// A template that can implement custom behaviour. + Any(TemplateAny), +} + +impl Pretty for TemplateNode { + fn pretty(&self, p: &mut Printer) { + match self { + // TODO: Pretty-print the values. + Self::Tree { tree, .. } => tree.pretty(p), + Self::Any(any) => any.pretty(p), + } + } +} + +/// A reference-counted dynamic template node (can implement custom behaviour). +#[derive(Clone)] +pub struct TemplateAny { + f: Rc, +} + +impl TemplateAny { + /// Create a new dynamic template value from a rust function or closure. + pub fn new(f: F) -> Self + where + F: Fn(&mut ExecContext) + 'static, + { + Self { f: Rc::new(f) } + } +} + +impl PartialEq for TemplateAny { + fn eq(&self, _: &Self) -> bool { + // TODO: Figure out what we want here. + false + } +} + +impl Deref for TemplateAny { + type Target = dyn Fn(&mut ExecContext); + + fn deref(&self) -> &Self::Target { + self.f.as_ref() + } +} + +impl Pretty for TemplateAny { + fn pretty(&self, p: &mut Printer) { + p.push_str(""); + } +} + +impl Debug for TemplateAny { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_struct("TemplateAny").finish() + } +} /// A wrapper around a reference-counted executable function. #[derive(Clone)] @@ -184,6 +253,7 @@ impl ValueFunc { impl PartialEq for ValueFunc { fn eq(&self, _: &Self) -> bool { + // TODO: Figure out what we want here. false } } @@ -497,9 +567,7 @@ macro_rules! impl_type { mod tests { use super::*; use crate::color::RgbaColor; - use crate::parse::parse; use crate::pretty::pretty; - use crate::syntax::Node; #[track_caller] fn test_pretty(value: impl Into, exp: &str) { @@ -517,7 +585,6 @@ mod tests { test_pretty(Relative::new(0.3) + Length::cm(2.0), "30.0% + 2.0cm"); test_pretty(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101"); test_pretty("hello", r#""hello""#); - test_pretty(vec![Spanned::zero(Node::Strong)], "[*]"); test_pretty(ValueFunc::new("nil", |_, _| Value::None), "nil"); test_pretty(ValueAny::new(1), "1"); test_pretty(Value::Error, "(error)"); @@ -533,8 +600,8 @@ mod tests { // Dictionary. let mut dict = BTreeMap::new(); dict.insert("one".into(), Value::Int(1)); - dict.insert("two".into(), Value::Template(parse("#[f]").output)); + dict.insert("two".into(), Value::Bool(false)); test_pretty(BTreeMap::new(), "(:)"); - test_pretty(dict, "(one: 1, two: #[f])"); + test_pretty(dict, "(one: 1, two: false)"); } } diff --git a/src/eval/context.rs b/src/exec/context.rs similarity index 92% rename from src/eval/context.rs rename to src/exec/context.rs index fd7e264fe..a1998f293 100644 --- a/src/eval/context.rs +++ b/src/exec/context.rs @@ -11,14 +11,12 @@ use crate::layout::{ Expansion, Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree, }; -/// The context for evaluation. +/// The context for execution. #[derive(Debug)] -pub struct EvalContext<'a> { +pub struct ExecContext<'a> { /// The environment from which resources are gathered. pub env: &'a mut Env, - /// The active scopes. - pub scopes: Scopes<'a>, - /// The active evaluation state. + /// The active execution state. pub state: State, /// The accumulated feedback. feedback: Feedback, @@ -35,12 +33,11 @@ pub struct EvalContext<'a> { inner: Vec, } -impl<'a> EvalContext<'a> { - /// Create a new evaluation context with a base state and scope. - pub fn new(env: &'a mut Env, scope: &'a Scope, state: State) -> Self { +impl<'a> ExecContext<'a> { + /// Create a new execution context with a base state. + pub fn new(env: &'a mut Env, state: State) -> Self { Self { env, - scopes: Scopes::new(Some(scope)), state, groups: vec![], inner: vec![], @@ -49,7 +46,7 @@ impl<'a> EvalContext<'a> { } } - /// Finish evaluation and return the created document. + /// Finish execution and return the created layout tree. pub fn finish(self) -> Pass { assert!(self.groups.is_empty(), "unfinished group"); Pass::new(Tree { runs: self.runs }, self.feedback) @@ -226,21 +223,19 @@ impl<'a> EvalContext<'a> { } } - /// Apply a forced line break. - pub fn apply_linebreak(&mut self) { - self.end_par_group(); - self.start_par_group(); - } - - /// Apply a forced paragraph break. - pub fn apply_parbreak(&mut self) { - self.end_par_group(); + /// Push a normal space. + pub fn push_space(&mut self) { let em = self.state.font.font_size(); self.push(NodeSpacing { - amount: self.state.par.par_spacing.resolve(em), + amount: self.state.par.word_spacing.resolve(em), softness: Softness::Soft, }); - self.start_par_group(); + } + + /// Push a text node. + pub fn push_text(&mut self, text: impl Into) { + let node = self.make_text_node(text.into()); + self.push(node); } /// Construct a text node from the given string based on the active text @@ -269,6 +264,23 @@ impl<'a> EvalContext<'a> { variant, } } + + /// Apply a forced line break. + pub fn apply_linebreak(&mut self) { + self.end_par_group(); + self.start_par_group(); + } + + /// Apply a forced paragraph break. + pub fn apply_parbreak(&mut self) { + self.end_par_group(); + let em = self.state.font.font_size(); + self.push(NodeSpacing { + amount: self.state.par.par_spacing.resolve(em), + softness: Softness::Soft, + }); + self.start_par_group(); + } } /// Defines how an item interacts with surrounding items. diff --git a/src/exec/mod.rs b/src/exec/mod.rs new file mode 100644 index 000000000..25edcce3c --- /dev/null +++ b/src/exec/mod.rs @@ -0,0 +1,161 @@ +//! Execution of syntax trees. + +mod context; +mod state; + +pub use context::*; +pub use state::*; + +use std::rc::Rc; + +use crate::diag::Pass; +use crate::env::Env; +use crate::eval::{ExprMap, TemplateAny, TemplateNode, Value, ValueTemplate}; +use crate::geom::Spec; +use crate::layout::{self, Expansion, NodeSpacing, NodeStack}; +use crate::pretty::pretty; +use crate::syntax::*; + +/// Execute a syntax tree to produce a layout tree. +/// +/// The `map` shall be an expression map computed for this tree with +/// [`eval`](crate::eval::eval). Note that `tree` must be the _exact_ same tree +/// as used for evaluation (no cloned version), because the expression map +/// depends on the pointers being stable. +/// +/// The `state` is the base state that may be updated over the course of +/// execution. +pub fn exec( + env: &mut Env, + tree: &Tree, + map: &ExprMap, + state: State, +) -> Pass { + let mut ctx = ExecContext::new(env, state); + ctx.start_page_group(Softness::Hard); + tree.exec_with(&mut ctx, &map); + ctx.end_page_group(|s| s == Softness::Hard); + ctx.finish() +} + +/// Execute a node. +/// +/// This manipulates active styling and document state and produces layout +/// nodes. Because syntax nodes and layout nodes do not correspond one-to-one, +/// constructed layout nodes are pushed into the context instead of returned. +/// The context takes care of reshaping the nodes into the correct tree +/// structure. +pub trait Exec { + /// Execute the node. + fn exec(&self, ctx: &mut ExecContext); +} + +/// Execute a node with an expression map that applies to it. +pub trait ExecWith { + /// Execute the node. + fn exec_with(&self, ctx: &mut ExecContext, map: &ExprMap); +} + +impl ExecWith for Tree { + fn exec_with(&self, ctx: &mut ExecContext, map: &ExprMap) { + for node in self { + match node { + Node::Text(text) => ctx.push_text(text), + Node::Space => ctx.push_space(), + Node::Linebreak => ctx.apply_linebreak(), + Node::Parbreak => ctx.apply_parbreak(), + Node::Strong => ctx.state.font.strong ^= true, + Node::Emph => ctx.state.font.emph ^= true, + Node::Heading(heading) => heading.exec_with(ctx, map), + Node::Raw(raw) => raw.exec(ctx), + Node::Expr(expr) => map[&(expr as *const _)].exec(ctx), + } + } + } +} + +impl ExecWith for NodeHeading { + fn exec_with(&self, ctx: &mut ExecContext, map: &ExprMap) { + let prev = ctx.state.clone(); + let upscale = 1.5 - 0.1 * self.level as f64; + ctx.state.font.scale *= upscale; + ctx.state.font.strong = true; + + self.contents.exec_with(ctx, map); + ctx.apply_parbreak(); + + ctx.state = prev; + } +} + +impl Exec for NodeRaw { + fn exec(&self, ctx: &mut ExecContext) { + let prev = Rc::clone(&ctx.state.font.families); + let families = ctx.state.font.families_mut(); + families.list.insert(0, "monospace".to_string()); + families.flatten(); + + let em = ctx.state.font.font_size(); + let line_spacing = ctx.state.par.line_spacing.resolve(em); + + let mut children = vec![]; + for line in &self.lines { + children.push(layout::Node::Text(ctx.make_text_node(line.clone()))); + children.push(layout::Node::Spacing(NodeSpacing { + amount: line_spacing, + softness: Softness::Hard, + })); + } + + if self.block { + ctx.apply_parbreak(); + } + + ctx.push(NodeStack { + dirs: ctx.state.dirs, + align: ctx.state.align, + expand: Spec::uniform(Expansion::Fit), + children, + }); + + if self.block { + ctx.apply_parbreak(); + } + + ctx.state.font.families = prev; + } +} + +impl Exec for Value { + fn exec(&self, ctx: &mut ExecContext) { + match self { + Value::None => {} + Value::Str(s) => ctx.push_text(s), + Value::Template(template) => template.exec(ctx), + other => ctx.push_text(pretty(other)), + } + } +} + +impl Exec for ValueTemplate { + fn exec(&self, ctx: &mut ExecContext) { + for part in self { + part.exec(ctx); + } + } +} + +impl Exec for TemplateNode { + fn exec(&self, ctx: &mut ExecContext) { + match self { + Self::Tree { tree, map } => tree.exec_with(ctx, &map), + Self::Any(any) => any.exec(ctx), + } + } +} + +impl Exec for TemplateAny { + fn exec(&self, ctx: &mut ExecContext) { + self(ctx); + } +} diff --git a/src/eval/state.rs b/src/exec/state.rs similarity index 100% rename from src/eval/state.rs rename to src/exec/state.rs diff --git a/src/geom/relative.rs b/src/geom/relative.rs index 1d040c728..0eca911e3 100644 --- a/src/geom/relative.rs +++ b/src/geom/relative.rs @@ -3,7 +3,7 @@ use super::*; /// A relative length. /// /// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the -/// corresponding [literal](crate::syntax::Expr::Percent). +/// corresponding [literal](crate::syntax::LitKind::Percent). #[derive(Default, Copy, Clone, PartialEq, PartialOrd)] pub struct Relative(f64); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 302958417..1974b5783 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -24,7 +24,7 @@ pub use stack::*; pub use text::*; /// Layout a tree into a collection of frames. -pub fn layout(tree: &Tree, env: &mut Env) -> Vec { +pub fn layout(env: &mut Env, tree: &Tree) -> Vec { tree.layout(&mut LayoutContext { env }) } diff --git a/src/layout/spacing.rs b/src/layout/spacing.rs index 1d6c7f9c2..4b564a2b5 100644 --- a/src/layout/spacing.rs +++ b/src/layout/spacing.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Debug, Formatter}; use super::*; -use crate::eval::Softness; +use crate::exec::Softness; /// A spacing node. #[derive(Copy, Clone, PartialEq)] diff --git a/src/lib.rs b/src/lib.rs index 39ca47a3c..efdbb4cb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,10 +5,13 @@ //! [iterator of tokens][tokens]. This token stream is [parsed] into a [syntax //! tree]. The structures describing the tree can be found in the [syntax] //! module. -//! - **Evaluation:** The next step is to [evaluate] the parsed "script" into a -//! [layout tree], a high-level, fully styled representation. The nodes of -//! this tree are fully self-contained and order-independent and thus much -//! better suited for layouting than the syntax tree. +//! - **Evaluation:** The next step is to [evaluate] the syntax tree. This +//! computes the value of each expression in document and stores them in a map +//! from expression-pointers to values. +//! - **Execution:** Now, we can [execute] the parsed and evaluated "script". +//! This produces a [layout tree], a high-level, fully styled representation. +//! The nodes of this tree are self-contained and order-independent and thus +//! much better suited for layouting than the syntax tree. //! - **Layouting:** Next, the tree is to [layouted] into a portable version of //! the typeset document. The output of this is a vector of [`Frame`]s //! (corresponding to pages), ready for exporting. @@ -20,6 +23,7 @@ //! [parsed]: parse::parse //! [syntax tree]: syntax::Tree //! [evaluate]: eval::eval +//! [execute]: exec::exec //! [layout tree]: layout::Tree //! [layouted]: layout::layout //! [_PDF_]: export::pdf @@ -30,6 +34,7 @@ pub mod diag; pub mod eval; pub mod color; pub mod env; +pub mod exec; pub mod export; pub mod font; pub mod geom; @@ -42,21 +47,27 @@ pub mod pretty; pub mod shaping; pub mod syntax; -use crate::diag::{Feedback, Pass}; +use crate::diag::Pass; use crate::env::Env; -use crate::eval::{Scope, State}; +use crate::eval::Scope; +use crate::exec::State; use crate::layout::Frame; /// Process _Typst_ source code directly into a collection of frames. pub fn typeset( - src: &str, env: &mut Env, + src: &str, scope: &Scope, state: State, ) -> Pass> { - let Pass { output: syntax_tree, feedback: f1 } = parse::parse(src); - let Pass { output: layout_tree, feedback: f2 } = - eval::eval(&syntax_tree, env, scope, state); - let frames = layout::layout(&layout_tree, env); - Pass::new(frames, Feedback::join(f1, f2)) + let parsed = parse::parse(src); + let evaluated = eval::eval(env, &parsed.output, scope); + let executed = exec::exec(env, &parsed.output, &evaluated.output, state); + let frames = layout::layout(env, &executed.output); + + let mut feedback = parsed.feedback; + feedback.extend(evaluated.feedback); + feedback.extend(executed.feedback); + + Pass::new(frames, feedback) } diff --git a/src/library/insert.rs b/src/library/insert.rs index 58e8a11c2..eff54e914 100644 --- a/src/library/insert.rs +++ b/src/library/insert.rs @@ -15,23 +15,23 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value { let width = args.get(ctx, "width"); let height = args.get(ctx, "height"); - if let Some(path) = path { - let loaded = ctx.env.resources.load(path.v, ImageResource::parse); - if let Some((res, img)) = loaded { - let dimensions = img.buf.dimensions(); - ctx.push(NodeImage { - res, - dimensions, - width, - height, - align: ctx.state.align, - }); - } else { - ctx.diag(error!(path.span, "failed to load image")); + Value::template(move |ctx| { + if let Some(path) = &path { + let loaded = ctx.env.resources.load(&path.v, ImageResource::parse); + if let Some((res, img)) = loaded { + let dimensions = img.buf.dimensions(); + ctx.push(NodeImage { + res, + dimensions, + width, + height, + align: ctx.state.align, + }); + } else { + ctx.diag(error!(path.span, "failed to load image")); + } } - } - - Value::None + }) } /// An image node. diff --git a/src/library/layout.rs b/src/library/layout.rs index e48399539..44c985362 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -1,9 +1,9 @@ use std::fmt::{self, Display, Formatter}; -use crate::layout::{Expansion, Fill, NodeFixed, NodeSpacing, NodeStack}; +use crate::exec::Softness; +use crate::layout::{Expansion, Fill, NodeBackground, NodeFixed, NodeSpacing, NodeStack}; use crate::paper::{Paper, PaperClass}; use crate::prelude::*; -use crate::{eval::Softness, layout::NodeBackground}; /// `align`: Align content along the layouting axes. /// @@ -27,88 +27,91 @@ use crate::{eval::Softness, layout::NodeBackground}; /// - `bottom` /// - `center` pub fn align(ctx: &mut EvalContext, args: &mut Args) -> Value { - let snapshot = ctx.state.clone(); - let first = args.find(ctx); let second = args.find(ctx); let hor = args.get(ctx, "horizontal"); let ver = args.get(ctx, "vertical"); + let body = args.find::(ctx); - let mut had = Gen::uniform(false); - let mut had_center = false; + Value::template(move |ctx| { + let snapshot = ctx.state.clone(); - for (axis, Spanned { v: arg, span }) in first - .into_iter() - .chain(second.into_iter()) - .map(|arg: Spanned| (arg.v.axis(), arg)) - .chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg))) - .chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg))) - { - // Check whether we know which axis this alignment belongs to. - if let Some(axis) = axis { - // We know the axis. - let gen_axis = axis.switch(ctx.state.dirs); - let gen_align = arg.switch(ctx.state.dirs); + let mut had = Gen::uniform(false); + let mut had_center = false; - if arg.axis().map_or(false, |a| a != axis) { - ctx.diag(error!(span, "invalid alignment for {} axis", axis)); - } else if had.get(gen_axis) { - ctx.diag(error!(span, "duplicate alignment for {} axis", axis)); + // Infer the axes alignments belong to. + for (axis, Spanned { v: arg, span }) in first + .into_iter() + .chain(second.into_iter()) + .map(|arg: Spanned| (arg.v.axis(), arg)) + .chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg))) + .chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg))) + { + // Check whether we know which axis this alignment belongs to. + if let Some(axis) = axis { + // We know the axis. + let gen_axis = axis.switch(ctx.state.dirs); + let gen_align = arg.switch(ctx.state.dirs); + + if arg.axis().map_or(false, |a| a != axis) { + ctx.diag(error!(span, "invalid alignment for {} axis", axis)); + } else if had.get(gen_axis) { + ctx.diag(error!(span, "duplicate alignment for {} axis", axis)); + } else { + *ctx.state.align.get_mut(gen_axis) = gen_align; + *had.get_mut(gen_axis) = true; + } } else { - *ctx.state.align.get_mut(gen_axis) = gen_align; - *had.get_mut(gen_axis) = true; + // We don't know the axis: This has to be a `center` alignment for a + // positional argument. + debug_assert_eq!(arg, Alignment::Center); + + if had.main && had.cross { + ctx.diag(error!(span, "duplicate alignment")); + } else if had_center { + // Both this and the previous one are unspecified `center` + // alignments. Both axes should be centered. + ctx.state.align.main = Align::Center; + ctx.state.align.cross = Align::Center; + had = Gen::uniform(true); + } else { + had_center = true; + } } - } else { - // We don't know the axis: This has to be a `center` alignment for a - // positional argument. - debug_assert_eq!(arg, Alignment::Center); - if had.main && had.cross { - ctx.diag(error!(span, "duplicate alignment")); - } else if had_center { - // Both this and the previous one are unspecified `center` - // alignments. Both axes should be centered. - ctx.state.align.main = Align::Center; - ctx.state.align.cross = Align::Center; - had = Gen::uniform(true); - } else { - had_center = true; + // If we we know the other alignment, we can handle the unspecified + // `center` alignment. + if had_center && (had.main || had.cross) { + if had.main { + ctx.state.align.cross = Align::Center; + had.cross = true; + } else { + ctx.state.align.main = Align::Center; + had.main = true; + } + had_center = false; } } - // If we we know the other alignment, we can handle the unspecified - // `center` alignment. - if had_center && (had.main || had.cross) { - if had.main { - ctx.state.align.cross = Align::Center; - had.cross = true; - } else { - ctx.state.align.main = Align::Center; - had.main = true; - } - had_center = false; + // If `had_center` wasn't flushed by now, it's the only argument and then we + // default to applying it to the cross axis. + if had_center { + ctx.state.align.cross = Align::Center; } - } - // If `had_center` wasn't flushed by now, it's the only argument and then we - // default to applying it to the cross axis. - if had_center { - ctx.state.align.cross = Align::Center; - } + if ctx.state.align.main != snapshot.align.main { + ctx.end_par_group(); + ctx.start_par_group(); + } - if ctx.state.align.main != snapshot.align.main { - ctx.end_par_group(); - ctx.start_par_group(); - } - - if let Some(body) = args.find::(ctx) { - body.eval(ctx); - ctx.state = snapshot; - } - - Value::None + if let Some(body) = &body { + body.exec(ctx); + ctx.state = snapshot; + } + }) } +/// An alignment argument. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub(crate) enum Alignment { Left, @@ -173,10 +176,10 @@ impl Display for Alignment { /// `box`: Layout content into a box. /// /// # Named arguments -/// - Width of the box: `width`, of type `linear` relative to parent width. -/// - Height of the box: `height`, of type `linear` relative to parent height. -/// - Main layouting direction: `main-dir`, of type `direction`. -/// - Cross layouting direction: `cross-dir`, of type `direction`. +/// - Width of the box: `width`, of type `linear` relative to parent width. +/// - Height of the box: `height`, of type `linear` relative to parent height. +/// - Main layouting direction: `main-dir`, of type `direction`. +/// - Cross layouting direction: `cross-dir`, of type `direction`. /// - Background color of the box: `color`, of type `color`. /// /// # Relevant types and constants @@ -186,47 +189,45 @@ impl Display for Alignment { /// - `ttb` (top to bottom) /// - `btt` (bottom to top) pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value { - let snapshot = ctx.state.clone(); - let width = args.get(ctx, "width"); let height = args.get(ctx, "height"); let main = args.get(ctx, "main-dir"); let cross = args.get(ctx, "cross-dir"); let color = args.get(ctx, "color"); + let body = args.find::(ctx); - ctx.set_dirs(Gen::new(main, cross)); + Value::template(move |ctx| { + let snapshot = ctx.state.clone(); - let dirs = ctx.state.dirs; - let align = ctx.state.align; + ctx.set_dirs(Gen::new(main, cross)); + let dirs = ctx.state.dirs; + let align = ctx.state.align; - ctx.start_content_group(); + ctx.start_content_group(); + if let Some(body) = &body { + body.exec(ctx); + } + let children = ctx.end_content_group(); - if let Some(body) = args.find::(ctx) { - body.eval(ctx); - } + let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit }; + let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some())); + let fixed = NodeFixed { + width, + height, + child: NodeStack { dirs, align, expand, children }.into(), + }; - let children = ctx.end_content_group(); + if let Some(color) = color { + ctx.push(NodeBackground { + fill: Fill::Color(color), + child: fixed.into(), + }); + } else { + ctx.push(fixed); + } - let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit }; - let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some())); - - let fixed_node = NodeFixed { - width, - height, - child: NodeStack { dirs, align, expand, children }.into(), - }; - - if let Some(color) = color { - ctx.push(NodeBackground { - fill: Fill::Color(color), - child: fixed_node.into(), - }) - } else { - ctx.push(fixed_node); - } - - ctx.state = snapshot; - Value::None + ctx.state = snapshot; + }) } impl_type! { @@ -253,19 +254,19 @@ pub fn v(ctx: &mut EvalContext, args: &mut Args) -> Value { fn spacing(ctx: &mut EvalContext, args: &mut Args, axis: SpecAxis) -> Value { let spacing: Option = args.require(ctx, "spacing"); - if let Some(linear) = spacing { - let amount = linear.resolve(ctx.state.font.font_size()); - let spacing = NodeSpacing { amount, softness: Softness::Hard }; - if axis == ctx.state.dirs.main.axis() { - ctx.end_par_group(); - ctx.push(spacing); - ctx.start_par_group(); - } else { - ctx.push(spacing); + Value::template(move |ctx| { + if let Some(linear) = spacing { + let amount = linear.resolve(ctx.state.font.font_size()); + let spacing = NodeSpacing { amount, softness: Softness::Hard }; + if axis == ctx.state.dirs.main.axis() { + ctx.end_par_group(); + ctx.push(spacing); + ctx.start_par_group(); + } else { + ctx.push(spacing); + } } - } - - Value::None + }) } /// `page`: Configure pages. @@ -275,88 +276,103 @@ fn spacing(ctx: &mut EvalContext, args: &mut Args, axis: SpecAxis) -> Value { /// full list of all paper names. /// /// # Named arguments -/// - Width of the page: `width`, of type `length`. -/// - Height of the page: `height`, of type `length`. -/// - Margins for all sides: `margins`, of type `linear` relative to sides. -/// - Left margin: `left`, of type `linear` relative to width. -/// - Right margin: `right`, of type `linear` relative to width. -/// - Top margin: `top`, of type `linear` relative to height. -/// - Bottom margin: `bottom`, of type `linear` relative to height. -/// - Flip width and height: `flip`, of type `bool`. +/// - Width of the page: `width`, of type `length`. +/// - Height of the page: `height`, of type `length`. +/// - Margins for all sides: `margins`, of type `linear` relative to sides. +/// - Left margin: `left`, of type `linear` relative to width. +/// - Right margin: `right`, of type `linear` relative to width. +/// - Top margin: `top`, of type `linear` relative to height. +/// - Bottom margin: `bottom`, of type `linear` relative to height. +/// - Flip width and height: `flip`, of type `bool`. +/// - Main layouting direction: `main-dir`, of type `direction`. +/// - Cross layouting direction: `cross-dir`, of type `direction`. pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value { - let snapshot = ctx.state.clone(); + let paper = args.find::>(ctx).and_then(|name| { + Paper::from_name(&name.v).or_else(|| { + ctx.diag(error!(name.span, "invalid paper name")); + None + }) + }); + let width = args.get(ctx, "width"); + let height = args.get(ctx, "height"); + let margins = args.get(ctx, "margins"); + let left = args.get(ctx, "left"); + let top = args.get(ctx, "top"); + let right = args.get(ctx, "right"); + let bottom = args.get(ctx, "bottom"); + let flip = args.get(ctx, "flip"); + let main = args.get(ctx, "main-dir"); + let cross = args.get(ctx, "cross-dir"); + let body = args.find::(ctx); - if let Some(name) = args.find::>(ctx) { - if let Some(paper) = Paper::from_name(&name.v) { + Value::template(move |ctx| { + let snapshot = ctx.state.clone(); + + if let Some(paper) = paper { ctx.state.page.class = paper.class; ctx.state.page.size = paper.size(); ctx.state.page.expand = Spec::uniform(Expansion::Fill); - } else { - ctx.diag(error!(name.span, "invalid paper name")); } - } - if let Some(width) = args.get(ctx, "width") { - ctx.state.page.class = PaperClass::Custom; - ctx.state.page.size.width = width; - ctx.state.page.expand.horizontal = Expansion::Fill; - } + if let Some(width) = width { + ctx.state.page.class = PaperClass::Custom; + ctx.state.page.size.width = width; + ctx.state.page.expand.horizontal = Expansion::Fill; + } - if let Some(height) = args.get(ctx, "height") { - ctx.state.page.class = PaperClass::Custom; - ctx.state.page.size.height = height; - ctx.state.page.expand.vertical = Expansion::Fill; - } + if let Some(height) = height { + ctx.state.page.class = PaperClass::Custom; + ctx.state.page.size.height = height; + ctx.state.page.expand.vertical = Expansion::Fill; + } - if let Some(margins) = args.get(ctx, "margins") { - ctx.state.page.margins = Sides::uniform(Some(margins)); - } + if let Some(margins) = margins { + ctx.state.page.margins = Sides::uniform(Some(margins)); + } - if let Some(left) = args.get(ctx, "left") { - ctx.state.page.margins.left = Some(left); - } + if let Some(left) = left { + ctx.state.page.margins.left = Some(left); + } - if let Some(top) = args.get(ctx, "top") { - ctx.state.page.margins.top = Some(top); - } + if let Some(top) = top { + ctx.state.page.margins.top = Some(top); + } - if let Some(right) = args.get(ctx, "right") { - ctx.state.page.margins.right = Some(right); - } + if let Some(right) = right { + ctx.state.page.margins.right = Some(right); + } - if let Some(bottom) = args.get(ctx, "bottom") { - ctx.state.page.margins.bottom = Some(bottom); - } + if let Some(bottom) = bottom { + ctx.state.page.margins.bottom = Some(bottom); + } - if args.get(ctx, "flip").unwrap_or(false) { - let page = &mut ctx.state.page; - std::mem::swap(&mut page.size.width, &mut page.size.height); - std::mem::swap(&mut page.expand.horizontal, &mut page.expand.vertical); - } + if flip.unwrap_or(false) { + let page = &mut ctx.state.page; + std::mem::swap(&mut page.size.width, &mut page.size.height); + std::mem::swap(&mut page.expand.horizontal, &mut page.expand.vertical); + } - let main = args.get(ctx, "main-dir"); - let cross = args.get(ctx, "cross-dir"); - ctx.set_dirs(Gen::new(main, cross)); + ctx.set_dirs(Gen::new(main, cross)); - let mut softness = ctx.end_page_group(|_| false); - if let Some(body) = args.find::(ctx) { - // TODO: Restrict body to a single page? - ctx.start_page_group(Softness::Hard); - body.eval(ctx); - ctx.end_page_group(|s| s == Softness::Hard); - softness = Softness::Soft; - ctx.state = snapshot; - } + let mut softness = ctx.end_page_group(|_| false); + if let Some(body) = &body { + // TODO: Restrict body to a single page? + ctx.start_page_group(Softness::Hard); + body.exec(ctx); + ctx.end_page_group(|s| s == Softness::Hard); + softness = Softness::Soft; + ctx.state = snapshot; + } - ctx.start_page_group(softness); - - Value::None + ctx.start_page_group(softness); + }) } /// `pagebreak`: Start a new page. -pub fn pagebreak(ctx: &mut EvalContext, _: &mut Args) -> Value { - ctx.end_page_group(|_| true); - ctx.start_page_group(Softness::Hard); - Value::None +pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> Value { + Value::template(move |ctx| { + ctx.end_page_group(|_| true); + ctx.start_page_group(Softness::Hard); + }) } diff --git a/src/library/style.rs b/src/library/style.rs index 670104d68..23bd5298e 100644 --- a/src/library/style.rs +++ b/src/library/style.rs @@ -55,68 +55,71 @@ use crate::prelude::*; /// - `extra-expanded` /// - `ultra-expanded` pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value { - let snapshot = ctx.state.clone(); - - if let Some(linear) = args.find::(ctx) { - if linear.rel.is_zero() { - ctx.state.font.size = linear.abs; - ctx.state.font.scale = Relative::ONE.into(); - } else { - ctx.state.font.scale = linear; - } - } - + let size = args.find::(ctx); let list: Vec<_> = args.filter::(ctx).map(|f| f.to_string()).collect(); - if !list.is_empty() { - let families = ctx.state.font.families_mut(); - families.list = list; - families.flatten(); - } + let style = args.get(ctx, "style"); + let weight = args.get(ctx, "weight"); + let stretch = args.get(ctx, "stretch"); + let serif = args.get(ctx, "serif"); + let sans_serif = args.get(ctx, "sans-serif"); + let monospace = args.get(ctx, "monospace"); + let body = args.find::(ctx); - if let Some(style) = args.get(ctx, "style") { - ctx.state.font.variant.style = style; - } + Value::template(move |ctx| { + let snapshot = ctx.state.clone(); - if let Some(weight) = args.get(ctx, "weight") { - ctx.state.font.variant.weight = weight; - } + if let Some(linear) = size { + if linear.rel.is_zero() { + ctx.state.font.size = linear.abs; + ctx.state.font.scale = Relative::ONE.into(); + } else { + ctx.state.font.scale = linear; + } + } - if let Some(stretch) = args.get(ctx, "stretch") { - ctx.state.font.variant.stretch = stretch; - } - - for variant in FontFamily::VARIANTS { - if let Some(FontFamilies(list)) = args.get(ctx, variant.as_str()) { - let strings = list.into_iter().map(|f| f.to_string()).collect(); + if !list.is_empty() { let families = ctx.state.font.families_mut(); - families.update_class_list(variant.to_string(), strings); + families.list = list.clone(); families.flatten(); } - } - if let Some(body) = args.find::(ctx) { - body.eval(ctx); - ctx.state = snapshot; - } + if let Some(style) = style { + ctx.state.font.variant.style = style; + } - Value::None + if let Some(weight) = weight { + ctx.state.font.variant.weight = weight; + } + + if let Some(stretch) = stretch { + ctx.state.font.variant.stretch = stretch; + } + + for (variant, arg) in &[ + (FontFamily::Serif, &serif), + (FontFamily::SansSerif, &sans_serif), + (FontFamily::Monospace, &monospace), + ] { + if let Some(FontFamilies(list)) = arg { + let strings = list.into_iter().map(|f| f.to_string()).collect(); + let families = ctx.state.font.families_mut(); + families.update_class_list(variant.to_string(), strings); + families.flatten(); + } + } + + if let Some(body) = &body { + body.exec(ctx); + ctx.state = snapshot; + } + }) } /// A list of font families. #[derive(Debug, Clone, PartialEq)] struct FontFamilies(Vec); -impl_type! { - FontFamilies: "font family or array of font families", - Value::Str(string) => Self(vec![FontFamily::Named(string.to_lowercase())]), - Value::Array(values) => Self(values - .into_iter() - .filter_map(|v| v.cast().ok()) - .collect() - ), - #(family: FontFamily) => Self(vec![family]), -} - +/// A single font family. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub(crate) enum FontFamily { Serif, @@ -126,9 +129,6 @@ pub(crate) enum FontFamily { } impl FontFamily { - pub const VARIANTS: &'static [Self] = - &[Self::Serif, Self::SansSerif, Self::Monospace]; - pub fn as_str(&self) -> &str { match self { Self::Serif => "serif", @@ -145,6 +145,17 @@ impl Display for FontFamily { } } +impl_type! { + FontFamilies: "font family or array of font families", + Value::Str(string) => Self(vec![FontFamily::Named(string.to_lowercase())]), + Value::Array(values) => Self(values + .into_iter() + .filter_map(|v| v.cast().ok()) + .collect() + ), + #(family: FontFamily) => Self(vec![family]), +} + impl_type! { FontFamily: "font family", Value::Str(string) => Self::Named(string.to_lowercase()) diff --git a/src/main.rs b/src/main.rs index 07eb673af..d9a4f0961 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use fontdock::fs::FsIndex; use typst::diag::{Feedback, Pass}; use typst::env::{Env, ResourceLoader}; -use typst::eval::State; +use typst::exec::State; use typst::export::pdf; use typst::font::FsIndexExt; use typst::library; @@ -51,7 +51,7 @@ fn main() -> anyhow::Result<()> { let Pass { output: frames, feedback: Feedback { mut diags, .. }, - } = typeset(&src, &mut env, &scope, state); + } = typeset(&mut env, &src, &scope, state); if !diags.is_empty() { diags.sort(); diff --git a/src/parse/collection.rs b/src/parse/collection.rs index 95ca98470..162a8bd5b 100644 --- a/src/parse/collection.rs +++ b/src/parse/collection.rs @@ -1,8 +1,10 @@ use super::*; /// Parse the arguments to a function call. -pub fn arguments(p: &mut Parser) -> ExprArgs { - collection(p, vec![]) +pub fn args(p: &mut Parser) -> ExprArgs { + let start = p.start(); + let items = collection(p, vec![]); + ExprArgs { span: p.span_from(start), items } } /// Parse a parenthesized group, which can be either of: @@ -16,8 +18,8 @@ pub fn parenthesized(p: &mut Parser) -> Expr { } else { collection(p, State::Unknown) }; - p.end_group(); - state.into_expr() + let span = p.end_group(); + state.into_expr(span) } /// Parse a collection. @@ -25,7 +27,7 @@ fn collection(p: &mut Parser, mut collection: T) -> T { let mut missing_coma = None; while !p.eof() { - if let Some(arg) = p.span_if(argument) { + if let Some(arg) = argument(p) { collection.push_arg(p, arg); if let Some(pos) = missing_coma.take() { @@ -36,7 +38,7 @@ fn collection(p: &mut Parser, mut collection: T) -> T { break; } - let behind = p.last_end(); + let behind = p.end(); if p.eat_if(Token::Comma) { collection.push_comma(); } else { @@ -50,14 +52,12 @@ fn collection(p: &mut Parser, mut collection: T) -> T { /// Parse an expression or a named pair. fn argument(p: &mut Parser) -> Option { - let first = p.span_if(expr)?; + let first = expr(p)?; if p.eat_if(Token::Colon) { - if let Expr::Ident(ident) = first.v { - let name = ident.with_span(first.span); - let expr = p.span_if(expr)?; - Some(Argument::Named(Named { name, expr })) + if let Expr::Ident(name) = first { + Some(Argument::Named(Named { name, expr: expr(p)? })) } else { - p.diag(error!(first.span, "expected identifier")); + p.diag(error!(first.span(), "expected identifier")); expr(p); None } @@ -68,13 +68,13 @@ fn argument(p: &mut Parser) -> Option { /// Abstraction for comma-separated list of expression / named pairs. trait Collection { - fn push_arg(&mut self, p: &mut Parser, arg: Spanned); + fn push_arg(&mut self, p: &mut Parser, arg: Argument); fn push_comma(&mut self) {} } -impl Collection for ExprArgs { - fn push_arg(&mut self, _: &mut Parser, arg: Spanned) { - self.push(arg.v); +impl Collection for Vec { + fn push_arg(&mut self, _: &mut Parser, arg: Argument) { + self.push(arg); } } @@ -82,38 +82,38 @@ impl Collection for ExprArgs { #[derive(Debug)] enum State { Unknown, - Expr(Spanned), - Array(ExprArray), - Dict(ExprDict), + Expr(Expr), + Array(Vec), + Dict(Vec), } impl State { - fn into_expr(self) -> Expr { + fn into_expr(self, span: Span) -> Expr { match self { - Self::Unknown => Expr::Array(vec![]), - Self::Expr(expr) => Expr::Group(Box::new(expr)), - Self::Array(array) => Expr::Array(array), - Self::Dict(dict) => Expr::Dict(dict), + Self::Unknown => Expr::Array(ExprArray { span, items: vec![] }), + Self::Expr(expr) => Expr::Group(ExprGroup { span, expr: Box::new(expr) }), + Self::Array(items) => Expr::Array(ExprArray { span, items }), + Self::Dict(items) => Expr::Dict(ExprDict { span, items }), } } } impl Collection for State { - fn push_arg(&mut self, p: &mut Parser, arg: Spanned) { + fn push_arg(&mut self, p: &mut Parser, arg: Argument) { match self { - Self::Unknown => match arg.v { + Self::Unknown => match arg { Argument::Pos(expr) => *self = Self::Expr(expr), Argument::Named(named) => *self = Self::Dict(vec![named]), }, - Self::Expr(prev) => match arg.v { + Self::Expr(prev) => match arg { Argument::Pos(expr) => *self = Self::Array(vec![take(prev), expr]), Argument::Named(_) => diag(p, arg), }, - Self::Array(array) => match arg.v { + Self::Array(array) => match arg { Argument::Pos(expr) => array.push(expr), Argument::Named(_) => diag(p, arg), }, - Self::Dict(dict) => match arg.v { + Self::Dict(dict) => match arg { Argument::Pos(_) => diag(p, arg), Argument::Named(named) => dict.push(named), }, @@ -127,13 +127,16 @@ impl Collection for State { } } -fn take(expr: &mut Spanned) -> Spanned { +fn take(expr: &mut Expr) -> Expr { // Replace with anything, it's overwritten anyway. - std::mem::replace(expr, Spanned::zero(Expr::Bool(false))) + std::mem::replace( + expr, + Expr::Lit(Lit { span: Span::ZERO, kind: LitKind::None }), + ) } -fn diag(p: &mut Parser, arg: Spanned) { - p.diag(error!(arg.span, "{}", match arg.v { +fn diag(p: &mut Parser, arg: Argument) { + p.diag(error!(arg.span(), "{}", match arg { Argument::Pos(_) => "expected named pair, found expression", Argument::Named(_) => "expected expression, found named pair", })); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 3fd2cca52..2c34d7b82 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -13,10 +13,11 @@ pub use resolve::*; pub use scanner::*; pub use tokens::*; +use std::rc::Rc; + use crate::diag::Pass; use crate::syntax::*; - -use collection::{arguments, parenthesized}; +use collection::{args, parenthesized}; /// Parse a string of source code. pub fn parse(src: &str) -> Pass { @@ -31,8 +32,8 @@ fn tree(p: &mut Parser) -> Tree { let mut at_start = true; let mut tree = vec![]; while !p.eof() { - if let Some(node) = p.span_if(|p| node(p, &mut at_start)) { - if !matches!(node.v, Node::Parbreak | Node::Space) { + if let Some(node) = node(p, &mut at_start) { + if !matches!(node, Node::Parbreak | Node::Space) { at_start = false; } tree.push(node); @@ -78,12 +79,12 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { p.start_group(group, TokenMode::Code); let expr = primary(p); if stmt && expr.is_some() && !p.eof() { - p.expected_at("semicolon or line break", p.last_end()); + p.expected_at("semicolon or line break", p.end()); } p.end_group(); // Uneat spaces we might have eaten eagerly. - p.jump(p.last_end()); + p.jump(p.end()); return expr.map(Node::Expr); } @@ -123,28 +124,24 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { /// Parse a heading. fn heading(p: &mut Parser) -> Node { + let start = p.start(); + p.assert(&[Token::Eq]); + // Count depth. - let mut level = p.span(|p| { - p.assert(&[Token::Eq]); + let mut level: usize = 0; + while p.eat_if(Token::Eq) { + level += 1; + } - let mut level = 0u8; - while p.eat_if(Token::Eq) { - level = level.saturating_add(1); - } - level - }); - - if level.v > 5 { - p.diag(warning!(level.span, "should not exceed depth 6")); - level.v = 5; + if level > 5 { + p.diag(warning!(start .. p.end(), "should not exceed depth 6")); + level = 5; } // Parse the heading contents. let mut contents = vec![]; while p.check(|t| !matches!(t, Token::Space(n) if n >= 1)) { - if let Some(node) = p.span_if(|p| node(p, &mut false)) { - contents.push(node); - } + contents.extend(node(p, &mut false)); } Node::Heading(NodeHeading { level, contents }) @@ -152,7 +149,7 @@ fn heading(p: &mut Parser) -> Node { /// Handle a raw block. fn raw(p: &mut Parser, token: TokenRaw) -> Node { - let raw = resolve::resolve_raw(token.text, token.backticks); + let raw = resolve::resolve_raw(token.text, token.backticks, p.start()); if !token.terminated { p.diag(error!(p.peek_span().end, "expected backtick(s)")); } @@ -183,10 +180,9 @@ fn bracket_call(p: &mut Parser) -> Option { // One header is guaranteed, but there may be more (through chaining). let mut outer = vec![]; - let mut inner = p.span_if(bracket_subheader); - + let mut inner = bracket_subheader(p); while p.eat_if(Token::Pipe) { - if let Some(new) = p.span_if(bracket_subheader) { + if let Some(new) = bracket_subheader(p) { outer.extend(inner); inner = Some(new); } @@ -194,49 +190,44 @@ fn bracket_call(p: &mut Parser) -> Option { p.end_group(); - let body = if p.peek() == Some(Token::LeftBracket) { - Some(p.span(|p| Expr::Template(bracket_body(p)))) - } else { - None + let body = match p.peek() { + Some(Token::LeftBracket) => Some(bracket_body(p)), + _ => None, }; let mut inner = inner?; if let Some(body) = body { - inner.span.expand(body.span); - inner.v.args.v.push(Argument::Pos(body)); + inner.span.expand(body.span()); + inner.args.items.push(Argument::Pos(body)); } while let Some(mut top) = outer.pop() { - let span = inner.span; - let node = inner.map(|c| Node::Expr(Expr::Call(c))); - let expr = Expr::Template(vec![node]).with_span(span); - top.v.args.v.push(Argument::Pos(expr)); + top.args.items.push(Argument::Pos(Expr::Call(inner))); inner = top; } - Some(Expr::Call(inner.v)) + Some(Expr::Call(inner)) } /// Parse one subheader of a bracketed function call. fn bracket_subheader(p: &mut Parser) -> Option { p.start_group(Group::Subheader, TokenMode::Code); - - let name = p.span_if(ident); - let args = p.span(arguments); - p.end_group(); - + let name = ident(p); + let args = args(p); + let span = p.end_group(); Some(ExprCall { - callee: Box::new(name?.map(Expr::Ident)), + span, + callee: Box::new(Expr::Ident(name?)), args, }) } /// Parse the body of a bracketed function call. -fn bracket_body(p: &mut Parser) -> Tree { +fn bracket_body(p: &mut Parser) -> Expr { p.start_group(Group::Bracket, TokenMode::Markup); - let tree = tree(p); - p.end_group(); - tree + let tree = Rc::new(tree(p)); + let span = p.end_group(); + Expr::Template(ExprTemplate { span, tree }) } /// Parse an expression. @@ -246,15 +237,14 @@ fn expr(p: &mut Parser) -> Option { /// Parse an expression with operators having at least the minimum precedence. fn expr_with(p: &mut Parser, min_prec: usize) -> Option { - let mut lhs = match p.span_if(|p| p.eat_map(UnOp::from_token)) { + let start = p.start(); + let mut lhs = match p.eat_map(UnOp::from_token) { Some(op) => { - let prec = op.v.precedence(); - let expr = p.span_if(|p| expr_with(p, prec))?; - let span = op.span.join(expr.span); - let unary = Expr::Unary(ExprUnary { op, expr: Box::new(expr) }); - unary.with_span(span) + let prec = op.precedence(); + let expr = Box::new(expr_with(p, prec)?); + Expr::Unary(ExprUnary { span: p.span_from(start), op, expr }) } - None => p.span_if(primary)?, + None => primary(p)?, }; loop { @@ -268,95 +258,87 @@ fn expr_with(p: &mut Parser, min_prec: usize) -> Option { break; } + p.eat(); match op.associativity() { Associativity::Left => prec += 1, Associativity::Right => {} } - let op = op.with_span(p.peek_span()); - p.eat(); - - let rhs = match p.span_if(|p| expr_with(p, prec)) { + let rhs = match expr_with(p, prec) { Some(rhs) => Box::new(rhs), None => break, }; - let span = lhs.span.join(rhs.span); - let binary = Expr::Binary(ExprBinary { lhs: Box::new(lhs), op, rhs }); - lhs = binary.with_span(span); + let span = lhs.span().join(rhs.span()); + lhs = Expr::Binary(ExprBinary { span, lhs: Box::new(lhs), op, rhs }); } - Some(lhs.v) + Some(lhs) } /// Parse a primary expression. fn primary(p: &mut Parser) -> Option { - let expr = match p.peek() { - // Basic values. - Some(Token::None) => Expr::None, - Some(Token::Bool(b)) => Expr::Bool(b), - Some(Token::Int(i)) => Expr::Int(i), - Some(Token::Float(f)) => Expr::Float(f), - Some(Token::Length(val, unit)) => Expr::Length(val, unit), - Some(Token::Angle(val, unit)) => Expr::Angle(val, unit), - Some(Token::Percent(p)) => Expr::Percent(p), - Some(Token::Color(color)) => Expr::Color(color), - Some(Token::Str(token)) => Expr::Str(string(p, token)), + if let Some(expr) = literal(p) { + return Some(expr); + } + match p.peek() { // Function or identifier. - Some(Token::Ident(id)) => { - p.eat(); - let ident = Ident(id.into()); + Some(Token::Ident(string)) => { + let ident = Ident { + span: p.eat_span(), + string: string.into(), + }; if p.peek() == Some(Token::LeftParen) { - let name = ident.with_span(p.peek_span()); - return Some(paren_call(p, name)); + Some(paren_call(p, ident)) } else { - return Some(Expr::Ident(ident)); + Some(Expr::Ident(ident)) } } // Keywords. - Some(Token::Let) => return expr_let(p), - Some(Token::If) => return expr_if(p), - Some(Token::For) => return expr_for(p), + Some(Token::Let) => expr_let(p), + Some(Token::If) => expr_if(p), + Some(Token::For) => expr_for(p), - // Block. - Some(Token::LeftBrace) => { - return block(p, true); - } - - // Template. - Some(Token::LeftBracket) => { - return Some(template(p)); - } - - // Function template. - Some(Token::HashBracket) => { - let call = p.span_if(bracket_call)?.map(Node::Expr); - return Some(Expr::Template(vec![call])); - } - - // Array, dictionary or parenthesized expression. - Some(Token::LeftParen) => { - return Some(parenthesized(p)); - } + // Structures. + Some(Token::LeftBrace) => block(p, true), + Some(Token::LeftBracket) => Some(template(p)), + Some(Token::HashBracket) => bracket_call(p), + Some(Token::LeftParen) => Some(parenthesized(p)), // Nothing. _ => { p.expected("expression"); - return None; + None } + } +} + +/// Parse a literal. +fn literal(p: &mut Parser) -> Option { + let kind = match p.peek()? { + // Basic values. + Token::None => LitKind::None, + Token::Bool(b) => LitKind::Bool(b), + Token::Int(i) => LitKind::Int(i), + Token::Float(f) => LitKind::Float(f), + Token::Length(val, unit) => LitKind::Length(val, unit), + Token::Angle(val, unit) => LitKind::Angle(val, unit), + Token::Percent(p) => LitKind::Percent(p), + Token::Color(color) => LitKind::Color(color), + Token::Str(token) => LitKind::Str(string(p, token)), + _ => return None, }; - p.eat(); - Some(expr) + Some(Expr::Lit(Lit { span: p.eat_span(), kind })) } // Parse a template value: `[...]`. fn template(p: &mut Parser) -> Expr { p.start_group(Group::Bracket, TokenMode::Markup); - let tree = tree(p); - p.end_group(); - Expr::Template(tree) + let tree = Rc::new(tree(p)); + let span = p.end_group(); + Expr::Template(ExprTemplate { span, tree }) } /// Parse a block expression: `{...}`. @@ -365,26 +347,27 @@ fn block(p: &mut Parser, scopes: bool) -> Option { let mut exprs = vec![]; while !p.eof() { p.start_group(Group::Stmt, TokenMode::Code); - if let Some(expr) = p.span_if(expr) { + if let Some(expr) = expr(p) { exprs.push(expr); if !p.eof() { - p.expected_at("semicolon or line break", p.last_end()); + p.expected_at("semicolon or line break", p.end()); } } p.end_group(); p.skip_white(); } - p.end_group(); - Some(Expr::Block(ExprBlock { exprs, scopes })) + let span = p.end_group(); + Some(Expr::Block(ExprBlock { span, exprs, scoping: scopes })) } /// Parse a parenthesized function call. -fn paren_call(p: &mut Parser, name: Spanned) -> Expr { +fn paren_call(p: &mut Parser, name: Ident) -> Expr { p.start_group(Group::Paren, TokenMode::Code); - let args = p.span(arguments); + let args = args(p); p.end_group(); Expr::Call(ExprCall { - callee: Box::new(name.map(Expr::Ident)), + span: p.span_from(name.span.start), + callee: Box::new(Expr::Ident(name)), args, }) } @@ -394,22 +377,26 @@ fn string(p: &mut Parser, token: TokenStr) -> String { if !token.terminated { p.expected_at("quote", p.peek_span().end); } - resolve::resolve_string(token.string) } /// Parse a let expression. fn expr_let(p: &mut Parser) -> Option { + let start = p.start(); p.assert(&[Token::Let]); let mut expr_let = None; - if let Some(pat) = p.span_if(ident) { + if let Some(binding) = ident(p) { let mut init = None; if p.eat_if(Token::Eq) { - init = p.span_if(expr); + init = expr(p); } - expr_let = Some(Expr::Let(ExprLet { pat, init: init.map(Box::new) })) + expr_let = Some(Expr::Let(ExprLet { + span: p.span_from(start), + binding, + init: init.map(Box::new), + })) } expr_let @@ -417,17 +404,19 @@ fn expr_let(p: &mut Parser) -> Option { /// Parse an if expresion. fn expr_if(p: &mut Parser) -> Option { + let start = p.start(); p.assert(&[Token::If]); let mut expr_if = None; - if let Some(condition) = p.span_if(expr) { - if let Some(if_body) = p.span_if(body) { + if let Some(condition) = expr(p) { + if let Some(if_body) = body(p) { let mut else_body = None; if p.eat_if(Token::Else) { - else_body = p.span_if(body); + else_body = body(p); } expr_if = Some(Expr::If(ExprIf { + span: p.span_from(start), condition: Box::new(condition), if_body: Box::new(if_body), else_body: else_body.map(Box::new), @@ -440,15 +429,17 @@ fn expr_if(p: &mut Parser) -> Option { /// Parse a for expression. fn expr_for(p: &mut Parser) -> Option { + let start = p.start(); p.assert(&[Token::For]); let mut expr_for = None; - if let Some(pat) = p.span_if(for_pattern) { + if let Some(pattern) = for_pattern(p) { if p.expect(Token::In) { - if let Some(iter) = p.span_if(expr) { - if let Some(body) = p.span_if(body) { + if let Some(iter) = expr(p) { + if let Some(body) = body(p) { expr_for = Some(Expr::For(ExprFor { - pat, + span: p.span_from(start), + pattern, iter: Box::new(iter), body: Box::new(body), })); @@ -473,15 +464,14 @@ fn for_pattern(p: &mut Parser) -> Option { /// Parse an identifier. fn ident(p: &mut Parser) -> Option { - match p.peek() { - Some(Token::Ident(id)) => { - p.eat(); - Some(Ident(id.into())) - } - _ => { - p.expected("identifier"); - None - } + if let Some(Token::Ident(string)) = p.peek() { + Some(Ident { + span: p.eat_span(), + string: string.to_string(), + }) + } else { + p.expected("identifier"); + None } } @@ -491,7 +481,7 @@ fn body(p: &mut Parser) -> Option { Some(Token::LeftBracket) => Some(template(p)), Some(Token::LeftBrace) => block(p, true), _ => { - p.expected_at("body", p.last_end()); + p.expected_at("body", p.end()); None } } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 986a36b09..a64e39dd5 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter}; use super::{Scanner, TokenMode, Tokens}; use crate::diag::Diag; use crate::diag::{Deco, Feedback}; -use crate::syntax::{Pos, Span, Spanned, Token, WithSpan}; +use crate::syntax::{Pos, Span, Spanned, Token}; /// A convenient token-based parser. pub struct Parser<'s> { @@ -18,14 +18,43 @@ pub struct Parser<'s> { next_start: Pos, /// The end position of the last (non-whitespace if in code mode) token. last_end: Pos, - /// The stack of modes we were in. - modes: Vec, /// The stack of open groups. - groups: Vec, + groups: Vec, /// Accumulated feedback. feedback: Feedback, } +/// A logical group of tokens, e.g. `[...]`. +struct GroupEntry { + /// The start position of the group. Used by `Parser::end_group` to return + /// The group's full span. + start: Pos, + /// The kind of group this is. This decides which tokens will end the group. + /// For example, a [`GroupKind::Paren`] will be ended by + /// [`Token::RightParen`]. + kind: Group, + /// The mode the parser was in _before_ the group started. + prev_mode: TokenMode, +} + +/// A group, confined by optional start and end delimiters. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Group { + /// A parenthesized group: `(...)`. + Paren, + /// A bracketed group: `[...]`. + Bracket, + /// A curly-braced group: `{...}`. + Brace, + /// A group ended by a chained subheader or a closing bracket: + /// `... >>`, `...]`. + Subheader, + /// A group ended by a semicolon or a line break: `;`, `\n`. + Stmt, + /// A group for a single expression. Not ended by something specific. + Expr, +} + impl<'s> Parser<'s> { /// Create a new parser for the source string. pub fn new(src: &'s str) -> Self { @@ -37,7 +66,6 @@ impl<'s> Parser<'s> { peeked: next, next_start: Pos::ZERO, last_end: Pos::ZERO, - modes: vec![], groups: vec![], feedback: Feedback::new(), } @@ -97,14 +125,17 @@ impl<'s> Parser<'s> { /// /// # Panics /// This panics if the next token does not start the given group. - pub fn start_group(&mut self, group: Group, mode: TokenMode) { - self.modes.push(self.tokens.mode()); - self.tokens.set_mode(mode); + pub fn start_group(&mut self, kind: Group, mode: TokenMode) { + self.groups.push(GroupEntry { + start: self.next_start, + kind, + prev_mode: self.tokens.mode(), + }); - self.groups.push(group); + self.tokens.set_mode(mode); self.repeek(); - match group { + match kind { Group::Paren => self.assert(&[Token::LeftParen]), Group::Bracket => self.assert(&[Token::HashBracket, Token::LeftBracket]), Group::Brace => self.assert(&[Token::LeftBrace]), @@ -118,15 +149,16 @@ impl<'s> Parser<'s> { /// /// # Panics /// This panics if no group was started. - pub fn end_group(&mut self) { + pub fn end_group(&mut self) -> Span { let prev_mode = self.tokens.mode(); - self.tokens.set_mode(self.modes.pop().expect("no pushed mode")); - let group = self.groups.pop().expect("no started group"); + self.tokens.set_mode(group.prev_mode); self.repeek(); + let mut rescan = self.tokens.mode() != prev_mode; + // Eat the end delimiter if there is one. - if let Some((end, required)) = match group { + if let Some((end, required)) = match group.kind { Group::Paren => Some((Token::RightParen, true)), Group::Bracket => Some((Token::RightBracket, true)), Group::Brace => Some((Token::RightBrace, true)), @@ -137,37 +169,19 @@ impl<'s> Parser<'s> { if self.next == Some(end) { // Bump the delimeter and return. No need to rescan in this case. self.bump(); - return; + rescan = false; } else if required { self.diag(error!(self.next_start, "expected {}", end.name())); } } // Rescan the peeked token if the mode changed. - if self.tokens.mode() != prev_mode { + if rescan { self.tokens.jump(self.last_end); self.bump(); } - } - /// Execute `f` and return the result alongside the span of everything `f` - /// ate. Excludes leading and trailing whitespace in code mode. - pub fn span(&mut self, f: F) -> Spanned - where - F: FnOnce(&mut Self) -> T, - { - let start = self.next_start; - let output = f(self); - let end = self.last_end; - output.with_span(start .. end) - } - - /// A version of [`span`](Self::span) that works better with options. - pub fn span_if(&mut self, f: F) -> Option> - where - F: FnOnce(&mut Self) -> Option, - { - self.span(f).transpose() + Span::new(group.start, self.last_end) } /// Consume the next token. @@ -200,6 +214,13 @@ impl<'s> Parser<'s> { mapped } + /// Eat the next token and return its span. + pub fn eat_span(&mut self) -> Span { + let start = self.next_start; + self.eat(); + Span::new(start, self.last_end) + } + /// Consume the next token if it is the given one and produce an error if /// not. pub fn expect(&mut self, t: Token) -> bool { @@ -264,17 +285,22 @@ impl<'s> Parser<'s> { } /// The position at which the next token starts. - pub fn next_start(&self) -> Pos { + pub fn start(&self) -> Pos { self.next_start } /// The position at which the last token ended. /// /// Refers to the end of the last _non-whitespace_ token in code mode. - pub fn last_end(&self) -> Pos { + pub fn end(&self) -> Pos { self.last_end } + /// The span from + pub fn span_from(&self, start: Pos) -> Span { + Span::new(start, self.last_end) + } + /// Jump to a position in the source string. pub fn jump(&mut self, pos: Pos) { self.tokens.jump(pos); @@ -325,13 +351,14 @@ impl<'s> Parser<'s> { None => return, }; + let inside = |x| self.kinds().any(|k| k == x); match token { - Token::RightParen if self.groups.contains(&Group::Paren) => {} - Token::RightBracket if self.groups.contains(&Group::Bracket) => {} - Token::RightBrace if self.groups.contains(&Group::Brace) => {} - Token::Semicolon if self.groups.contains(&Group::Stmt) => {} + Token::RightParen if inside(Group::Paren) => {} + Token::RightBracket if inside(Group::Bracket) => {} + Token::RightBrace if inside(Group::Brace) => {} + Token::Semicolon if inside(Group::Stmt) => {} + Token::Pipe if inside(Group::Subheader) => {} Token::Space(n) if n >= 1 && self.in_line_group() => {} - Token::Pipe if self.groups.contains(&Group::Subheader) => {} _ => return, } @@ -340,7 +367,15 @@ impl<'s> Parser<'s> { /// Whether the active group ends at a newline. fn in_line_group(&self) -> bool { - matches!(self.groups.last(), Some(&Group::Stmt) | Some(&Group::Expr)) + matches!( + self.kinds().next_back(), + Some(Group::Stmt) | Some(Group::Expr) + ) + } + + /// The outer groups. + fn kinds(&self) -> impl DoubleEndedIterator + '_ { + self.groups.iter().map(|group| group.kind) } } @@ -350,21 +385,3 @@ impl Debug for Parser<'_> { write!(f, "Parser({}|{})", s.eaten(), s.rest()) } } - -/// A group, confined by optional start and end delimiters. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum Group { - /// A parenthesized group: `(...)`. - Paren, - /// A bracketed group: `[...]`. - Bracket, - /// A curly-braced group: `{...}`. - Brace, - /// A group ended by a chained subheader or a closing bracket: - /// `... >>`, `...]`. - Subheader, - /// A group ended by a semicolon or a line break: `;`, `\n`. - Stmt, - /// A group for a single expression. Not ended by something specific. - Expr, -} diff --git a/src/parse/resolve.rs b/src/parse/resolve.rs index a5e831daa..4592acbcb 100644 --- a/src/parse/resolve.rs +++ b/src/parse/resolve.rs @@ -1,5 +1,5 @@ use super::{is_newline, Scanner}; -use crate::syntax::{Ident, NodeRaw}; +use crate::syntax::{Ident, NodeRaw, Offset, Pos}; /// Resolve all escape sequences in a string. pub fn resolve_string(string: &str) -> String { @@ -47,12 +47,12 @@ pub fn resolve_hex(sequence: &str) -> Option { } /// Resolve the language tag and trims the raw text. -pub fn resolve_raw(text: &str, backticks: usize) -> NodeRaw { +pub fn resolve_raw(text: &str, backticks: usize, start: Pos) -> NodeRaw { if backticks > 1 { let (tag, inner) = split_at_lang_tag(text); let (lines, had_newline) = trim_and_split_raw(inner); NodeRaw { - lang: Ident::new(tag), + lang: Ident::new(tag, start .. start.offset(tag.len())), lines, block: had_newline, } @@ -125,6 +125,7 @@ pub fn split_lines(text: &str) -> Vec { #[cfg(test)] #[rustfmt::skip] mod tests { + use crate::syntax::Span; use super::*; #[test] @@ -173,11 +174,11 @@ mod tests { lines: &[&str], block: bool, ) { - assert_eq!(resolve_raw(raw, backticks), NodeRaw { - lang: lang.map(|id| Ident(id.into())), + Span::without_cmp(|| assert_eq!(resolve_raw(raw, backticks, Pos(0)), NodeRaw { + lang: lang.and_then(|id| Ident::new(id, 0)), lines: lines.iter().map(ToString::to_string).collect(), block, - }); + })); } // Just one backtick. diff --git a/src/prelude.rs b/src/prelude.rs index 9856a8377..6bab80e67 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,9 +3,11 @@ pub use crate::diag::{Feedback, Pass}; #[doc(no_inline)] pub use crate::eval::{ - Args, CastResult, Eval, EvalContext, Value, ValueAny, ValueArray, ValueDict, - ValueTemplate, + Args, CastResult, Eval, EvalContext, TemplateAny, TemplateNode, Value, ValueAny, + ValueArray, ValueDict, ValueTemplate, }; +#[doc(no_inline)] +pub use crate::exec::{Exec, ExecContext}; pub use crate::geom::*; #[doc(no_inline)] pub use crate::layout::Node; diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 3160e0e40..f431ba8dc 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use super::*; use crate::color::RgbaColor; use crate::geom::{AngularUnit, LengthUnit}; @@ -5,29 +7,10 @@ use crate::geom::{AngularUnit, LengthUnit}; /// An expression. #[derive(Debug, Clone, PartialEq)] pub enum Expr { - /// The none literal: `none`. - None, - /// A identifier literal: `left`. + /// A literal. + Lit(Lit), + /// An identifier: `left`. Ident(Ident), - /// A boolean literal: `true`, `false`. - Bool(bool), - /// An integer literal: `120`. - Int(i64), - /// A floating-point literal: `1.2`, `10e-4`. - Float(f64), - /// A length literal: `12pt`, `3cm`. - Length(f64, LengthUnit), - /// An angle literal: `1.5rad`, `90deg`. - Angle(f64, AngularUnit), - /// A percent literal: `50%`. - /// - /// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the - /// corresponding [value](crate::geom::Relative). - Percent(f64), - /// A color literal: `#ffccee`. - Color(RgbaColor), - /// A string literal: `"hello!"`. - Str(String), /// An array expression: `(1, "hi", 12cm)`. Array(ExprArray), /// A dictionary expression: `(color: #f79143, pattern: dashed)`. @@ -52,11 +35,92 @@ pub enum Expr { For(ExprFor), } +impl Expr { + /// The source code location. + pub fn span(&self) -> Span { + match self { + Self::Lit(v) => v.span, + Self::Ident(v) => v.span, + Self::Array(v) => v.span, + Self::Dict(v) => v.span, + Self::Template(v) => v.span, + Self::Group(v) => v.span, + Self::Block(v) => v.span, + Self::Unary(v) => v.span, + Self::Binary(v) => v.span, + Self::Call(v) => v.span, + Self::Let(v) => v.span, + Self::If(v) => v.span, + Self::For(v) => v.span, + } + } +} + impl Pretty for Expr { fn pretty(&self, p: &mut Printer) { match self { - Self::None => p.push_str("none"), + Self::Lit(v) => v.pretty(p), Self::Ident(v) => v.pretty(p), + Self::Array(v) => v.pretty(p), + Self::Dict(v) => v.pretty(p), + Self::Template(v) => v.pretty(p), + Self::Group(v) => v.pretty(p), + Self::Block(v) => v.pretty(p), + Self::Unary(v) => v.pretty(p), + Self::Binary(v) => v.pretty(p), + Self::Call(v) => v.pretty(p), + Self::Let(v) => v.pretty(p), + Self::If(v) => v.pretty(p), + Self::For(v) => v.pretty(p), + } + } +} + +/// A literal. +#[derive(Debug, Clone, PartialEq)] +pub struct Lit { + /// The source code location. + pub span: Span, + /// The kind of literal. + pub kind: LitKind, +} + +impl Pretty for Lit { + fn pretty(&self, p: &mut Printer) { + self.kind.pretty(p); + } +} + +/// A kind of literal. +#[derive(Debug, Clone, PartialEq)] +pub enum LitKind { + /// The none literal: `none`. + None, + /// A boolean literal: `true`, `false`. + Bool(bool), + /// An integer literal: `120`. + Int(i64), + /// A floating-point literal: `1.2`, `10e-4`. + Float(f64), + /// A length literal: `12pt`, `3cm`. + Length(f64, LengthUnit), + /// An angle literal: `1.5rad`, `90deg`. + Angle(f64, AngularUnit), + /// A percent literal: `50%`. + /// + /// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the + /// corresponding [value](crate::geom::Relative). + Percent(f64), + /// A color literal: `#ffccee`. + Color(RgbaColor), + /// A string literal: `"hello!"`. + Str(String), +} + +impl Pretty for LitKind { + fn pretty(&self, p: &mut Printer) { + match self { + Self::None => p.push_str("none"), Self::Bool(v) => v.pretty(p), Self::Int(v) => v.pretty(p), Self::Float(v) => v.pretty(p), @@ -71,33 +135,24 @@ impl Pretty for Expr { } Self::Color(v) => v.pretty(p), Self::Str(v) => v.pretty(p), - Self::Array(v) => v.pretty(p), - Self::Dict(v) => v.pretty(p), - Self::Template(v) => pretty_template(v, p), - Self::Group(v) => { - p.push('('); - v.v.pretty(p); - p.push(')'); - } - Self::Block(v) => v.pretty(p), - Self::Unary(v) => v.pretty(p), - Self::Binary(v) => v.pretty(p), - Self::Call(v) => v.pretty(p), - Self::Let(v) => v.pretty(p), - Self::If(v) => v.pretty(p), - Self::For(v) => v.pretty(p), } } } /// An array expression: `(1, "hi", 12cm)`. -pub type ExprArray = SpanVec; +#[derive(Debug, Clone, PartialEq)] +pub struct ExprArray { + /// The source code location. + pub span: Span, + /// The entries of the array. + pub items: Vec, +} impl Pretty for ExprArray { fn pretty(&self, p: &mut Printer) { p.push('('); - p.join(self, ", ", |item, p| item.v.pretty(p)); - if self.len() == 1 { + p.join(&self.items, ", ", |item, p| item.pretty(p)); + if self.items.len() == 1 { p.push(','); } p.push(')'); @@ -105,15 +160,21 @@ impl Pretty for ExprArray { } /// A dictionary expression: `(color: #f79143, pattern: dashed)`. -pub type ExprDict = Vec; +#[derive(Debug, Clone, PartialEq)] +pub struct ExprDict { + /// The source code location. + pub span: Span, + /// The named dictionary entries. + pub items: Vec, +} impl Pretty for ExprDict { fn pretty(&self, p: &mut Printer) { p.push('('); - if self.is_empty() { + if self.items.is_empty() { p.push(':'); } else { - p.join(self, ", ", |named, p| named.pretty(p)); + p.join(&self.items, ", ", |named, p| named.pretty(p)); } p.push(')'); } @@ -123,43 +184,73 @@ impl Pretty for ExprDict { #[derive(Debug, Clone, PartialEq)] pub struct Named { /// The name: `pattern`. - pub name: Spanned, + pub name: Ident, /// The right-hand side of the pair: `dashed`. - pub expr: Spanned, + pub expr: Expr, +} + +impl Named { + /// The source code location. + pub fn span(&self) -> Span { + self.name.span.join(self.expr.span()) + } } impl Pretty for Named { fn pretty(&self, p: &mut Printer) { - self.name.v.pretty(p); + self.name.pretty(p); p.push_str(": "); - self.expr.v.pretty(p); + self.expr.pretty(p); } } /// A template expression: `[*Hi* there!]`. -pub type ExprTemplate = Tree; +#[derive(Debug, Clone, PartialEq)] +pub struct ExprTemplate { + /// The source code location. + pub span: Span, + /// The contents of the template. + pub tree: Rc, +} -/// Pretty print a template. -pub fn pretty_template(template: &ExprTemplate, p: &mut Printer) { - if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = template.as_slice() { - pretty_func_template(call, p, false) - } else { - p.push('['); - template.pretty(p); - p.push(']'); +impl Pretty for ExprTemplate { + fn pretty(&self, p: &mut Printer) { + if let [Node::Expr(Expr::Call(call))] = self.tree.as_slice() { + call.pretty_bracketed(p, false); + } else { + p.push('['); + self.tree.pretty(p); + p.push(']'); + } } } /// A grouped expression: `(1 + 2)`. -pub type ExprGroup = SpanBox; +#[derive(Debug, Clone, PartialEq)] +pub struct ExprGroup { + /// The source code location. + pub span: Span, + /// The wrapped expression. + pub expr: Box, +} + +impl Pretty for ExprGroup { + fn pretty(&self, p: &mut Printer) { + p.push('('); + self.expr.pretty(p); + p.push(')'); + } +} /// A block expression: `{ #let x = 1; x + 2 }`. #[derive(Debug, Clone, PartialEq)] pub struct ExprBlock { + /// The source code location. + pub span: Span, /// The list of expressions contained in the block. - pub exprs: SpanVec, + pub exprs: Vec, /// Whether the block should create a scope. - pub scopes: bool, + pub scoping: bool, } impl Pretty for ExprBlock { @@ -168,7 +259,7 @@ impl Pretty for ExprBlock { if self.exprs.len() > 1 { p.push(' '); } - p.join(&self.exprs, "; ", |expr, p| expr.v.pretty(p)); + p.join(&self.exprs, "; ", |expr, p| expr.pretty(p)); if self.exprs.len() > 1 { p.push(' '); } @@ -179,19 +270,21 @@ impl Pretty for ExprBlock { /// A unary operation: `-x`. #[derive(Debug, Clone, PartialEq)] pub struct ExprUnary { + /// The source code location. + pub span: Span, /// The operator: `-`. - pub op: Spanned, + pub op: UnOp, /// The expression to operator on: `x`. - pub expr: SpanBox, + pub expr: Box, } impl Pretty for ExprUnary { fn pretty(&self, p: &mut Printer) { - self.op.v.pretty(p); - if self.op.v == UnOp::Not { + self.op.pretty(p); + if self.op == UnOp::Not { p.push(' '); } - self.expr.v.pretty(p); + self.expr.pretty(p); } } @@ -244,21 +337,23 @@ impl Pretty for UnOp { /// A binary operation: `a + b`. #[derive(Debug, Clone, PartialEq)] pub struct ExprBinary { + /// The source code location. + pub span: Span, /// The left-hand side of the operation: `a`. - pub lhs: SpanBox, + pub lhs: Box, /// The operator: `+`. - pub op: Spanned, + pub op: BinOp, /// The right-hand side of the operation: `b`. - pub rhs: SpanBox, + pub rhs: Box, } impl Pretty for ExprBinary { fn pretty(&self, p: &mut Printer) { - self.lhs.v.pretty(p); + self.lhs.pretty(p); p.push(' '); - self.op.v.pretty(p); + self.op.pretty(p); p.push(' '); - self.rhs.v.pretty(p); + self.rhs.pretty(p); } } @@ -407,71 +502,83 @@ pub enum Associativity { /// An invocation of a function: `foo(...)`, `#[foo ...]`. #[derive(Debug, Clone, PartialEq)] pub struct ExprCall { + /// The source code location. + pub span: Span, /// The callee of the function. - pub callee: SpanBox, + pub callee: Box, /// The arguments to the function. - pub args: Spanned, + pub args: ExprArgs, } impl Pretty for ExprCall { fn pretty(&self, p: &mut Printer) { - self.callee.v.pretty(p); + self.callee.pretty(p); p.push('('); - self.args.v.pretty(p); + self.args.pretty(p); p.push(')'); } } -/// Pretty print a function template, with body or chaining when possible. -pub fn pretty_func_template(call: &ExprCall, p: &mut Printer, chained: bool) { - if chained { - p.push_str(" | "); - } else { - p.push_str("#["); - } - - // Function name. - call.callee.v.pretty(p); - - // Find out whether this can be written with a body or as a chain. - // - // Example: Transforms "#[v [Hi]]" => "#[v][Hi]". - if let [head @ .., Argument::Pos(Spanned { v: Expr::Template(template), .. })] = - call.args.v.as_slice() - { - // Previous arguments. - if !head.is_empty() { - p.push(' '); - p.join(head, ", ", |item, p| item.pretty(p)); - } - - // Find out whether this can written as a chain. - // - // Example: Transforms "#[v][[f]]" => "#[v | f]". - if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = template.as_slice() { - return pretty_func_template(call, p, true); +impl ExprCall { + /// Pretty print a function template, with body or chaining when possible. + pub fn pretty_bracketed(&self, p: &mut Printer, chained: bool) { + if chained { + p.push_str(" | "); } else { - p.push_str("]["); - template.pretty(p); + p.push_str("#["); } - } else if !call.args.v.is_empty() { - p.push(' '); - call.args.v.pretty(p); - } - // Either end of header or end of body. - p.push(']'); + // Function name. + self.callee.pretty(p); + + let mut write_args = |items: &[Argument]| { + if !items.is_empty() { + p.push(' '); + p.join(items, ", ", |item, p| item.pretty(p)); + } + }; + + match self.args.items.as_slice() { + // This can written as a chain. + // + // Example: Transforms "#[v][[f]]" => "#[v | f]". + [head @ .., Argument::Pos(Expr::Call(call))] => { + write_args(head); + call.pretty_bracketed(p, true); + } + + // This can be written with a body. + // + // Example: Transforms "#[v [Hi]]" => "#[v][Hi]". + [head @ .., Argument::Pos(Expr::Template(template))] => { + write_args(head); + p.push(']'); + template.pretty(p); + } + + items => { + write_args(items); + p.push(']'); + } + } + } } /// The arguments to a function: `12, draw: false`. /// /// In case of a bracketed invocation with a body, the body is _not_ /// included in the span for the sake of clearer error messages. -pub type ExprArgs = Vec; +#[derive(Debug, Clone, PartialEq)] +pub struct ExprArgs { + /// The source code location. + pub span: Span, + /// The positional and named arguments. + pub items: Vec, +} -impl Pretty for Vec { +impl Pretty for ExprArgs { fn pretty(&self, p: &mut Printer) { - p.join(self, ", ", |item, p| item.pretty(p)); + p.join(&self.items, ", ", |item, p| item.pretty(p)); } } @@ -479,15 +586,25 @@ impl Pretty for Vec { #[derive(Debug, Clone, PartialEq)] pub enum Argument { /// A positional arguments. - Pos(Spanned), + Pos(Expr), /// A named argument. Named(Named), } +impl Argument { + /// The source code location. + pub fn span(&self) -> Span { + match self { + Self::Pos(expr) => expr.span(), + Self::Named(named) => named.span(), + } + } +} + impl Pretty for Argument { fn pretty(&self, p: &mut Printer) { match self { - Self::Pos(expr) => expr.v.pretty(p), + Self::Pos(expr) => expr.pretty(p), Self::Named(named) => named.pretty(p), } } @@ -496,19 +613,21 @@ impl Pretty for Argument { /// A let expression: `#let x = 1`. #[derive(Debug, Clone, PartialEq)] pub struct ExprLet { - /// The pattern to assign to. - pub pat: Spanned, + /// The source code location. + pub span: Span, + /// The binding to assign to. + pub binding: Ident, /// The expression the pattern is initialized with. - pub init: Option>, + pub init: Option>, } impl Pretty for ExprLet { fn pretty(&self, p: &mut Printer) { p.push_str("#let "); - self.pat.v.pretty(p); + self.binding.pretty(p); if let Some(init) = &self.init { p.push_str(" = "); - init.v.pretty(p); + init.pretty(p); } } } @@ -516,23 +635,25 @@ impl Pretty for ExprLet { /// An if expression: `#if x { y } #else { z }`. #[derive(Debug, Clone, PartialEq)] pub struct ExprIf { + /// The source code location. + pub span: Span, /// The condition which selects the body to evaluate. - pub condition: SpanBox, + pub condition: Box, /// The expression to evaluate if the condition is true. - pub if_body: SpanBox, + pub if_body: Box, /// The expression to evaluate if the condition is false. - pub else_body: Option>, + pub else_body: Option>, } impl Pretty for ExprIf { fn pretty(&self, p: &mut Printer) { p.push_str("#if "); - self.condition.v.pretty(p); + self.condition.pretty(p); p.push(' '); - self.if_body.v.pretty(p); + self.if_body.pretty(p); if let Some(expr) = &self.else_body { p.push_str(" #else "); - expr.v.pretty(p); + expr.pretty(p); } } } @@ -540,22 +661,24 @@ impl Pretty for ExprIf { /// A for expression: `#for x #in y { z }`. #[derive(Debug, Clone, PartialEq)] pub struct ExprFor { + /// The source code location. + pub span: Span, /// The pattern to assign to. - pub pat: Spanned, + pub pattern: ForPattern, /// The expression to iterate over. - pub iter: SpanBox, + pub iter: Box, /// The expression to evaluate for each iteration. - pub body: SpanBox, + pub body: Box, } impl Pretty for ExprFor { fn pretty(&self, p: &mut Printer) { p.push_str("#for "); - self.pat.v.pretty(p); + self.pattern.pretty(p); p.push_str(" #in "); - self.iter.v.pretty(p); + self.iter.pretty(p); p.push(' '); - self.body.v.pretty(p); + self.body.pretty(p); } } @@ -568,6 +691,16 @@ pub enum ForPattern { KeyValue(Ident, Ident), } +impl ForPattern { + /// The source code location. + pub fn span(&self) -> Span { + match self { + Self::Value(v) => v.span, + Self::KeyValue(k, v) => k.span.join(v.span), + } + } +} + impl Pretty for ForPattern { fn pretty(&self, p: &mut Printer) { match self { diff --git a/src/syntax/ident.rs b/src/syntax/ident.rs index c4cc19bc4..731a27896 100644 --- a/src/syntax/ident.rs +++ b/src/syntax/ident.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use unicode_xid::UnicodeXID; +use super::Span; use crate::pretty::{Pretty, Printer}; /// An Unicode identifier with a few extra permissible characters. @@ -12,13 +13,21 @@ use crate::pretty::{Pretty, Printer}; /// /// [uax31]: http://www.unicode.org/reports/tr31/ #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub struct Ident(pub String); +pub struct Ident { + /// The source code location. + pub span: Span, + /// The identifier string. + pub string: String, +} impl Ident { /// Create a new identifier from a string checking that it is a valid. - pub fn new(ident: impl AsRef + Into) -> Option { - if is_ident(ident.as_ref()) { - Some(Self(ident.into())) + pub fn new( + string: impl AsRef + Into, + span: impl Into, + ) -> Option { + if is_ident(string.as_ref()) { + Some(Self { span: span.into(), string: string.into() }) } else { None } @@ -26,19 +35,13 @@ impl Ident { /// Return a reference to the underlying string. pub fn as_str(&self) -> &str { - self - } -} - -impl Pretty for Ident { - fn pretty(&self, p: &mut Printer) { - p.push_str(self.as_str()); + self.string.as_str() } } impl AsRef for Ident { fn as_ref(&self) -> &str { - self + self.as_str() } } @@ -46,7 +49,13 @@ impl Deref for Ident { type Target = str; fn deref(&self) -> &Self::Target { - self.0.as_str() + self.as_str() + } +} + +impl Pretty for Ident { + fn pretty(&self, p: &mut Printer) { + p.push_str(self.as_str()); } } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 8bb6931ab..a8ed24576 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -16,12 +16,12 @@ pub use token::*; use crate::pretty::{Pretty, Printer}; /// The abstract syntax tree. -pub type Tree = SpanVec; +pub type Tree = Vec; impl Pretty for Tree { fn pretty(&self, p: &mut Printer) { for node in self { - node.v.pretty(p); + node.pretty(p); } } } @@ -133,9 +133,8 @@ mod tests { roundtrip("#[v 1]"); roundtrip("#[v 1, 2][*Ok*]"); roundtrip("#[v 1 | f 2]"); - roundtrip("{#[v]}"); + test("{#[v]}", "{v()}"); test("#[v 1, #[f 2]]", "#[v 1 | f 2]"); - test("#[v 1, 2][#[f 3]]", "#[v 1, 2 | f 3]"); // Keywords. roundtrip("#let x = 1 + 2"); diff --git a/src/syntax/node.rs b/src/syntax/node.rs index b48660682..fe9767a18 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -37,7 +37,7 @@ impl Pretty for Node { Self::Expr(expr) => { if let Expr::Call(call) = expr { // Format function templates appropriately. - pretty_func_template(call, p, false) + call.pretty_bracketed(p, false) } else { expr.pretty(p); } @@ -49,15 +49,15 @@ impl Pretty for Node { /// A section heading: `= Introduction`. #[derive(Debug, Clone, PartialEq)] pub struct NodeHeading { - /// The section depth (numer of equals signs minus 1, capped at 5). - pub level: Spanned, + /// The section depth (numer of equals signs minus 1). + pub level: usize, /// The contents of the heading. pub contents: Tree, } impl Pretty for NodeHeading { fn pretty(&self, p: &mut Printer) { - for _ in 0 ..= self.level.v { + for _ in 0 ..= self.level { p.push('='); } self.contents.pretty(p); diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 5087dffa2..65b1d637d 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -19,14 +19,15 @@ impl WithSpan for T {} /// Span offsetting. pub trait Offset { /// Offset all spans contained in `Self` by the given position. - fn offset(self, by: Pos) -> Self; + fn offset(self, by: impl Into) -> Self; } /// A vector of spanned values of type `T`. pub type SpanVec = Vec>; impl Offset for SpanVec { - fn offset(mut self, by: Pos) -> Self { + fn offset(mut self, by: impl Into) -> Self { + let by = by.into(); for spanned in &mut self { spanned.span = spanned.span.offset(by); } @@ -34,9 +35,6 @@ impl Offset for SpanVec { } } -/// A box of a spanned value of type `T`. -pub type SpanBox = Box>; - /// A value with the span it corresponds to in the source code. #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] @@ -90,7 +88,7 @@ impl Spanned> { } impl Offset for Spanned { - fn offset(self, by: Pos) -> Self { + fn offset(self, by: impl Into) -> Self { self.map_span(|span| span.offset(by)) } } @@ -174,7 +172,8 @@ impl Span { } impl Offset for Span { - fn offset(self, by: Pos) -> Self { + fn offset(self, by: impl Into) -> Self { + let by = by.into(); Self { start: self.start.offset(by), end: self.end.offset(by), @@ -236,8 +235,8 @@ impl Pos { } impl Offset for Pos { - fn offset(self, by: Self) -> Self { - Pos(self.0 + by.0) + fn offset(self, by: impl Into) -> Self { + Pos(self.0 + by.into().0) } } diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 5e69a350a..411e835fe 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -123,7 +123,7 @@ pub enum Token<'s> { /// A percentage: `50%`. /// /// _Note_: `50%` is stored as `50.0` here, as in the corresponding - /// [literal](super::Expr::Percent). + /// [literal](super::LitKind::Percent). Percent(f64), /// A color value: `#20d82a`. Color(RgbaColor), diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs index 3a289eb16..70159d2d8 100644 --- a/src/syntax/visit.rs +++ b/src/syntax/visit.rs @@ -3,17 +3,17 @@ use super::*; macro_rules! visit { - ($(fn $name:ident($v:ident, $item:ident: &$ty:ty) $body:block)*) => { + ($(fn $name:ident($v:ident, $node:ident: &$ty:ty) $body:block)*) => { /// Traverses the syntax tree. pub trait Visit<'ast> { - $(fn $name(&mut self, $item: &'ast $ty) { - $name(self, $item); + $(fn $name(&mut self, $node: &'ast $ty) { + $name(self, $node); })* } $(visit! { @concat!("Walk a node of type [`", stringify!($ty), "`]."), - pub fn $name<'ast, V>($v: &mut V, $item: &'ast $ty) + pub fn $name<'ast, V>($v: &mut V, $node: &'ast $ty) where V: Visit<'ast> + ?Sized $body @@ -27,14 +27,14 @@ macro_rules! visit { } visit! { - fn visit_tree(v, item: &Tree) { - for node in item { - v.visit_node(&node.v); + fn visit_tree(v, node: &Tree) { + for node in node { + v.visit_node(&node); } } - fn visit_node(v, item: &Node) { - match item { + fn visit_node(v, node: &Node) { + match node { Node::Strong => {} Node::Emph => {} Node::Space => {} @@ -47,18 +47,10 @@ visit! { } } - fn visit_expr(v, item: &Expr) { - match item { - Expr::None => {} + fn visit_expr(v, node: &Expr) { + match node { + Expr::Lit(_) => {} Expr::Ident(_) => {} - Expr::Bool(_) => {} - Expr::Int(_) => {} - Expr::Float(_) => {} - Expr::Length(_, _) => {} - Expr::Angle(_, _) => {} - Expr::Percent(_) => {} - Expr::Color(_) => {} - Expr::Str(_) => {} Expr::Array(e) => v.visit_array(e), Expr::Dict(e) => v.visit_dict(e), Expr::Template(e) => v.visit_template(e), @@ -73,75 +65,75 @@ visit! { } } - fn visit_array(v, item: &ExprArray) { - for expr in item { - v.visit_expr(&expr.v); + fn visit_array(v, node: &ExprArray) { + for expr in &node.items { + v.visit_expr(&expr); } } - fn visit_dict(v, item: &ExprDict) { - for named in item { - v.visit_expr(&named.expr.v); + fn visit_dict(v, node: &ExprDict) { + for named in &node.items { + v.visit_expr(&named.expr); } } - fn visit_template(v, item: &ExprTemplate) { - v.visit_tree(item); + fn visit_template(v, node: &ExprTemplate) { + v.visit_tree(&node.tree); } - fn visit_group(v, item: &ExprGroup) { - v.visit_expr(&item.v); + fn visit_group(v, node: &ExprGroup) { + v.visit_expr(&node.expr); } - fn visit_block(v, item: &ExprBlock) { - for expr in &item.exprs { - v.visit_expr(&expr.v); + fn visit_block(v, node: &ExprBlock) { + for expr in &node.exprs { + v.visit_expr(&expr); } } - fn visit_binary(v, item: &ExprBinary) { - v.visit_expr(&item.lhs.v); - v.visit_expr(&item.rhs.v); + fn visit_binary(v, node: &ExprBinary) { + v.visit_expr(&node.lhs); + v.visit_expr(&node.rhs); } - fn visit_unary(v, item: &ExprUnary) { - v.visit_expr(&item.expr.v); + fn visit_unary(v, node: &ExprUnary) { + v.visit_expr(&node.expr); } - fn visit_call(v, item: &ExprCall) { - v.visit_expr(&item.callee.v); - v.visit_args(&item.args.v); + fn visit_call(v, node: &ExprCall) { + v.visit_expr(&node.callee); + v.visit_args(&node.args); } - fn visit_args(v, item: &ExprArgs) { - for arg in item { + fn visit_args(v, node: &ExprArgs) { + for arg in &node.items { v.visit_arg(arg); } } - fn visit_arg(v, item: &Argument) { - match item { - Argument::Pos(expr) => v.visit_expr(&expr.v), - Argument::Named(named) => v.visit_expr(&named.expr.v), + fn visit_arg(v, node: &Argument) { + match node { + Argument::Pos(expr) => v.visit_expr(&expr), + Argument::Named(named) => v.visit_expr(&named.expr), } } - fn visit_let(v, item: &ExprLet) { - if let Some(init) = &item.init { - v.visit_expr(&init.v); + fn visit_let(v, node: &ExprLet) { + if let Some(init) = &node.init { + v.visit_expr(&init); } } - fn visit_if(v, item: &ExprIf) { - v.visit_expr(&item.condition.v); - v.visit_expr(&item.if_body.v); - if let Some(body) = &item.else_body { - v.visit_expr(&body.v); + fn visit_if(v, node: &ExprIf) { + v.visit_expr(&node.condition); + v.visit_expr(&node.if_body); + if let Some(body) = &node.else_body { + v.visit_expr(&body); } } - fn visit_for(v, item: &ExprFor) { - v.visit_expr(&item.iter.v); - v.visit_expr(&item.body.v); + fn visit_for(v, node: &ExprFor) { + v.visit_expr(&node.iter); + v.visit_expr(&node.body); } } diff --git a/tests/typeset.rs b/tests/typeset.rs index 7e7fb4633..c894b2d48 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -16,7 +16,8 @@ use walkdir::WalkDir; use typst::diag::{Diag, Feedback, Level, Pass}; use typst::env::{Env, ImageResource, ResourceLoader}; -use typst::eval::{Args, EvalContext, Scope, State, Value, ValueFunc}; +use typst::eval::{Args, EvalContext, Scope, Value, ValueFunc}; +use typst::exec::State; use typst::export::pdf; use typst::font::FsIndexExt; use typst::geom::{Length, Point, Sides, Size, Spec}; @@ -222,7 +223,7 @@ fn test_part( let Pass { output: mut frames, feedback: Feedback { mut diags, .. }, - } = typeset(&src, env, &scope, state); + } = typeset(env, &src, &scope, state); if !compare_ref { frames.clear();