diff --git a/src/eval/func.rs b/src/eval/func.rs index c3ec42334..af80ac92c 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -13,7 +13,7 @@ use super::{ Vm, }; use crate::diag::{bail, SourceResult, StrResult}; -use crate::model::{NodeId, Selector, StyleMap, Vt}; +use crate::model::{Introspector, NodeId, Selector, StabilityProvider, StyleMap, Vt}; use crate::syntax::ast::{self, AstNode, Expr}; use crate::syntax::{SourceId, Span, SyntaxNode}; use crate::util::hash128; @@ -102,9 +102,11 @@ impl Func { Closure::call( self, - vm.world, + vm.world(), route, - TrackedMut::reborrow_mut(&mut vm.tracer), + TrackedMut::reborrow_mut(&mut vm.vt.tracer), + TrackedMut::reborrow_mut(&mut vm.vt.provider), + vm.vt.introspector, vm.depth + 1, args, ) @@ -125,13 +127,7 @@ impl Func { let route = Route::default(); let id = SourceId::detached(); let scopes = Scopes::new(None); - let mut vm = Vm::new( - vt.world, - route.track(), - TrackedMut::reborrow_mut(&mut vt.tracer), - id, - scopes, - ); + let mut vm = Vm::new(vt.reborrow_mut(), route.track(), id, scopes); let args = Args::new(self.span(), args); self.call_vm(&mut vm, args) } @@ -318,6 +314,8 @@ impl Closure { world: Tracked, route: Tracked, tracer: TrackedMut, + provider: TrackedMut, + introspector: Tracked, depth: usize, mut args: Args, ) -> SourceResult { @@ -358,7 +356,8 @@ impl Closure { args.finish()?; // Evaluate the body. - let mut sub = Vm::new(world, route, tracer, closure.location, scopes); + let vt = Vt { world, tracer, provider, introspector }; + let mut sub = Vm::new(vt, route, closure.location, scopes); sub.depth = depth; // Handle control flow. diff --git a/src/eval/methods.rs b/src/eval/methods.rs index 036f7ba22..eef825ff8 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -73,6 +73,8 @@ pub fn call( "func" => Value::Func(content.id().into()), "has" => Value::Bool(content.has(&args.expect::("field")?)), "at" => content.at(&args.expect::("field")?).at(span)?.clone(), + "page" => content.page(&vm.vt).at(span)?.into(), + "location" => content.location(&vm.vt).at(span)?.into(), _ => return missing(), }, @@ -249,7 +251,13 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { ("starts-with", true), ("trim", true), ], - "content" => &[("func", false), ("has", true), ("at", true)], + "content" => &[ + ("func", false), + ("has", true), + ("at", true), + ("page", false), + ("location", false), + ], "array" => &[ ("all", true), ("any", true), diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 1c002c494..a7f4b8256 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -47,7 +47,10 @@ use unicode_segmentation::UnicodeSegmentation; use crate::diag::{ bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, }; +use crate::model::Introspector; +use crate::model::StabilityProvider; use crate::model::Unlabellable; +use crate::model::Vt; use crate::model::{Content, Label, Recipe, Selector, StyleMap, Transform}; use crate::syntax::ast::AstNode; use crate::syntax::{ @@ -81,7 +84,15 @@ pub fn eval( // Evaluate the module. let route = unsafe { Route::insert(route, id) }; let scopes = Scopes::new(Some(library)); - let mut vm = Vm::new(world, route.track(), tracer, id, scopes); + let mut provider = StabilityProvider::new(); + let introspector = Introspector::new(&[]); + let vt = Vt { + world, + tracer, + provider: provider.track_mut(), + introspector: introspector.track(), + }; + let mut vm = Vm::new(vt, route.track(), id, scopes); let root = match source.root().cast::() { Some(markup) if vm.traced.is_some() => markup, _ => source.ast()?, @@ -121,7 +132,15 @@ pub fn eval_code_str( let scopes = Scopes::new(Some(library)); let route = Route::default(); let mut tracer = Tracer::default(); - let mut vm = Vm::new(world, route.track(), tracer.track_mut(), id, scopes); + let mut provider = StabilityProvider::new(); + let introspector = Introspector::new(&[]); + let vt = Vt { + world, + tracer: tracer.track_mut(), + provider: provider.track_mut(), + introspector: introspector.track(), + }; + let mut vm = Vm::new(vt, route.track(), id, scopes); let code = root.cast::().unwrap(); let result = code.eval(&mut vm); @@ -138,14 +157,12 @@ pub fn eval_code_str( /// Holds the state needed to [evaluate](eval) Typst sources. A new /// virtual machine is created for each module evaluation and function call. pub struct Vm<'a> { - /// The compilation environment. - world: Tracked<'a, dyn World>, + /// The underlying virtual typesetter. + vt: Vt<'a>, /// The language items. items: LangItems, /// The route of source ids the VM took to reach its current location. route: Tracked<'a, Route>, - /// The tracer for inspection of the values an expression produces. - tracer: TrackedMut<'a, Tracer>, /// The current location. location: SourceId, /// A control flow event that is currently happening. @@ -161,18 +178,17 @@ pub struct Vm<'a> { impl<'a> Vm<'a> { /// Create a new virtual machine. fn new( - world: Tracked<'a, dyn World>, + vt: Vt<'a>, route: Tracked<'a, Route>, - tracer: TrackedMut<'a, Tracer>, location: SourceId, scopes: Scopes<'a>, ) -> Self { - let traced = tracer.span(location); + let traced = vt.tracer.span(location); + let items = vt.world.library().items.clone(); Self { - world, - items: world.library().items.clone(), + vt, + items, route, - tracer, location, flow: None, scopes, @@ -183,14 +199,14 @@ impl<'a> Vm<'a> { /// Access the underlying world. pub fn world(&self) -> Tracked<'a, dyn World> { - self.world + self.vt.world } /// Define a variable in the current scope. pub fn define(&mut self, var: ast::Ident, value: impl Into) { let value = value.into(); if self.traced == Some(var.span()) { - self.tracer.trace(value.clone()); + self.vt.tracer.trace(value.clone()); } self.scopes.top.define(var.take(), value); } @@ -200,10 +216,10 @@ impl<'a> Vm<'a> { pub fn locate(&self, path: &str) -> StrResult { if !self.location.is_detached() { if let Some(path) = path.strip_prefix('/') { - return Ok(self.world.root().join(path).normalize()); + return Ok(self.world().root().join(path).normalize()); } - if let Some(dir) = self.world.source(self.location).path().parent() { + if let Some(dir) = self.world().source(self.location).path().parent() { return Ok(dir.join(path).normalize()); } } @@ -450,7 +466,7 @@ impl Eval for ast::Expr { .spanned(span); if vm.traced == Some(span) { - vm.tracer.trace(v.clone()); + vm.vt.tracer.trace(v.clone()); } Ok(v) @@ -1004,16 +1020,22 @@ impl Eval for ast::FuncCall { let args = args.eval(vm)?; let target = target.access(vm)?; if !matches!(target, Value::Symbol(_) | Value::Module(_)) { - return methods::call_mut(target, &field, args, span) - .trace(vm.world, point, span); + return methods::call_mut(target, &field, args, span).trace( + vm.world(), + point, + span, + ); } (target.field(&field).at(field_span)?, args) } else { let target = target.eval(vm)?; let args = args.eval(vm)?; if !matches!(target, Value::Symbol(_) | Value::Module(_)) { - return methods::call(vm, target, &field, args, span) - .trace(vm.world, point, span); + return methods::call(vm, target, &field, args, span).trace( + vm.world(), + point, + span, + ); } (target.field(&field).at(field_span)?, args) } @@ -1052,7 +1074,7 @@ impl Eval for ast::FuncCall { let callee = callee.cast::().at(callee_span)?; let point = || Tracepoint::Call(callee.name().map(Into::into)); - callee.call_vm(vm, args).trace(vm.world, point, span) + callee.call_vm(vm, args).trace(vm.world(), point, span) } } @@ -1431,8 +1453,9 @@ fn import(vm: &mut Vm, source: Value, span: Span) -> SourceResult { }; // Load the source file. + let world = vm.world(); let full = vm.locate(&path).at(span)?; - let id = vm.world.resolve(&full).at(span)?; + let id = world.resolve(&full).at(span)?; // Prevent cyclic importing. if vm.route.contains(id) { @@ -1440,10 +1463,10 @@ fn import(vm: &mut Vm, source: Value, span: Span) -> SourceResult { } // Evaluate the file. - let source = vm.world.source(id); + let source = world.source(id); let point = || Tracepoint::Import; - eval(vm.world, vm.route, TrackedMut::reborrow_mut(&mut vm.tracer), source) - .trace(vm.world, point, span) + eval(world, vm.route, TrackedMut::reborrow_mut(&mut vm.vt.tracer), source) + .trace(world, point, span) } impl Eval for ast::LoopBreak { @@ -1506,7 +1529,7 @@ impl Access for ast::Ident { let span = self.span(); let value = vm.scopes.get_mut(self).at(span)?; if vm.traced == Some(span) { - vm.tracer.trace(value.clone()); + vm.vt.tracer.trace(value.clone()); } Ok(value) } diff --git a/src/model/content.rs b/src/model/content.rs index 5317236e6..3c6619292 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -2,6 +2,7 @@ use std::any::TypeId; use std::fmt::{self, Debug, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::iter::{self, Sum}; +use std::num::NonZeroUsize; use std::ops::{Add, AddAssign, Deref}; use ecow::{eco_format, EcoString, EcoVec}; @@ -9,10 +10,10 @@ use once_cell::sync::Lazy; use super::{ node, Behave, Behaviour, Fold, Guard, Locatable, Recipe, StableId, Style, StyleMap, - Synthesize, + Synthesize, Vt, }; use crate::diag::{SourceResult, StrResult}; -use crate::doc::Meta; +use crate::doc::{Location, Meta}; use crate::eval::{ cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm, }; @@ -185,6 +186,22 @@ impl Content { self.field(field).ok_or_else(|| missing_field(field)) } + /// Determine the page of this content. + pub fn page(&self, vt: &Vt) -> StrResult { + match self.stable_id() { + Some(id) => Ok(vt.introspector.page(id)), + None => Err("this method can only be called on queried content".into()), + } + } + + /// Determine the location of this content. + pub fn location(&self, vt: &Vt) -> StrResult { + match self.stable_id() { + Some(id) => Ok(vt.introspector.location(id)), + None => Err("this method can only be called on queried content".into()), + } + } + /// The content's label. pub fn label(&self) -> Option<&Label> { match self.field("label")? { diff --git a/src/model/typeset.rs b/src/model/typeset.rs index 683f2846a..162a2a677 100644 --- a/src/model/typeset.rs +++ b/src/model/typeset.rs @@ -53,8 +53,7 @@ pub fn typeset( /// A virtual typesetter. /// -/// Holds the state needed to [typeset] content. This is the equivalent to the -/// [Vm](crate::eval::Vm) for typesetting. +/// Holds the state needed to [typeset] content. pub struct Vt<'a> { /// The compilation environment. pub world: Tracked<'a, dyn World>, @@ -66,6 +65,18 @@ pub struct Vt<'a> { pub introspector: Tracked<'a, Introspector>, } +impl Vt<'_> { + /// Mutably reborrow with a shorter lifetime. + pub fn reborrow_mut(&mut self) -> Vt<'_> { + Vt { + world: self.world, + tracer: TrackedMut::reborrow_mut(&mut self.tracer), + provider: TrackedMut::reborrow_mut(&mut self.provider), + introspector: self.introspector, + } + } +} + /// Provides stable identities to nodes. #[derive(Clone)] pub struct StabilityProvider {