diff options
Diffstat (limited to 'src/model')
| -rw-r--r-- | src/model/array.rs | 14 | ||||
| -rw-r--r-- | src/model/dict.rs | 2 | ||||
| -rw-r--r-- | src/model/eval.rs | 80 | ||||
| -rw-r--r-- | src/model/func.rs | 22 | ||||
| -rw-r--r-- | src/model/methods.rs | 2 | ||||
| -rw-r--r-- | src/model/value.rs | 8 |
6 files changed, 102 insertions, 26 deletions
diff --git a/src/model/array.rs b/src/model/array.rs index 0071d4f4..be35c651 100644 --- a/src/model/array.rs +++ b/src/model/array.rs @@ -136,7 +136,7 @@ impl Array { } /// Return the first matching element. - pub fn find(&self, vm: &Vm, func: Func) -> SourceResult<Option<Value>> { + pub fn find(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<Value>> { if func.argc().map_or(false, |count| count != 1) { bail!(func.span(), "function must have exactly one parameter"); } @@ -151,7 +151,7 @@ impl Array { } /// Return the index of the first matching element. - pub fn position(&self, vm: &Vm, func: Func) -> SourceResult<Option<i64>> { + pub fn position(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<i64>> { if func.argc().map_or(false, |count| count != 1) { bail!(func.span(), "function must have exactly one parameter"); } @@ -167,7 +167,7 @@ impl Array { /// Return a new array with only those elements for which the function /// returns true. - pub fn filter(&self, vm: &Vm, func: Func) -> SourceResult<Self> { + pub fn filter(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> { if func.argc().map_or(false, |count| count != 1) { bail!(func.span(), "function must have exactly one parameter"); } @@ -182,7 +182,7 @@ impl Array { } /// Transform each item in the array with a function. - pub fn map(&self, vm: &Vm, func: Func) -> SourceResult<Self> { + pub fn map(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> { if func.argc().map_or(false, |count| !(1..=2).contains(&count)) { bail!(func.span(), "function must have one or two parameters"); } @@ -201,7 +201,7 @@ impl Array { } /// Fold all of the array's elements into one with a function. - pub fn fold(&self, vm: &Vm, init: Value, func: Func) -> SourceResult<Value> { + pub fn fold(&self, vm: &mut Vm, init: Value, func: Func) -> SourceResult<Value> { if func.argc().map_or(false, |count| count != 2) { bail!(func.span(), "function must have exactly two parameters"); } @@ -214,7 +214,7 @@ impl Array { } /// Whether any element matches. - pub fn any(&self, vm: &Vm, func: Func) -> SourceResult<bool> { + pub fn any(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> { if func.argc().map_or(false, |count| count != 1) { bail!(func.span(), "function must have exactly one parameter"); } @@ -229,7 +229,7 @@ impl Array { } /// Whether all elements match. - pub fn all(&self, vm: &Vm, func: Func) -> SourceResult<bool> { + pub fn all(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> { if func.argc().map_or(false, |count| count != 1) { bail!(func.span(), "function must have exactly one parameter"); } diff --git a/src/model/dict.rs b/src/model/dict.rs index e94db923..76d194a8 100644 --- a/src/model/dict.rs +++ b/src/model/dict.rs @@ -105,7 +105,7 @@ impl Dict { } /// Transform each pair in the dictionary with a function. - pub fn map(&self, vm: &Vm, func: Func) -> SourceResult<Array> { + pub fn map(&self, vm: &mut Vm, func: Func) -> SourceResult<Array> { if func.argc().map_or(false, |count| count != 2) { bail!(func.span(), "function must have exactly two parameters"); } diff --git a/src/model/eval.rs b/src/model/eval.rs index 6a1884eb..50d0ffc3 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; use std::mem; use std::path::{Path, PathBuf}; -use comemo::{Track, Tracked}; +use comemo::{Track, Tracked, TrackedMut}; use unicode_segmentation::UnicodeSegmentation; use super::{ @@ -17,7 +17,7 @@ use crate::diag::{ use crate::geom::{Abs, Angle, Em, Fr, Ratio}; use crate::syntax::ast::AstNode; use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode}; -use crate::util::{EcoString, PathExt}; +use crate::util::PathExt; use crate::World; const MAX_ITERATIONS: usize = 10_000; @@ -28,6 +28,7 @@ const MAX_CALL_DEPTH: usize = 256; pub fn eval( world: Tracked<dyn World>, route: Tracked<Route>, + tracer: TrackedMut<Tracer>, source: &Source, ) -> SourceResult<Module> { // Prevent cyclic evaluation. @@ -44,7 +45,7 @@ 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(), id, scopes, 0); + let mut vm = Vm::new(world, route.track(), tracer, id, scopes, 0); let result = source.ast()?.eval(&mut vm); // Handle control flow. @@ -68,6 +69,8 @@ pub struct Vm<'a> { pub(super) items: LangItems, /// The route of source ids the VM took to reach its current location. pub(super) route: Tracked<'a, Route>, + /// The tracer for inspection of the values an expression produces. + pub(super) tracer: TrackedMut<'a, Tracer>, /// The current location. pub(super) location: SourceId, /// A control flow event that is currently happening. @@ -76,6 +79,8 @@ pub struct Vm<'a> { pub(super) scopes: Scopes<'a>, /// The current call depth. pub(super) depth: usize, + /// A span that is currently traced. + pub(super) traced: Option<Span>, } impl<'a> Vm<'a> { @@ -83,18 +88,22 @@ impl<'a> Vm<'a> { pub(super) fn new( world: Tracked<'a, dyn World>, route: Tracked<'a, Route>, + tracer: TrackedMut<'a, Tracer>, location: SourceId, scopes: Scopes<'a>, depth: usize, ) -> Self { + let traced = tracer.span(location); Self { world, items: world.library().items.clone(), route, + tracer, location, flow: None, scopes, depth, + traced, } } @@ -103,6 +112,15 @@ impl<'a> Vm<'a> { self.world } + /// Define a variable in the current scope. + pub fn define(&mut self, var: ast::Ident, value: impl Into<Value>) { + let value = value.into(); + if self.traced == Some(var.span()) { + self.tracer.trace(value.clone()); + } + self.scopes.top.define(var.take(), value); + } + /// Resolve a user-entered path to be relative to the compilation /// environment's root. pub fn locate(&self, path: &str) -> StrResult<PathBuf> { @@ -182,6 +200,47 @@ impl Route { } } +/// Traces which values existed for the expression with the given span. +#[derive(Default, Clone)] +pub struct Tracer { + span: Option<Span>, + values: Vec<Value>, +} + +impl Tracer { + /// The maximum number of traced items. + pub const MAX: usize = 10; + + /// Create a new tracer, possibly with a span under inspection. + pub fn new(span: Option<Span>) -> Self { + Self { span, values: vec![] } + } + + /// Get the traced values. + pub fn finish(self) -> Vec<Value> { + self.values + } +} + +#[comemo::track] +impl Tracer { + /// The traced span if it is part of the given source file. + fn span(&self, id: SourceId) -> Option<Span> { + if self.span.map(Span::source) == Some(id) { + self.span + } else { + None + } + } + + /// Trace a value for the span. + fn trace(&mut self, v: Value) { + if self.values.len() < Self::MAX { + self.values.push(v); + } + } +} + /// Evaluate an expression. pub(super) trait Eval { /// The output of evaluating the expression. @@ -259,12 +318,11 @@ impl Eval for ast::Expr { error!(span, "{} is only allowed directly in code and content blocks", name) }; - match self { + let v = match self { Self::Text(v) => v.eval(vm).map(Value::Content), Self::Space(v) => v.eval(vm).map(Value::Content), Self::Linebreak(v) => v.eval(vm).map(Value::Content), Self::Parbreak(v) => v.eval(vm).map(Value::Content), - Self::Symbol(v) => v.eval(vm).map(Value::Content), Self::Escape(v) => v.eval(vm), Self::Shorthand(v) => v.eval(vm), Self::SmartQuote(v) => v.eval(vm).map(Value::Content), @@ -319,6 +377,8 @@ impl Eval for ast::Expr { }? .spanned(span); + if vm.traced == Some(span) { + vm.tracer.trace(v.clone()); } Ok(v) @@ -1049,7 +1109,7 @@ impl Eval for ast::LetBinding { Some(expr) => expr.eval(vm)?, None => Value::None, }; - vm.scopes.top.define(self.binding().take(), value); + vm.define(self.binding(), value); Ok(Value::None) } } @@ -1183,7 +1243,7 @@ impl Eval for ast::ForLoop { #[allow(unused_parens)] for ($($value),*) in $iter { - $(vm.scopes.top.define($binding.clone(), $value);)* + $(vm.define($binding.clone(), $value);)* let body = self.body(); let value = body.eval(vm)?; @@ -1206,8 +1266,8 @@ impl Eval for ast::ForLoop { let iter = self.iter().eval(vm)?; let pattern = self.pattern(); - let key = pattern.key().map(ast::Ident::take); - let value = pattern.value().take(); + let key = pattern.key(); + let value = pattern.value(); match (key, value, iter) { (None, v, Value::Str(string)) => { @@ -1271,7 +1331,7 @@ impl Eval for ast::ModuleImport { let mut errors = vec![]; for ident in idents { if let Some(value) = module.scope().get(&ident) { - vm.scopes.top.define(ident.take(), value.clone()); + vm.define(ident, value.clone()); } else { errors.push(error!(ident.span(), "unresolved import")); } diff --git a/src/model/func.rs b/src/model/func.rs index 00e59bbd..8cf3ea99 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -2,11 +2,11 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use comemo::{Track, Tracked}; +use comemo::{Track, Tracked, TrackedMut}; use super::{ Args, CastInfo, Dict, Eval, Flow, Node, NodeId, Route, Scope, Scopes, Selector, - StyleMap, Value, Vm, + StyleMap, Tracer, Value, Vm, }; use crate::diag::{bail, SourceResult, StrResult}; use crate::syntax::ast::{self, AstNode, Expr}; @@ -110,7 +110,7 @@ impl Func { } /// Call the function with the given arguments. - pub fn call(&self, vm: &Vm, mut args: Args) -> SourceResult<Value> { + pub fn call(&self, vm: &mut Vm, mut args: Args) -> SourceResult<Value> { let value = match self.0.as_ref() { Repr::Native(native) => (native.func)(vm, &mut args)?, Repr::Closure(closure) => closure.call(vm, self, &mut args)?, @@ -132,8 +132,9 @@ impl Func { let route = Route::default(); let id = SourceId::detached(); let scopes = Scopes::new(None); - let vm = Vm::new(world, route.track(), id, scopes, 0); - self.call(&vm, args) + let mut tracer = Tracer::default(); + let mut vm = Vm::new(world, route.track(), tracer.track_mut(), id, scopes, 0); + self.call(&mut vm, args) } /// Apply the given arguments to the function. @@ -292,7 +293,7 @@ pub(super) struct Closure { impl Closure { /// Call the function in the context with the arguments. - fn call(&self, vm: &Vm, this: &Func, args: &mut Args) -> SourceResult<Value> { + fn call(&self, vm: &mut Vm, this: &Func, args: &mut Args) -> SourceResult<Value> { // Don't leak the scopes from the call site. Instead, we use the scope // of captured variables we collected earlier. let mut scopes = Scopes::new(None); @@ -327,7 +328,14 @@ impl Closure { let route = if detached { fresh.track() } else { vm.route }; // Evaluate the body. - let mut sub = Vm::new(vm.world, route, self.location, scopes, vm.depth + 1); + let mut sub = Vm::new( + vm.world, + route, + TrackedMut::reborrow_mut(&mut vm.tracer), + self.location, + scopes, + vm.depth + 1, + ); let result = self.body.eval(&mut sub); // Handle control flow. diff --git a/src/model/methods.rs b/src/model/methods.rs index f80839f8..173b95fe 100644 --- a/src/model/methods.rs +++ b/src/model/methods.rs @@ -7,7 +7,7 @@ use crate::util::EcoString; /// Call a method on a value. pub fn call( - vm: &Vm, + vm: &mut Vm, value: Value, method: &str, mut args: Args, diff --git a/src/model/value.rs b/src/model/value.rs index 15656c42..ba3a550f 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -155,6 +155,14 @@ impl Value { _ => self.display(), } } + + /// Try to extract documentation for the value. + pub fn docs(&self) -> Option<&'static str> { + match self { + Self::Func(func) => func.info().map(|info| info.docs), + _ => None, + } + } } impl Default for Value { |
