diff options
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/func.rs | 24 | ||||
| -rw-r--r-- | src/eval/mod.rs | 57 | ||||
| -rw-r--r-- | src/eval/vm.rs | 33 |
3 files changed, 83 insertions, 31 deletions
diff --git a/src/eval/func.rs b/src/eval/func.rs index b8730a8a..c307b237 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -2,7 +2,9 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use super::{Args, Eval, Flow, Scope, Scopes, Value, Vm}; +use comemo::{Track, Tracked}; + +use super::{Args, Eval, Flow, Route, Scope, Scopes, Value, Vm}; use crate::diag::{SourceResult, StrResult}; use crate::model::{Content, NodeId, StyleMap}; use crate::source::SourceId; @@ -100,8 +102,13 @@ impl Func { } /// Call the function without an existing virtual machine. - pub fn call_detached(&self, world: &dyn World, args: Args) -> SourceResult<Value> { - let mut vm = Vm::new(world, vec![], Scopes::new(None)); + pub fn call_detached( + &self, + world: Tracked<dyn World>, + args: Args, + ) -> SourceResult<Value> { + let route = Route::default(); + let mut vm = Vm::new(world, route.track(), None, Scopes::new(None)); self.call(&mut vm, args) } @@ -220,15 +227,12 @@ impl Closure { } // Determine the route inside the closure. - let detached = vm.route.is_empty(); - let route = if detached { - self.location.into_iter().collect() - } else { - vm.route.clone() - }; + let detached = vm.location.is_none(); + let fresh = Route::new(self.location); + let route = if detached { fresh.track() } else { vm.route }; // Evaluate the body. - let mut sub = Vm::new(vm.world, route, scopes); + let mut sub = Vm::new(vm.world, route, self.location, scopes); let result = self.body.eval(&mut sub); // Handle control flow. diff --git a/src/eval/mod.rs b/src/eval/mod.rs index eeb95534..fb65420d 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -34,6 +34,7 @@ pub use vm::*; use std::collections::BTreeMap; +use comemo::{Track, Tracked}; use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint}; @@ -51,24 +52,24 @@ use crate::World; /// Returns either a module containing a scope with top-level bindings and /// layoutable contents or diagnostics in the form of a vector of error /// messages with file and span information. -pub fn evaluate( - world: &dyn World, +#[comemo::memoize] +pub fn eval( + world: Tracked<dyn World>, + route: Tracked<Route>, id: SourceId, - mut route: Vec<SourceId>, ) -> SourceResult<Module> { // Prevent cyclic evaluation. - if route.contains(&id) { + if route.contains(id) { let path = world.source(id).path().display(); panic!("Tried to cyclicly evaluate {}", path); } - route.push(id); - // Evaluate the module. + let route = unsafe { Route::insert(route, id) }; let ast = world.source(id).ast()?; let std = &world.config().std; let scopes = Scopes::new(Some(std)); - let mut vm = Vm::new(world, route, scopes); + let mut vm = Vm::new(world, route.track(), Some(id), scopes); let result = ast.eval(&mut vm); // Handle control flow. @@ -80,6 +81,39 @@ pub fn evaluate( Ok(Module { scope: vm.scopes.top, content: result? }) } +/// A route of source ids. +#[derive(Default)] +pub struct Route { + parent: Option<Tracked<'static, Self>>, + id: Option<SourceId>, +} + +impl Route { + /// Create a new, empty route. + pub fn new(id: Option<SourceId>) -> Self { + Self { id, parent: None } + } + + /// Insert a new id into the route. + /// + /// You must guarantee that `outer` lives longer than the resulting + /// route is ever used. + unsafe fn insert(outer: Tracked<Route>, id: SourceId) -> Route { + Route { + parent: Some(std::mem::transmute(outer)), + id: Some(id), + } + } +} + +#[comemo::track] +impl Route { + /// Whether the given id is part of the route. + fn contains(&self, id: SourceId) -> bool { + self.id == Some(id) || self.parent.map_or(false, |parent| parent.contains(id)) + } +} + /// An evaluated module, ready for importing or layouting. #[derive(Debug, Clone)] pub struct Module { @@ -696,7 +730,7 @@ impl Eval for ClosureExpr { // Define the actual function. Ok(Value::Func(Func::from_closure(Closure { - location: vm.route.last().copied(), + location: vm.location, name, captured, params, @@ -755,7 +789,7 @@ impl Eval for ShowExpr { let body = self.body(); let span = body.span(); let func = Func::from_closure(Closure { - location: vm.route.last().copied(), + location: vm.location, name: None, captured, params, @@ -940,14 +974,13 @@ fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> { let id = vm.world.resolve(&full).at(span)?; // Prevent cyclic importing. - if vm.route.contains(&id) { + if vm.route.contains(id) { bail!(span, "cyclic import"); } // Evaluate the file. - let route = vm.route.clone(); let module = - evaluate(vm.world, id, route).trace(vm.world, || Tracepoint::Import, span)?; + eval(vm.world, vm.route, id).trace(vm.world, || Tracepoint::Import, span)?; Ok(module) } diff --git a/src/eval/vm.rs b/src/eval/vm.rs index 7c8e8e31..0604e7be 100644 --- a/src/eval/vm.rs +++ b/src/eval/vm.rs @@ -1,6 +1,8 @@ use std::path::PathBuf; -use super::{Scopes, Value}; +use comemo::Tracked; + +use super::{Route, Scopes, Value}; use crate::diag::{SourceError, StrResult}; use crate::source::SourceId; use crate::syntax::Span; @@ -8,27 +10,40 @@ use crate::util::PathExt; use crate::World; /// A virtual machine. -pub struct Vm<'w> { +pub struct Vm<'a> { /// The core context. - pub world: &'w dyn World, + pub world: Tracked<'a, dyn World>, /// The route of source ids the machine took to reach its current location. - pub route: Vec<SourceId>, + pub route: Tracked<'a, Route>, + /// The current location. + pub location: Option<SourceId>, /// The stack of scopes. - pub scopes: Scopes<'w>, + pub scopes: Scopes<'a>, /// A control flow event that is currently happening. pub flow: Option<Flow>, } -impl<'w> Vm<'w> { +impl<'a> Vm<'a> { /// Create a new virtual machine. - pub fn new(ctx: &'w dyn World, route: Vec<SourceId>, scopes: Scopes<'w>) -> Self { - Self { world: ctx, route, scopes, flow: None } + pub fn new( + world: Tracked<'a, dyn World>, + route: Tracked<'a, Route>, + location: Option<SourceId>, + scopes: Scopes<'a>, + ) -> Self { + Self { + world, + route, + location, + scopes, + flow: None, + } } /// Resolve a user-entered path to be relative to the compilation /// environment's root. pub fn locate(&self, path: &str) -> StrResult<PathBuf> { - if let Some(&id) = self.route.last() { + if let Some(id) = self.location { if let Some(path) = path.strip_prefix('/') { return Ok(self.world.config().root.join(path).normalize()); } |
