diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs index f3e417d5c..8c5117f73 100644 --- a/crates/typst-ide/src/analyze.rs +++ b/crates/typst-ide/src/analyze.rs @@ -1,9 +1,10 @@ use comemo::Track; use ecow::{eco_vec, EcoString, EcoVec}; -use typst::eval::{Route, Tracer, Vm}; +use typst::engine::{Engine, Route}; +use typst::eval::{Tracer, Vm}; use typst::foundations::{Label, Scopes, Value}; use typst::introspection::{Introspector, Locator}; -use typst::layout::{Frame, Vt}; +use typst::layout::Frame; use typst::model::BibliographyElem; use typst::syntax::{ast, LinkedNode, Span, SyntaxKind}; use typst::World; @@ -46,7 +47,6 @@ pub fn analyze_expr(world: &dyn World, node: &LinkedNode) -> EcoVec { /// Try to load a module from the current source file. pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option { - let id = source.span().id()?; let source = analyze_expr(world, source).into_iter().next()?; if source.scope().is_some() { return Some(source); @@ -55,15 +55,15 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option { let mut locator = Locator::default(); let introspector = Introspector::default(); let mut tracer = Tracer::new(); - let vt = Vt { + let engine = Engine { world: world.track(), + route: Route::default(), introspector: introspector.track(), locator: &mut locator, tracer: tracer.track_mut(), }; - let route = Route::default(); - let mut vm = Vm::new(vt, route.track(), Some(id), Scopes::new(Some(world.library()))); + let mut vm = Vm::new(engine, Scopes::new(Some(world.library())), Span::detached()); typst::eval::import(&mut vm, source, Span::detached(), true) .ok() .map(Value::Module) diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs index 9791427b0..ce80cc667 100644 --- a/crates/typst-macros/src/elem.rs +++ b/crates/typst-macros/src/elem.rs @@ -1083,7 +1083,7 @@ fn create_construct_impl(element: &Elem) -> TokenStream { quote! { impl #foundations::Construct for #ident { fn construct( - vm: &mut ::typst::eval::Vm, + engine: &mut ::typst::engine::Engine, args: &mut #foundations::Args, ) -> ::typst::diag::SourceResult<#foundations::Content> { #(#pre)* @@ -1115,7 +1115,7 @@ fn create_set_impl(element: &Elem) -> TokenStream { quote! { impl #foundations::Set for #ident { fn set( - vm: &mut ::typst::eval::Vm, + engine: &mut ::typst::engine::Engine, args: &mut #foundations::Args, ) -> ::typst::diag::SourceResult<#foundations::Styles> { let mut styles = #foundations::Styles::new(); diff --git a/crates/typst-macros/src/func.rs b/crates/typst-macros/src/func.rs index 8537ac4f2..953df4282 100644 --- a/crates/typst-macros/src/func.rs +++ b/crates/typst-macros/src/func.rs @@ -36,8 +36,7 @@ struct Func { #[derive(Default)] struct SpecialParams { self_: Option, - vm: bool, - vt: bool, + engine: bool, args: bool, span: bool, } @@ -171,8 +170,7 @@ fn parse_param( }; match ident.to_string().as_str() { - "vm" => special.vm = true, - "vt" => special.vt = true, + "engine" => special.engine = true, "args" => special.args = true, "span" => special.span = true, _ => { @@ -322,13 +320,12 @@ fn create_wrapper_closure(func: &Func) -> TokenStream { .as_ref() .map(bind) .map(|tokens| quote! { #tokens, }); - let vm_ = func.special.vm.then(|| quote! { vm, }); - let vt_ = func.special.vt.then(|| quote! { &mut vm.vt, }); + let vt_ = func.special.engine.then(|| quote! { engine, }); let args_ = func.special.args.then(|| quote! { args, }); let span_ = func.special.span.then(|| quote! { args.span, }); let forwarded = func.params.iter().filter(|param| !param.external).map(bind); quote! { - __typst_func(#self_ #vm_ #vt_ #args_ #span_ #(#forwarded,)*) + __typst_func(#self_ #vt_ #args_ #span_ #(#forwarded,)*) } }; @@ -336,7 +333,7 @@ fn create_wrapper_closure(func: &Func) -> TokenStream { let ident = &func.ident; let parent = func.parent.as_ref().map(|ty| quote! { #ty:: }); quote! { - |vm, args| { + |engine, args| { let __typst_func = #parent #ident; #handlers #finish diff --git a/crates/typst-syntax/src/span.rs b/crates/typst-syntax/src/span.rs index 14e5e2167..8138a3166 100644 --- a/crates/typst-syntax/src/span.rs +++ b/crates/typst-syntax/src/span.rs @@ -2,6 +2,8 @@ use std::fmt::{self, Debug, Formatter}; use std::num::NonZeroU64; use std::ops::Range; +use ecow::EcoString; + use crate::FileId; /// A unique identifier for a syntax node. @@ -80,6 +82,14 @@ impl Span { pub const fn number(self) -> u64 { self.0.get() & ((1 << Self::BITS) - 1) } + + /// Resolve a file location relative to this span's source. + pub fn resolve_path(self, path: &str) -> Result { + let Some(file) = self.id() else { + return Err("cannot access file system from here".into()); + }; + Ok(file.join(path)) + } } /// A value with a span locating it in the source code. diff --git a/crates/typst/src/engine.rs b/crates/typst/src/engine.rs new file mode 100644 index 000000000..189034cff --- /dev/null +++ b/crates/typst/src/engine.rs @@ -0,0 +1,159 @@ +use std::cell::Cell; + +use comemo::{Track, Tracked, TrackedMut, Validate}; + +use crate::diag::SourceResult; +use crate::eval::Tracer; +use crate::introspection::{Introspector, Locator}; +use crate::syntax::FileId; +use crate::World; + +/// The maxmium stack nesting depth. +const MAX_DEPTH: usize = 64; + +/// Holds all data needed during compilation. +pub struct Engine<'a> { + /// The compilation environment. + pub world: Tracked<'a, dyn World + 'a>, + /// Provides access to information about the document. + pub introspector: Tracked<'a, Introspector>, + /// The route the engine took during compilation. This is used to detect + /// cyclic imports and too much nesting. + pub route: Route<'a>, + /// Provides stable identities to elements. + pub locator: &'a mut Locator<'a>, + /// The tracer for inspection of the values an expression produces. + pub tracer: TrackedMut<'a, Tracer>, +} + +impl Engine<'_> { + /// Perform a fallible operation that does not immediately terminate further + /// execution. Instead it produces a delayed error that is only promoted to + /// a fatal one if it remains at the end of the introspection loop. + pub fn delayed(&mut self, f: F) -> T + where + F: FnOnce(&mut Self) -> SourceResult, + T: Default, + { + match f(self) { + Ok(value) => value, + Err(errors) => { + self.tracer.delay(errors); + T::default() + } + } + } +} + +/// The route the engine took during compilation. This is used to detect +/// cyclic imports and too much nesting. +#[derive(Clone)] +pub struct Route<'a> { + // We need to override the constraint's lifetime here so that `Tracked` is + // covariant over the constraint. If it becomes invariant, we're in for a + // world of lifetime pain. + outer: Option as Validate>::Constraint>>, + /// This is set if this route segment was inserted through the start of a + /// module evaluation. + id: Option, + /// This is set whenever we enter a function, nested layout, or are applying + /// a show rule. The length of this segment plus the lengths of all `outer` + /// route segments make up the length of the route. If the length of the + /// route exceeds `MAX_DEPTH`, then we throw a "maximum ... depth exceeded" + /// error. + len: usize, + /// The upper bound we've established for the parent chain length. We don't + /// know the exact length (that would defeat the whole purpose because it + /// would prevent cache reuse of some computation at different, + /// non-exceeding depths). + upper: Cell, +} + +impl<'a> Route<'a> { + /// Create a new, empty route. + pub fn root() -> Self { + Self { id: None, outer: None, len: 0, upper: Cell::new(0) } + } + + /// Insert a new id into the route. + /// + /// You must guarantee that `outer` lives longer than the resulting + /// route is ever used. + pub fn insert(outer: Tracked<'a, Self>, id: FileId) -> Self { + Route { + outer: Some(outer), + id: Some(id), + len: 0, + upper: Cell::new(usize::MAX), + } + } + + /// Extend the route without another id. + pub fn extend(outer: Tracked<'a, Self>) -> Self { + Route { + outer: Some(outer), + id: None, + len: 1, + upper: Cell::new(usize::MAX), + } + } + + /// Start tracking this route. + /// + /// In comparison to [`Track::track`], this method skips this chain link + /// if it does not contribute anything. + pub fn track(&self) -> Tracked<'_, Self> { + match self.outer { + Some(outer) if self.id.is_none() && self.len == 0 => outer, + _ => Track::track(self), + } + } + + /// Increase the nesting depth for this route segment. + pub fn increase(&mut self) { + self.len += 1; + } + + /// Decrease the nesting depth for this route segment. + pub fn decrease(&mut self) { + self.len -= 1; + } + + /// Check whether the nesting depth exceeds the limit. + pub fn exceeding(&self) -> bool { + !self.within(MAX_DEPTH) + } +} + +#[comemo::track] +impl<'a> Route<'a> { + /// Whether the given id is part of the route. + pub fn contains(&self, id: FileId) -> bool { + self.id == Some(id) || self.outer.map_or(false, |outer| outer.contains(id)) + } + + /// Whether the route's depth is less than or equal to the given depth. + pub fn within(&self, depth: usize) -> bool { + if self.upper.get().saturating_add(self.len) <= depth { + return true; + } + + match self.outer { + Some(_) if depth < self.len => false, + Some(outer) => { + let within = outer.within(depth - self.len); + if within && depth < self.upper.get() { + self.upper.set(depth); + } + within + } + None => true, + } + } +} + +impl Default for Route<'_> { + fn default() -> Self { + Self::root() + } +} diff --git a/crates/typst/src/eval/access.rs b/crates/typst/src/eval/access.rs index ff0b75124..99b2f72ed 100644 --- a/crates/typst/src/eval/access.rs +++ b/crates/typst/src/eval/access.rs @@ -31,7 +31,7 @@ impl Access for ast::Ident<'_> { let span = self.span(); let value = vm.scopes.get_mut(&self).at(span)?; if vm.inspected == Some(span) { - vm.vt.tracer.value(value.clone()); + vm.engine.tracer.value(value.clone()); } Ok(value) } diff --git a/crates/typst/src/eval/call.rs b/crates/typst/src/eval/call.rs index 7ccebe858..a57cb1127 100644 --- a/crates/typst/src/eval/call.rs +++ b/crates/typst/src/eval/call.rs @@ -2,13 +2,13 @@ use comemo::{Prehashed, Tracked, TrackedMut}; use ecow::EcoVec; use crate::diag::{bail, error, At, HintedStrResult, SourceResult, Trace, Tracepoint}; +use crate::engine::Engine; use crate::eval::{Access, Eval, FlowEvent, Route, Tracer, Vm}; use crate::foundations::{ call_method_mut, is_mutating_method, Arg, Args, Bytes, Closure, Content, Func, IntoValue, NativeElement, Scope, Scopes, Value, }; use crate::introspection::{Introspector, Locator}; -use crate::layout::Vt; use crate::math::{Accent, AccentElem, LrElem}; use crate::symbols::Symbol; use crate::syntax::ast::{self, AstNode}; @@ -16,23 +16,19 @@ use crate::syntax::{Spanned, SyntaxNode}; use crate::text::TextElem; use crate::World; -/// The maxmium function call depth. -const MAX_CALL_DEPTH: usize = 64; - impl Eval for ast::FuncCall<'_> { type Output = Value; #[tracing::instrument(name = "FuncCall::eval", skip_all)] fn eval(self, vm: &mut Vm) -> SourceResult { let span = self.span(); - if vm.depth >= MAX_CALL_DEPTH { - bail!(span, "maximum function call depth exceeded"); - } - let callee = self.callee(); let in_math = in_math(callee); let callee_span = callee.span(); let args = self.args(); + if vm.engine.route.exceeding() { + bail!(span, "maximum function call depth exceeded"); + } // Try to evaluate as a call to an associated function or field. let (callee, mut args) = if let ast::Expr::FieldAccess(access) = callee { @@ -146,7 +142,7 @@ impl Eval for ast::FuncCall<'_> { let callee = callee.cast::().at(callee_span)?; let point = || Tracepoint::Call(callee.name().map(Into::into)); - let f = || callee.call_vm(vm, args).trace(vm.world(), point, span); + let f = || callee.call(&mut vm.engine, args).trace(vm.world(), point, span); // Stacker is broken on WASM. #[cfg(target_arch = "wasm32")] @@ -229,7 +225,6 @@ impl Eval for ast::Closure<'_> { // Define the closure. let closure = Closure { node: self.to_untyped().clone(), - file: vm.file, defaults, captured, }; @@ -246,11 +241,10 @@ pub(crate) fn call_closure( func: &Func, closure: &Prehashed, world: Tracked, - route: Tracked, introspector: Tracked, + route: Tracked, locator: Tracked, tracer: TrackedMut, - depth: usize, mut args: Args, ) -> SourceResult { let node = closure.node.cast::().unwrap(); @@ -260,13 +254,18 @@ pub(crate) fn call_closure( let mut scopes = Scopes::new(None); scopes.top = closure.captured.clone(); - // Prepare VT. + // Prepare the engine. let mut locator = Locator::chained(locator); - let vt = Vt { world, introspector, locator: &mut locator, tracer }; + let engine = Engine { + world, + introspector, + route: Route::extend(route), + locator: &mut locator, + tracer, + }; // Prepare VM. - let mut vm = Vm::new(vt, route, closure.file, scopes); - vm.depth = depth; + let mut vm = Vm::new(engine, scopes, node.span()); // Provide the closure itself for recursive calls. if let Some(name) = node.name() { @@ -279,6 +278,7 @@ pub(crate) fn call_closure( .children() .filter(|p| matches!(p, ast::Param::Pos(_))) .count(); + let num_pos_args = args.to_pos().len(); let sink_size = num_pos_args.checked_sub(num_pos_params); diff --git a/crates/typst/src/eval/code.rs b/crates/typst/src/eval/code.rs index 1df5876c2..06b2f2263 100644 --- a/crates/typst/src/eval/code.rs +++ b/crates/typst/src/eval/code.rs @@ -40,7 +40,7 @@ fn eval_code<'a>( } let tail = eval_code(vm, exprs)?.display(); - Value::Content(tail.styled_with_recipe(vm, recipe)?) + Value::Content(tail.styled_with_recipe(&mut vm.engine, recipe)?) } _ => expr.eval(vm)?, }; @@ -130,7 +130,7 @@ impl Eval for ast::Expr<'_> { .spanned(span); if vm.inspected == Some(span) { - vm.vt.tracer.value(v.clone()); + vm.engine.tracer.value(v.clone()); } Ok(v) diff --git a/crates/typst/src/eval/import.rs b/crates/typst/src/eval/import.rs index 79daf999c..aa3388a10 100644 --- a/crates/typst/src/eval/import.rs +++ b/crates/typst/src/eval/import.rs @@ -38,7 +38,7 @@ impl Eval for ast::ModuleImport<'_> { if let ast::Expr::Ident(ident) = self.source() { if ident.as_str() == new_name.as_str() { // Warn on `import x as x` - vm.vt.tracer.warn(warning!( + vm.engine.tracer.warn(warning!( new_name.span(), "unnecessary import rename to same name", )); @@ -73,7 +73,7 @@ impl Eval for ast::ModuleImport<'_> { if renamed_item.original_name().as_str() == renamed_item.new_name().as_str() { - vm.vt.tracer.warn(warning!( + vm.engine.tracer.warn(warning!( renamed_item.new_name().span(), "unnecessary import rename to same name", )); @@ -145,27 +145,37 @@ fn import_package(vm: &mut Vm, spec: PackageSpec, span: Span) -> SourceResult SourceResult { // Load the source file. let world = vm.world(); - let id = vm.resolve_path(path).at(span)?; + let id = span.resolve_path(path).at(span)?; let source = world.source(id).at(span)?; // Prevent cyclic importing. - if vm.route.contains(source.id()) { + if vm.engine.route.contains(source.id()) { bail!(span, "cyclic import"); } // Evaluate the file. let point = || Tracepoint::Import; - eval(world, vm.route, TrackedMut::reborrow_mut(&mut vm.vt.tracer), &source) - .trace(world, point, span) + eval( + world, + vm.engine.route.track(), + TrackedMut::reborrow_mut(&mut vm.engine.tracer), + &source, + ) + .trace(world, point, span) } /// A parsed package manifest. diff --git a/crates/typst/src/eval/markup.rs b/crates/typst/src/eval/markup.rs index 16ea9eef1..a40b978e0 100644 --- a/crates/typst/src/eval/markup.rs +++ b/crates/typst/src/eval/markup.rs @@ -43,7 +43,7 @@ fn eval_markup<'a>( } let tail = eval_markup(vm, exprs)?; - seq.push(tail.styled_with_recipe(vm, recipe)?) + seq.push(tail.styled_with_recipe(&mut vm.engine, recipe)?) } expr => match expr.eval(vm)? { Value::Label(label) => { @@ -139,11 +139,11 @@ impl Eval for ast::Strong<'_> { fn eval(self, vm: &mut Vm) -> SourceResult { let body = self.body(); if body.exprs().next().is_none() { - vm.vt - .tracer - .warn(warning!(self.span(), "no text within stars").with_hint( + vm.engine.tracer.warn( + warning!(self.span(), "no text within stars").with_hint( "using multiple consecutive stars (e.g. **) has no additional effect", - )); + ), + ); } Ok(StrongElem::new(body.eval(vm)?).pack()) @@ -157,7 +157,7 @@ impl Eval for ast::Emph<'_> { fn eval(self, vm: &mut Vm) -> SourceResult { let body = self.body(); if body.exprs().next().is_none() { - vm.vt + vm.engine .tracer .warn(warning!(self.span(), "no text within underscores").with_hint( "using multiple consecutive underscores (e.g. __) has no additional effect" diff --git a/crates/typst/src/eval/mod.rs b/crates/typst/src/eval/mod.rs index eaba69e0c..9de4f1b73 100644 --- a/crates/typst/src/eval/mod.rs +++ b/crates/typst/src/eval/mod.rs @@ -26,9 +26,9 @@ pub(crate) use self::flow::*; use comemo::{Track, Tracked, TrackedMut}; use crate::diag::{bail, SourceResult}; +use crate::engine::{Engine, Route}; use crate::foundations::{Cast, Module, NativeElement, Scope, Scopes, Value}; use crate::introspection::{Introspector, Locator}; -use crate::layout::Vt; use crate::math::EquationElem; use crate::syntax::{ast, parse, parse_code, parse_math, Source, Span}; use crate::World; @@ -48,22 +48,23 @@ pub fn eval( panic!("Tried to cyclicly evaluate {:?}", id.vpath()); } - // Prepare VT. + // Prepare the engine. let mut locator = Locator::new(); let introspector = Introspector::default(); - let vt = Vt { + let engine = Engine { world, + route: Route::insert(route, id), introspector: introspector.track(), locator: &mut locator, tracer, }; // Prepare VM. - let route = Route::insert(route, id); - let scopes = Scopes::new(Some(world.library())); - let mut vm = Vm::new(vt, route.track(), Some(id), scopes); - let root = source.root(); + let scopes = Scopes::new(Some(world.library())); + let mut vm = Vm::new(engine, scopes, root.span()); + + // Check for well-formedness unless we are in trace mode. let errors = root.errors(); if !errors.is_empty() && vm.inspected.is_none() { return Err(errors.into_iter().map(Into::into).collect()); @@ -108,26 +109,27 @@ pub fn eval_string( root.synthesize(span); + // Check for well-formedness. let errors = root.errors(); if !errors.is_empty() { return Err(errors.into_iter().map(Into::into).collect()); } - // Prepare VT. + // Prepare the engine. let mut tracer = Tracer::new(); let mut locator = Locator::new(); let introspector = Introspector::default(); - let vt = Vt { + let engine = Engine { world, introspector: introspector.track(), + route: Route::default(), locator: &mut locator, tracer: tracer.track_mut(), }; // Prepare VM. - let route = Route::default(); let scopes = Scopes::new(Some(world.library())); - let mut vm = Vm::new(vt, route.track(), None, scopes); + let mut vm = Vm::new(engine, scopes, root.span()); vm.scopes.scopes.push(scope); // Evaluate the code. diff --git a/crates/typst/src/eval/rules.rs b/crates/typst/src/eval/rules.rs index c85b747b3..e34fe5cd1 100644 --- a/crates/typst/src/eval/rules.rs +++ b/crates/typst/src/eval/rules.rs @@ -24,7 +24,7 @@ impl Eval for ast::SetRule<'_> { }) .at(target.span())?; let args = self.args().eval(vm)?; - Ok(target.set(vm, args)?.spanned(self.span())) + Ok(target.set(&mut vm.engine, args)?.spanned(self.span())) } } diff --git a/crates/typst/src/eval/vm.rs b/crates/typst/src/eval/vm.rs index c34c1070d..8cedb906b 100644 --- a/crates/typst/src/eval/vm.rs +++ b/crates/typst/src/eval/vm.rs @@ -1,74 +1,37 @@ -use comemo::{Track, Tracked, Validate}; +use comemo::Tracked; -use crate::diag::{bail, StrResult}; +use crate::engine::Engine; use crate::eval::FlowEvent; use crate::foundations::{IntoValue, Scopes}; -use crate::layout::Vt; use crate::syntax::ast::{self, AstNode}; -use crate::syntax::{FileId, Span}; +use crate::syntax::Span; use crate::World; /// A virtual machine. /// -/// Holds the state needed to [evaluate](crate::eval::eval()) Typst sources. A new -/// virtual machine is created for each module evaluation and function call. +/// Holds the state needed to [evaluate](crate::eval::eval()) Typst sources. A +/// new virtual machine is created for each module evaluation and function call. pub struct Vm<'a> { /// The underlying virtual typesetter. - pub(crate) vt: Vt<'a>, - /// The route of source ids the VM took to reach its current location. - pub(crate) route: Tracked<'a, Route<'a>>, - /// The id of the currently evaluated file. - pub(crate) file: Option, + pub(crate) engine: Engine<'a>, /// A control flow event that is currently happening. pub(crate) flow: Option, /// The stack of scopes. pub(crate) scopes: Scopes<'a>, - /// The current call depth. - pub(crate) depth: usize, /// A span that is currently under inspection. pub(crate) inspected: Option, } impl<'a> Vm<'a> { /// Create a new virtual machine. - pub fn new( - vt: Vt<'a>, - route: Tracked<'a, Route>, - file: Option, - scopes: Scopes<'a>, - ) -> Self { - let inspected = file.and_then(|id| vt.tracer.inspected(id)); - Self { - vt, - route, - file, - flow: None, - scopes, - depth: 0, - inspected, - } + pub fn new(engine: Engine<'a>, scopes: Scopes<'a>, target: Span) -> Self { + let inspected = target.id().and_then(|id| engine.tracer.inspected(id)); + Self { engine, flow: None, scopes, inspected } } /// Access the underlying world. pub fn world(&self) -> Tracked<'a, dyn World + 'a> { - self.vt.world - } - - /// The id of the currently evaluated file. - /// - /// Returns `None` if the VM is in a detached context, e.g. when evaluating - /// a user-provided string. - pub fn file(&self) -> Option { - self.file - } - - /// Resolve a path relative to the currently evaluated file. - pub fn resolve_path(&self, path: &str) -> StrResult { - let Some(file) = self.file else { - bail!("cannot access file system from here"); - }; - - Ok(file.join(path)) + self.engine.world } /// Define a variable in the current scope. @@ -76,52 +39,8 @@ impl<'a> Vm<'a> { pub fn define(&mut self, var: ast::Ident, value: impl IntoValue) { let value = value.into_value(); if self.inspected == Some(var.span()) { - self.vt.tracer.value(value.clone()); + self.engine.tracer.value(value.clone()); } self.scopes.top.define(var.get().clone(), value); } } - -/// A route of source ids. -#[derive(Default)] -pub struct Route<'a> { - // We need to override the constraint's lifetime here so that `Tracked` is - // covariant over the constraint. If it becomes invariant, we're in for a - // world of lifetime pain. - outer: Option as Validate>::Constraint>>, - id: Option, -} - -impl<'a> Route<'a> { - /// Create a new route with just one entry. - pub fn new(id: Option) -> Self { - Self { id, outer: None } - } - - /// Insert a new id into the route. - /// - /// You must guarantee that `outer` lives longer than the resulting - /// route is ever used. - pub fn insert(outer: Tracked<'a, Self>, id: FileId) -> Self { - Route { outer: Some(outer), id: Some(id) } - } - - /// Start tracking this locator. - /// - /// In comparison to [`Track::track`], this method skips this chain link - /// if it does not contribute anything. - pub fn track(&self) -> Tracked<'_, Self> { - match self.outer { - Some(outer) if self.id.is_none() => outer, - _ => Track::track(self), - } - } -} - -#[comemo::track] -impl<'a> Route<'a> { - /// Whether the given id is part of the route. - pub fn contains(&self, id: FileId) -> bool { - self.id == Some(id) || self.outer.map_or(false, |outer| outer.contains(id)) - } -} diff --git a/crates/typst/src/foundations/args.rs b/crates/typst/src/foundations/args.rs index af5d07b16..e11fe2bb9 100644 --- a/crates/typst/src/foundations/args.rs +++ b/crates/typst/src/foundations/args.rs @@ -320,3 +320,26 @@ impl PartialEq for Arg { self.name == other.name && self.value.v == other.value.v } } + +/// Things that can be used as arguments. +pub trait IntoArgs { + /// Convert into arguments, attaching the `fallback` span in case `Self` + /// doesn't have a span. + fn into_args(self, fallback: Span) -> Args; +} + +impl IntoArgs for Args { + fn into_args(self, _: Span) -> Args { + self + } +} + +impl IntoArgs for I +where + I: IntoIterator, + T: IntoValue, +{ + fn into_args(self, fallback: Span) -> Args { + Args::new(fallback, self) + } +} diff --git a/crates/typst/src/foundations/array.rs b/crates/typst/src/foundations/array.rs index 47afa9e25..8f2e9149e 100644 --- a/crates/typst/src/foundations/array.rs +++ b/crates/typst/src/foundations/array.rs @@ -8,7 +8,8 @@ use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use crate::diag::{At, SourceResult, StrResult}; -use crate::eval::{ops, Vm}; +use crate::engine::Engine; +use crate::eval::ops; use crate::foundations::{ cast, func, repr, scope, ty, Args, Bytes, CastInfo, FromValue, Func, IntoValue, Reflect, Repr, Value, Version, @@ -297,14 +298,17 @@ impl Array { #[func] pub fn find( &self, - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// The function to apply to each item. Must return a boolean. searcher: Func, ) -> SourceResult> { for item in self.iter() { - let args = Args::new(searcher.span(), [item.clone()]); - if searcher.call_vm(vm, args)?.cast::().at(searcher.span())? { + if searcher + .call(engine, [item.clone()])? + .cast::() + .at(searcher.span())? + { return Ok(Some(item.clone())); } } @@ -316,14 +320,17 @@ impl Array { #[func] pub fn position( &self, - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// The function to apply to each item. Must return a boolean. searcher: Func, ) -> SourceResult> { for (i, item) in self.iter().enumerate() { - let args = Args::new(searcher.span(), [item.clone()]); - if searcher.call_vm(vm, args)?.cast::().at(searcher.span())? { + if searcher + .call(engine, [item.clone()])? + .cast::() + .at(searcher.span())? + { return Ok(Some(i as i64)); } } @@ -388,15 +395,14 @@ impl Array { #[func] pub fn filter( &self, - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// The function to apply to each item. Must return a boolean. test: Func, ) -> SourceResult { let mut kept = EcoVec::new(); for item in self.iter() { - let args = Args::new(test.span(), [item.clone()]); - if test.call_vm(vm, args)?.cast::().at(test.span())? { + if test.call(engine, [item.clone()])?.cast::().at(test.span())? { kept.push(item.clone()) } } @@ -408,17 +414,12 @@ impl Array { #[func] pub fn map( self, - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// The function to apply to each item. mapper: Func, ) -> SourceResult { - self.into_iter() - .map(|item| { - let args = Args::new(mapper.span(), [item]); - mapper.call_vm(vm, args) - }) - .collect() + self.into_iter().map(|item| mapper.call(engine, [item])).collect() } /// Returns a new array with the values alongside their indices. @@ -518,8 +519,8 @@ impl Array { #[func] pub fn fold( self, - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// The initial value to start with. init: Value, /// The folding function. Must have two parameters: One for the @@ -528,8 +529,7 @@ impl Array { ) -> SourceResult { let mut acc = init; for item in self { - let args = Args::new(folder.span(), [acc, item]); - acc = folder.call_vm(vm, args)?; + acc = folder.call(engine, [acc, item])?; } Ok(acc) } @@ -579,14 +579,13 @@ impl Array { #[func] pub fn any( self, - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// The function to apply to each item. Must return a boolean. test: Func, ) -> SourceResult { for item in self { - let args = Args::new(test.span(), [item]); - if test.call_vm(vm, args)?.cast::().at(test.span())? { + if test.call(engine, [item])?.cast::().at(test.span())? { return Ok(true); } } @@ -598,14 +597,13 @@ impl Array { #[func] pub fn all( self, - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// The function to apply to each item. Must return a boolean. test: Func, ) -> SourceResult { for item in self { - let args = Args::new(test.span(), [item]); - if !test.call_vm(vm, args)?.cast::().at(test.span())? { + if !test.call(engine, [item])?.cast::().at(test.span())? { return Ok(false); } } @@ -714,8 +712,8 @@ impl Array { #[func] pub fn sorted( self, - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// The callsite span. span: Span, /// If given, applies this function to the elements in the array to @@ -728,7 +726,7 @@ impl Array { let mut key_of = |x: Value| match &key { // NOTE: We are relying on `comemo`'s memoization of function // evaluation to not excessively reevaluate the `key`. - Some(f) => f.call_vm(vm, Args::new(f.span(), [x])), + Some(f) => f.call(engine, [x]), None => Ok(x), }; vec.make_mut().sort_by(|a, b| { @@ -762,8 +760,8 @@ impl Array { #[func(title = "Deduplicate")] pub fn dedup( self, - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// If given, applies this function to the elements in the array to /// determine the keys to deduplicate by. #[named] @@ -773,7 +771,7 @@ impl Array { let mut key_of = |x: Value| match &key { // NOTE: We are relying on `comemo`'s memoization of function // evaluation to not excessively reevaluate the `key`. - Some(f) => f.call_vm(vm, Args::new(f.span(), [x])), + Some(f) => f.call(engine, [x]), None => Ok(x), }; diff --git a/crates/typst/src/foundations/cast.rs b/crates/typst/src/foundations/cast.rs index fbd5ab145..e3b7dd014 100644 --- a/crates/typst/src/foundations/cast.rs +++ b/crates/typst/src/foundations/cast.rs @@ -12,6 +12,7 @@ use crate::diag::{At, SourceResult, StrResult}; use crate::foundations::{repr, Repr, Type, Value}; use crate::syntax::{Span, Spanned}; +#[rustfmt::skip] #[doc(inline)] pub use typst_macros::{cast, Cast}; diff --git a/crates/typst/src/foundations/content.rs b/crates/typst/src/foundations/content.rs index 7a4026293..111b33ea6 100644 --- a/crates/typst/src/foundations/content.rs +++ b/crates/typst/src/foundations/content.rs @@ -10,7 +10,7 @@ use serde::{Serialize, Serializer}; use smallvec::smallvec; use crate::diag::{SourceResult, StrResult}; -use crate::eval::Vm; +use crate::engine::Engine; use crate::foundations::{ elem, func, scope, ty, Dict, Element, FromValue, Guard, IntoValue, Label, NativeElement, Recipe, Repr, Selector, Str, Style, Styles, Value, @@ -299,16 +299,19 @@ impl Content { /// Access the child and styles. pub fn to_styled(&self) -> Option<(&Content, &Styles)> { let styled = self.to::()?; - let child = styled.child(); let styles = styled.styles(); Some((child, styles)) } /// Style this content with a recipe, eagerly applying it if possible. - pub fn styled_with_recipe(self, vm: &mut Vm, recipe: Recipe) -> SourceResult { + pub fn styled_with_recipe( + self, + engine: &mut Engine, + recipe: Recipe, + ) -> SourceResult { if recipe.selector.is_none() { - recipe.apply_vm(vm, self) + recipe.apply(engine, self) } else { Ok(self.styled(recipe)) } diff --git a/crates/typst/src/foundations/datetime.rs b/crates/typst/src/foundations/datetime.rs index f50daf72a..78290c994 100644 --- a/crates/typst/src/foundations/datetime.rs +++ b/crates/typst/src/foundations/datetime.rs @@ -8,7 +8,7 @@ use time::macros::format_description; use time::{format_description, Month, PrimitiveDateTime}; use crate::diag::{bail, StrResult}; -use crate::eval::Vm; +use crate::engine::Engine; use crate::foundations::{ cast, func, repr, scope, ty, Dict, Duration, Repr, Smart, Str, Value, }; @@ -296,16 +296,15 @@ impl Datetime { /// ``` #[func] pub fn today( - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// An offset to apply to the current UTC date. If set to `{auto}`, the /// offset will be the local offset. #[named] #[default] offset: Smart, ) -> StrResult { - Ok(vm - .vt + Ok(engine .world .today(offset.as_custom()) .ok_or("unable to get the current date")?) diff --git a/crates/typst/src/foundations/element.rs b/crates/typst/src/foundations/element.rs index 64b47b789..8e4d159a0 100644 --- a/crates/typst/src/foundations/element.rs +++ b/crates/typst/src/foundations/element.rs @@ -10,13 +10,12 @@ use once_cell::sync::Lazy; use smallvec::SmallVec; use crate::diag::{SourceResult, StrResult}; -use crate::eval::Vm; +use crate::engine::Engine; use crate::foundations::{ cast, Args, Content, Dict, Func, Label, ParamInfo, Repr, Scope, Selector, StyleChain, Styles, Value, }; use crate::introspection::Location; -use crate::layout::Vt; use crate::syntax::Span; use crate::text::{Lang, Region}; use crate::util::Static; @@ -66,13 +65,17 @@ impl Element { } /// Construct an instance of this element. - pub fn construct(self, vm: &mut Vm, args: &mut Args) -> SourceResult { - (self.0.construct)(vm, args) + pub fn construct( + self, + engine: &mut Engine, + args: &mut Args, + ) -> SourceResult { + (self.0.construct)(engine, args) } /// Execute the set rule for the element and return the resulting style map. - pub fn set(self, vm: &mut Vm, mut args: Args) -> SourceResult { - let styles = (self.0.set)(vm, &mut args)?; + pub fn set(self, engine: &mut Engine, mut args: Args) -> SourceResult { + let styles = (self.0.set)(engine, &mut args)?; args.finish()?; Ok(styles) } @@ -275,7 +278,7 @@ pub trait Construct { /// /// This is passed only the arguments that remain after execution of the /// element's set rule. - fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult + fn construct(engine: &mut Engine, args: &mut Args) -> SourceResult where Self: Sized; } @@ -283,7 +286,7 @@ pub trait Construct { /// An element's set rule. pub trait Set { /// Parse relevant arguments into style properties for this element. - fn set(vm: &mut Vm, args: &mut Args) -> SourceResult + fn set(engine: &mut Engine, args: &mut Args) -> SourceResult where Self: Sized; } @@ -295,8 +298,8 @@ pub struct NativeElementData { pub title: &'static str, pub docs: &'static str, pub keywords: &'static [&'static str], - pub construct: fn(&mut Vm, &mut Args) -> SourceResult, - pub set: fn(&mut Vm, &mut Args) -> SourceResult, + pub construct: fn(&mut Engine, &mut Args) -> SourceResult, + pub set: fn(&mut Engine, &mut Args) -> SourceResult, pub vtable: fn(of: TypeId) -> Option<*const ()>, pub field_id: fn(name: &str) -> Option, pub field_name: fn(u8) -> Option<&'static str>, @@ -320,13 +323,14 @@ cast! { /// rule. pub trait Synthesize { /// Prepare the element for show rule application. - fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()>; + fn synthesize(&mut self, engine: &mut Engine, styles: StyleChain) + -> SourceResult<()>; } /// The base recipe for an element. pub trait Show { /// Execute the base recipe for this element. - fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult; + fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult; } /// Post-process an element after it was realized. diff --git a/crates/typst/src/foundations/func.rs b/crates/typst/src/foundations/func.rs index f1da6e37f..6729b39d7 100644 --- a/crates/typst/src/foundations/func.rs +++ b/crates/typst/src/foundations/func.rs @@ -6,14 +6,12 @@ use ecow::{eco_format, EcoString}; use once_cell::sync::Lazy; use crate::diag::{bail, SourceResult, StrResult}; -use crate::eval::{Route, Vm}; +use crate::engine::Engine; use crate::foundations::{ - cast, repr, scope, ty, Args, CastInfo, Content, Element, IntoValue, Scope, Scopes, - Selector, Type, Value, + cast, repr, scope, ty, Args, CastInfo, Content, Element, IntoArgs, Scope, Selector, + Type, Value, }; -use crate::introspection::Locator; -use crate::layout::Vt; -use crate::syntax::{ast, FileId, Span, SyntaxNode}; +use crate::syntax::{ast, Span, SyntaxNode}; use crate::util::Static; #[doc(inline)] @@ -252,7 +250,13 @@ impl Func { } /// Call the function with the given arguments. - pub fn call_vm(&self, vm: &mut Vm, mut args: Args) -> SourceResult { + #[tracing::instrument(skip_all)] + pub fn call(&self, engine: &mut Engine, args: impl IntoArgs) -> SourceResult { + self.call_impl(engine, args.into_args(self.span)) + } + + /// Non-generic implementation of `call`. + fn call_impl(&self, engine: &mut Engine, mut args: Args) -> SourceResult { let _span = tracing::info_span!( "call", name = self.name().unwrap_or(""), @@ -261,59 +265,32 @@ impl Func { match &self.repr { Repr::Native(native) => { - let value = (native.function)(vm, &mut args)?; + let value = (native.function)(engine, &mut args)?; args.finish()?; Ok(value) } Repr::Element(func) => { - let value = func.construct(vm, &mut args)?; + let value = func.construct(engine, &mut args)?; args.finish()?; Ok(Value::Content(value)) } - Repr::Closure(closure) => { - // Determine the route inside the closure. - let fresh = Route::new(closure.file); - let route = if vm.file.is_none() { fresh.track() } else { vm.route }; - crate::eval::call_closure( - self, - closure, - vm.world(), - route, - vm.vt.introspector, - vm.vt.locator.track(), - TrackedMut::reborrow_mut(&mut vm.vt.tracer), - vm.depth + 1, - args, - ) - } + Repr::Closure(closure) => crate::eval::call_closure( + self, + closure, + engine.world, + engine.introspector, + engine.route.track(), + engine.locator.track(), + TrackedMut::reborrow_mut(&mut engine.tracer), + args, + ), Repr::With(with) => { args.items = with.1.items.iter().cloned().chain(args.items).collect(); - with.0.call_vm(vm, args) + with.0.call(engine, args) } } } - /// Call the function with a Vt. - #[tracing::instrument(skip_all)] - pub fn call_vt( - &self, - vt: &mut Vt, - args: impl IntoIterator, - ) -> SourceResult { - let route = Route::default(); - let scopes = Scopes::new(None); - let mut locator = Locator::chained(vt.locator.track()); - let vt = Vt { - world: vt.world, - introspector: vt.introspector, - locator: &mut locator, - tracer: TrackedMut::reborrow_mut(&mut vt.tracer), - }; - let mut vm = Vm::new(vt, route.track(), None, scopes); - let args = Args::new(self.span(), args); - self.call_vm(&mut vm, args) - } - /// The function's span. pub fn span(&self) -> Span { self.span @@ -443,7 +420,7 @@ pub trait NativeFunc { /// Defines a native function. #[derive(Debug)] pub struct NativeFuncData { - pub function: fn(&mut Vm, &mut Args) -> SourceResult, + pub function: fn(&mut Engine, &mut Args) -> SourceResult, pub name: &'static str, pub title: &'static str, pub docs: &'static str, @@ -495,8 +472,6 @@ pub struct ParamInfo { pub struct Closure { /// The closure's syntax node. Must be castable to `ast::Closure`. pub node: SyntaxNode, - /// The source file where the closure was defined. - pub file: Option, /// Default values of named parameters. pub defaults: Vec, /// Captured values from outer scopes. diff --git a/crates/typst/src/foundations/mod.rs b/crates/typst/src/foundations/mod.rs index b2c351944..4ddc2162e 100644 --- a/crates/typst/src/foundations/mod.rs +++ b/crates/typst/src/foundations/mod.rs @@ -60,6 +60,7 @@ pub use self::ty::*; pub use self::value::*; pub use self::version::*; +#[rustfmt::skip] #[doc(hidden)] pub use { ecow::{eco_format, eco_vec}, @@ -70,7 +71,8 @@ pub use { use ecow::EcoString; use crate::diag::{bail, SourceResult, StrResult}; -use crate::eval::{EvalMode, Vm}; +use crate::engine::Engine; +use crate::eval::EvalMode; use crate::syntax::Spanned; /// Foundational types and functions. @@ -251,8 +253,8 @@ impl assert { /// ``` #[func(title = "Evaluate")] pub fn eval( - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// A string of Typst code to evaluate. /// /// The code in the string cannot interact with the file system. @@ -289,5 +291,5 @@ pub fn eval( for (key, value) in dict { scope.define(key, value); } - crate::eval::eval_string(vm.world(), &text, span, mode, scope) + crate::eval::eval_string(engine.world, &text, span, mode, scope) } diff --git a/crates/typst/src/foundations/plugin.rs b/crates/typst/src/foundations/plugin.rs index 6071b3bd2..89237f940 100644 --- a/crates/typst/src/foundations/plugin.rs +++ b/crates/typst/src/foundations/plugin.rs @@ -3,10 +3,10 @@ use std::hash::{Hash, Hasher}; use std::sync::{Arc, Mutex}; use ecow::{eco_format, EcoString}; -use wasmi::{AsContext, AsContextMut, Caller, Engine, Linker, Module}; +use wasmi::{AsContext, AsContextMut}; use crate::diag::{bail, At, SourceResult, StrResult}; -use crate::eval::Vm; +use crate::engine::Engine; use crate::foundations::{func, repr, scope, ty, Bytes}; use crate::syntax::Spanned; use crate::World; @@ -152,14 +152,14 @@ impl Plugin { /// Creates a new plugin from a WebAssembly file. #[func(constructor)] pub fn construct( - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// Path to a WebAssembly file. path: Spanned, ) -> SourceResult { let Spanned { v: path, span } = path; - let id = vm.resolve_path(&path).at(span)?; - let data = vm.world().file(id).at(span)?; + let id = span.resolve_path(&path).at(span)?; + let data = engine.world.file(id).at(span)?; Plugin::new(data).at(span) } } @@ -168,11 +168,11 @@ impl Plugin { /// Create a new plugin from raw WebAssembly bytes. #[comemo::memoize] pub fn new(bytes: Bytes) -> StrResult { - let engine = Engine::default(); - let module = Module::new(&engine, bytes.as_slice()) + let engine = wasmi::Engine::default(); + let module = wasmi::Module::new(&engine, bytes.as_slice()) .map_err(|err| format!("failed to load WebAssembly module ({err})"))?; - let mut linker = Linker::new(&engine); + let mut linker = wasmi::Linker::new(&engine); linker .func_wrap( "typst_env", @@ -323,7 +323,10 @@ impl Hash for Plugin { } /// Write the arguments to the plugin function into the plugin's memory. -fn wasm_minimal_protocol_write_args_to_buffer(mut caller: Caller, ptr: u32) { +fn wasm_minimal_protocol_write_args_to_buffer( + mut caller: wasmi::Caller, + ptr: u32, +) { let memory = caller.get_export("memory").unwrap().into_memory().unwrap(); let arguments = std::mem::take(&mut caller.data_mut().args); let mut offset = ptr as usize; @@ -342,7 +345,7 @@ fn wasm_minimal_protocol_write_args_to_buffer(mut caller: Caller, ptr /// Extracts the output of the plugin function from the plugin's memory. fn wasm_minimal_protocol_send_result_to_host( - mut caller: Caller, + mut caller: wasmi::Caller, ptr: u32, len: u32, ) { diff --git a/crates/typst/src/foundations/str.rs b/crates/typst/src/foundations/str.rs index 4b56971ae..83bde675b 100644 --- a/crates/typst/src/foundations/str.rs +++ b/crates/typst/src/foundations/str.rs @@ -8,10 +8,10 @@ use serde::{Deserialize, Serialize}; use unicode_segmentation::UnicodeSegmentation; use crate::diag::{bail, At, SourceResult, StrResult}; -use crate::eval::Vm; +use crate::engine::Engine; use crate::foundations::{ - cast, dict, func, repr, scope, ty, Args, Array, Bytes, Dict, Func, IntoValue, Label, - Repr, Type, Value, Version, + cast, dict, func, repr, scope, ty, Array, Bytes, Dict, Func, IntoValue, Label, Repr, + Type, Value, Version, }; use crate::layout::Align; use crate::syntax::{Span, Spanned}; @@ -422,8 +422,8 @@ impl Str { #[func] pub fn replace( &self, - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// The pattern to search for. pattern: StrPattern, /// The string to replace the matches with or a function that gets a @@ -449,8 +449,8 @@ impl Str { match &replacement { Replacement::Str(s) => output.push_str(s), Replacement::Func(func) => { - let args = Args::new(func.span(), [dict.into_value()]); - let piece = func.call_vm(vm, args)?.cast::().at(func.span())?; + let piece = + func.call(engine, [dict])?.cast::().at(func.span())?; output.push_str(&piece); } } diff --git a/crates/typst/src/foundations/styles.rs b/crates/typst/src/foundations/styles.rs index 78bd24d63..ca6a9e788 100644 --- a/crates/typst/src/foundations/styles.rs +++ b/crates/typst/src/foundations/styles.rs @@ -10,12 +10,10 @@ use once_cell::sync::Lazy; use smallvec::SmallVec; use crate::diag::{SourceResult, Trace, Tracepoint}; -use crate::eval::Vm; +use crate::engine::Engine; use crate::foundations::{ - cast, elem, func, ty, Args, Content, Element, Func, NativeElement, Repr, Selector, - Show, Value, + cast, elem, func, ty, Content, Element, Func, NativeElement, Repr, Selector, Show, }; -use crate::layout::Vt; use crate::syntax::Span; use crate::text::{FontFamily, FontList, TextElem}; @@ -58,8 +56,8 @@ struct StyleElem { impl Show for StyleElem { #[tracing::instrument(name = "StyleElem::show", skip_all)] - fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self.func().call_vt(vt, [styles.to_map()])?.display()) + fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { + Ok(self.func().call(engine, [styles.to_map()])?.display()) } } @@ -356,37 +354,23 @@ impl Recipe { } /// Apply the recipe to the given content. - pub fn apply_vm(&self, vm: &mut Vm, content: Content) -> SourceResult { - match &self.transform { - Transformation::Content(content) => Ok(content.clone()), + pub fn apply(&self, engine: &mut Engine, content: Content) -> SourceResult { + let mut content = match &self.transform { + Transformation::Content(content) => content.clone(), Transformation::Func(func) => { - let args = Args::new(self.span, [Value::Content(content.clone())]); - let mut result = func.call_vm(vm, args); - // For selector-less show rules, a tracepoint makes no sense. + let mut result = func.call(engine, [content.clone()]); if self.selector.is_some() { let point = || Tracepoint::Show(content.func().name().into()); - result = result.trace(vm.world(), point, content.span()); + result = result.trace(engine.world, point, content.span()); } - Ok(result?.display()) + result?.display() } - Transformation::Style(styles) => Ok(content.styled_with_map(styles.clone())), - } - } - - /// Apply the recipe to the given content. - pub fn apply_vt(&self, vt: &mut Vt, content: Content) -> SourceResult { - match &self.transform { - Transformation::Content(content) => Ok(content.clone()), - Transformation::Func(func) => { - let mut result = func.call_vt(vt, [Value::Content(content.clone())]); - if self.selector.is_some() { - let point = || Tracepoint::Show(content.func().name().into()); - result = result.trace(vt.world, point, content.span()); - } - Ok(result?.display()) - } - Transformation::Style(styles) => Ok(content.styled_with_map(styles.clone())), + Transformation::Style(styles) => content.styled_with_map(styles.clone()), + }; + if content.span().is_detached() { + content = content.spanned(self.span); } + Ok(content) } } diff --git a/crates/typst/src/foundations/ty.rs b/crates/typst/src/foundations/ty.rs index 47add6ec9..4c2098f38 100644 --- a/crates/typst/src/foundations/ty.rs +++ b/crates/typst/src/foundations/ty.rs @@ -8,6 +8,7 @@ use crate::diag::StrResult; use crate::foundations::{cast, func, Func, NativeFuncData, Repr, Scope, Value}; use crate::util::Static; +#[rustfmt::skip] #[doc(inline)] pub use typst_macros::{scope, ty}; diff --git a/crates/typst/src/introspection/counter.rs b/crates/typst/src/introspection/counter.rs index 7b0736818..ce4859976 100644 --- a/crates/typst/src/introspection/counter.rs +++ b/crates/typst/src/introspection/counter.rs @@ -6,6 +6,7 @@ use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use smallvec::{smallvec, SmallVec}; use crate::diag::{At, SourceResult, StrResult}; +use crate::engine::{Engine, Route}; use crate::eval::Tracer; use crate::foundations::{ cast, elem, func, scope, select_where, ty, Array, Content, Element, Func, IntoValue, @@ -13,7 +14,7 @@ use crate::foundations::{ Value, }; use crate::introspection::{Introspector, Locatable, Location, Locator, Meta}; -use crate::layout::{Frame, FrameItem, PageElem, Vt}; +use crate::layout::{Frame, FrameItem, PageElem}; use crate::math::EquationElem; use crate::model::{FigureElem, HeadingElem, Numbering, NumberingPattern}; use crate::util::NonZeroExt; @@ -222,9 +223,13 @@ impl Counter { } /// Gets the current and final value of the state combined in one state. - pub fn both(&self, vt: &mut Vt, location: Location) -> SourceResult { - let sequence = self.sequence(vt)?; - let offset = vt + pub fn both( + &self, + engine: &mut Engine, + location: Location, + ) -> SourceResult { + let sequence = self.sequence(engine)?; + let offset = engine .introspector .query(&self.selector().before(location.into(), true)) .len(); @@ -232,10 +237,10 @@ impl Counter { let (mut final_state, final_page) = sequence.last().unwrap().clone(); if self.is_page() { let at_delta = - vt.introspector.page(location).get().saturating_sub(at_page.get()); + engine.introspector.page(location).get().saturating_sub(at_page.get()); at_state.step(NonZeroUsize::ONE, at_delta); let final_delta = - vt.introspector.pages().get().saturating_sub(final_page.get()); + engine.introspector.pages().get().saturating_sub(final_page.get()); final_state.step(NonZeroUsize::ONE, final_delta); } Ok(CounterState(smallvec![at_state.first(), final_state.first()])) @@ -247,13 +252,14 @@ impl Counter { /// of counter updates from quadratic to linear. fn sequence( &self, - vt: &mut Vt, + engine: &mut Engine, ) -> SourceResult> { self.sequence_impl( - vt.world, - vt.introspector, - vt.locator.track(), - TrackedMut::reborrow_mut(&mut vt.tracer), + engine.world, + engine.introspector, + engine.route.track(), + engine.locator.track(), + TrackedMut::reborrow_mut(&mut engine.tracer), ) } @@ -263,11 +269,18 @@ impl Counter { &self, world: Tracked, introspector: Tracked, + route: Tracked, locator: Tracked, tracer: TrackedMut, ) -> SourceResult> { let mut locator = Locator::chained(locator); - let mut vt = Vt { world, introspector, locator: &mut locator, tracer }; + let mut engine = Engine { + world, + introspector, + route: Route::extend(route), + locator: &mut locator, + tracer, + }; let mut state = CounterState::init(&self.0); let mut page = NonZeroUsize::ONE; @@ -288,7 +301,7 @@ impl Counter { Some(countable) => countable.update(), None => Some(CounterUpdate::Step(NonZeroUsize::ONE)), } { - state.update(&mut vt, update)?; + state.update(&mut engine, update)?; } stops.push((state.clone(), page)); @@ -399,21 +412,22 @@ impl Counter { #[func] pub fn at( &self, - /// The virtual typesetter. - vt: &mut Vt, + /// The engine. + engine: &mut Engine, /// The location at which the counter value should be retrieved. A /// suitable location can be retrieved from [`locate`]($locate) or /// [`query`]($query). location: Location, ) -> SourceResult { - let sequence = self.sequence(vt)?; - let offset = vt + let sequence = self.sequence(engine)?; + let offset = engine .introspector .query(&self.selector().before(location.into(), true)) .len(); let (mut state, page) = sequence[offset].clone(); if self.is_page() { - let delta = vt.introspector.page(location).get().saturating_sub(page.get()); + let delta = + engine.introspector.page(location).get().saturating_sub(page.get()); state.step(NonZeroUsize::ONE, delta); } @@ -425,8 +439,8 @@ impl Counter { #[func] pub fn final_( &self, - /// The virtual typesetter. - vt: &mut Vt, + /// The engine. + engine: &mut Engine, /// Can be an arbitrary location, as its value is irrelevant for the /// method's return value. Why is it required then? Typst has to /// evaluate parts of your code multiple times to determine all counter @@ -438,10 +452,10 @@ impl Counter { location: Location, ) -> SourceResult { let _ = location; - let sequence = self.sequence(vt)?; + let sequence = self.sequence(engine)?; let (mut state, page) = sequence.last().unwrap().clone(); if self.is_page() { - let delta = vt.introspector.pages().get().saturating_sub(page.get()); + let delta = engine.introspector.pages().get().saturating_sub(page.get()); state.step(NonZeroUsize::ONE, delta); } Ok(state) @@ -544,12 +558,17 @@ impl CounterState { } /// Advance the counter and return the numbers for the given heading. - pub fn update(&mut self, vt: &mut Vt, update: CounterUpdate) -> SourceResult<()> { + pub fn update( + &mut self, + engine: &mut Engine, + update: CounterUpdate, + ) -> SourceResult<()> { match update { CounterUpdate::Set(state) => *self = state, CounterUpdate::Step(level) => self.step(level, 1), CounterUpdate::Func(func) => { - *self = func.call_vt(vt, self.0.iter().copied())?.cast().at(func.span())? + *self = + func.call(engine, self.0.iter().copied())?.cast().at(func.span())? } } Ok(()) @@ -575,8 +594,12 @@ impl CounterState { } /// Display the counter state with a numbering. - pub fn display(&self, vt: &mut Vt, numbering: &Numbering) -> SourceResult { - Ok(numbering.apply_vt(vt, &self.0)?.display()) + pub fn display( + &self, + engine: &mut Engine, + numbering: &Numbering, + ) -> SourceResult { + Ok(numbering.apply(engine, &self.0)?.display()) } } @@ -608,8 +631,8 @@ struct DisplayElem { impl Show for DisplayElem { #[tracing::instrument(name = "DisplayElem::show", skip_all)] - fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(vt.delayed(|vt| { + fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { + Ok(engine.delayed(|engine| { let location = self.location().unwrap(); let counter = self.counter(); let numbering = self @@ -633,12 +656,12 @@ impl Show for DisplayElem { .unwrap_or_else(|| NumberingPattern::from_str("1.1").unwrap().into()); let state = if *self.both() { - counter.both(vt, location)? + counter.both(engine, location)? } else { - counter.at(vt, location)? + counter.at(engine, location)? }; - state.display(vt, &numbering) + state.display(engine, &numbering) })) } } @@ -657,7 +680,7 @@ struct UpdateElem { impl Show for UpdateElem { #[tracing::instrument(name = "UpdateElem::show", skip(self))] - fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult { + fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult { Ok(Content::empty()) } } @@ -693,15 +716,15 @@ impl ManualPageCounter { } /// Advance past a page. - pub fn visit(&mut self, vt: &mut Vt, page: &Frame) -> SourceResult<()> { + pub fn visit(&mut self, engine: &mut Engine, page: &Frame) -> SourceResult<()> { for (_, item) in page.items() { match item { - FrameItem::Group(group) => self.visit(vt, &group.frame)?, + FrameItem::Group(group) => self.visit(engine, &group.frame)?, FrameItem::Meta(Meta::Elem(elem), _) => { let Some(elem) = elem.to::() else { continue }; if *elem.key() == CounterKey::Page { let mut state = CounterState(smallvec![self.logical]); - state.update(vt, elem.update().clone())?; + state.update(engine, elem.update().clone())?; self.logical = state.first(); } } diff --git a/crates/typst/src/introspection/locate.rs b/crates/typst/src/introspection/locate.rs index 1c4f0e5a1..69dcadd8c 100644 --- a/crates/typst/src/introspection/locate.rs +++ b/crates/typst/src/introspection/locate.rs @@ -1,7 +1,7 @@ use crate::diag::SourceResult; +use crate::engine::Engine; use crate::foundations::{elem, func, Content, Func, NativeElement, Show, StyleChain}; use crate::introspection::Locatable; -use crate::layout::Vt; /// Provides access to the location of content. /// @@ -37,11 +37,11 @@ struct LocateElem { } impl Show for LocateElem { - #[tracing::instrument(name = "LocateElem::show", skip(self, vt))] - fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { - Ok(vt.delayed(|vt| { + #[tracing::instrument(name = "LocateElem::show", skip(self, engine))] + fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult { + Ok(engine.delayed(|engine| { let location = self.location().unwrap(); - Ok(self.func().call_vt(vt, [location])?.display()) + Ok(self.func().call(engine, [location])?.display()) })) } } diff --git a/crates/typst/src/introspection/location.rs b/crates/typst/src/introspection/location.rs index b70dc4ad4..0ceaac8b4 100644 --- a/crates/typst/src/introspection/location.rs +++ b/crates/typst/src/introspection/location.rs @@ -2,7 +2,7 @@ use std::num::NonZeroUsize; use ecow::EcoString; -use crate::eval::Vm; +use crate::engine::Engine; use crate::foundations::{cast, func, scope, ty, Dict, Repr}; use crate::model::Numbering; @@ -45,8 +45,8 @@ impl Location { /// If you want to know the value of the page counter, use /// `{counter(page).at(loc)}` instead. #[func] - pub fn page(self, vm: &mut Vm) -> NonZeroUsize { - vm.vt.introspector.page(self) + pub fn page(self, engine: &mut Engine) -> NonZeroUsize { + engine.introspector.page(self) } /// Return a dictionary with the page number and the x, y position for this @@ -56,8 +56,8 @@ impl Location { /// If you only need the page number, use `page()` instead as it allows /// Typst to skip unnecessary work. #[func] - pub fn position(self, vm: &mut Vm) -> Dict { - vm.vt.introspector.position(self).into() + pub fn position(self, engine: &mut Engine) -> Dict { + engine.introspector.position(self).into() } /// Returns the page numbering pattern of the page at this location. This @@ -68,8 +68,8 @@ impl Location { /// If the page numbering is set to `none` at that location, this function /// returns `none`. #[func] - pub fn page_numbering(self, vm: &mut Vm) -> Option { - vm.vt.introspector.page_numbering(self).cloned() + pub fn page_numbering(self, engine: &mut Engine) -> Option { + engine.introspector.page_numbering(self).cloned() } } @@ -83,5 +83,5 @@ cast! { type Location, } -/// Makes this element locatable through `vt.locate`. +/// Makes this element locatable through `engine.locate`. pub trait Locatable {} diff --git a/crates/typst/src/introspection/metadata.rs b/crates/typst/src/introspection/metadata.rs index 4042bc04f..15d144eb7 100644 --- a/crates/typst/src/introspection/metadata.rs +++ b/crates/typst/src/introspection/metadata.rs @@ -1,7 +1,7 @@ use crate::diag::SourceResult; +use crate::engine::Engine; use crate::foundations::{elem, Behave, Behaviour, Content, Show, StyleChain, Value}; use crate::introspection::Locatable; -use crate::layout::Vt; /// Exposes a value to the query system without producing visible content. /// @@ -31,7 +31,7 @@ pub struct MetadataElem { } impl Show for MetadataElem { - fn show(&self, _vt: &mut Vt, _styles: StyleChain) -> SourceResult { + fn show(&self, _: &mut Engine, _styles: StyleChain) -> SourceResult { Ok(Content::empty()) } } diff --git a/crates/typst/src/introspection/query.rs b/crates/typst/src/introspection/query.rs index 1024238e9..5b70ba512 100644 --- a/crates/typst/src/introspection/query.rs +++ b/crates/typst/src/introspection/query.rs @@ -1,4 +1,4 @@ -use crate::eval::Vm; +use crate::engine::Engine; use crate::foundations::{func, Array, LocatableSelector, Value}; use crate::introspection::Location; @@ -129,8 +129,8 @@ use crate::introspection::Location; /// ``` #[func] pub fn query( - /// The virtual machine. - vm: &mut Vm, + /// The engine. + engine: &mut Engine, /// Can be an element function like a `heading` or `figure`, a `{