summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/func.rs24
-rw-r--r--src/eval/mod.rs57
-rw-r--r--src/eval/vm.rs33
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());
}