diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-05-25 13:50:33 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-05-25 13:59:06 +0200 |
| commit | c010cbc17dcbb2f0d6005d21530143bf57cb5871 (patch) | |
| tree | 937fe79f0c121bcc025480181287fd4a3d0c0f4f /src/eval | |
| parent | 6935cf8dfefff3d6cf234f077a7d61661fd5ca57 (diff) | |
Move route from context to VM
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/args.rs | 7 | ||||
| -rw-r--r-- | src/eval/array.rs | 11 | ||||
| -rw-r--r-- | src/eval/dict.rs | 11 | ||||
| -rw-r--r-- | src/eval/func.rs | 46 | ||||
| -rw-r--r-- | src/eval/machine.rs | 28 | ||||
| -rw-r--r-- | src/eval/methods.rs | 11 | ||||
| -rw-r--r-- | src/eval/mod.rs | 96 | ||||
| -rw-r--r-- | src/eval/scope.rs | 5 |
8 files changed, 144 insertions, 71 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs index 9b21cfa2..69e6aaee 100644 --- a/src/eval/args.rs +++ b/src/eval/args.rs @@ -26,13 +26,8 @@ pub struct Arg { } impl Args { - /// Create empty arguments from a span. - pub fn new(span: Span) -> Self { - Self { span, items: vec![] } - } - /// Create positional arguments from a span and values. - pub fn from_values(span: Span, values: impl IntoIterator<Item = Value>) -> Self { + pub fn new(span: Span, values: impl IntoIterator<Item = Value>) -> Self { let items = values .into_iter() .map(|value| Arg { diff --git a/src/eval/array.rs b/src/eval/array.rs index 86347106..840c0aef 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -3,11 +3,10 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; use std::sync::Arc; -use super::{ops, Args, Func, Value}; +use super::{ops, Args, Func, Machine, Value}; use crate::diag::{At, StrResult, TypResult}; use crate::syntax::Spanned; use crate::util::ArcExt; -use crate::Context; /// Create a new [`Array`] from values. #[allow(unused_macros)] @@ -120,21 +119,21 @@ impl Array { } /// Transform each item in the array with a function. - pub fn map(&self, ctx: &mut Context, f: Spanned<Func>) -> TypResult<Self> { + pub fn map(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Self> { Ok(self .iter() .cloned() - .map(|item| f.v.call(ctx, Args::from_values(f.span, [item]))) + .map(|item| f.v.call(vm, Args::new(f.span, [item]))) .collect::<TypResult<_>>()?) } /// Return a new array with only those elements for which the function /// return true. - pub fn filter(&self, ctx: &mut Context, f: Spanned<Func>) -> TypResult<Self> { + pub fn filter(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Self> { let mut kept = vec![]; for item in self.iter() { if f.v - .call(ctx, Args::from_values(f.span, [item.clone()]))? + .call(vm, Args::new(f.span, [item.clone()]))? .cast::<bool>() .at(f.span)? { diff --git a/src/eval/dict.rs b/src/eval/dict.rs index 22b73e76..35bd75d5 100644 --- a/src/eval/dict.rs +++ b/src/eval/dict.rs @@ -3,12 +3,11 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; use std::sync::Arc; -use super::{Args, Array, Func, Value}; +use super::{Args, Array, Func, Machine, Value}; use crate::diag::{StrResult, TypResult}; use crate::parse::is_ident; use crate::syntax::Spanned; use crate::util::{ArcExt, EcoString}; -use crate::Context; /// Create a new [`Dict`] from key-value pairs. #[allow(unused_macros)] @@ -97,14 +96,12 @@ impl Dict { } /// Transform each pair in the array with a function. - pub fn map(&self, ctx: &mut Context, f: Spanned<Func>) -> TypResult<Array> { + pub fn map(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Array> { Ok(self .iter() .map(|(key, value)| { - f.v.call( - ctx, - Args::from_values(f.span, [Value::Str(key.clone()), value.clone()]), - ) + let args = Args::new(f.span, [Value::Str(key.clone()), value.clone()]); + f.v.call(vm, args) }) .collect::<TypResult<_>>()?) } diff --git a/src/eval/func.rs b/src/eval/func.rs index 83171ce7..16575c80 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -29,7 +29,7 @@ impl Func { /// Create a new function from a native rust function. pub fn from_fn( name: &'static str, - func: fn(&mut Context, &mut Args) -> TypResult<Value>, + func: fn(&mut Machine, &mut Args) -> TypResult<Value>, ) -> Self { Self(Arc::new(Repr::Native(Native { name, @@ -86,19 +86,25 @@ impl Func { } /// Call the function with the given arguments. - pub fn call(&self, ctx: &mut Context, mut args: Args) -> TypResult<Value> { + pub fn call(&self, vm: &mut Machine, mut args: Args) -> TypResult<Value> { let value = match self.0.as_ref() { - Repr::Native(native) => (native.func)(ctx, &mut args)?, - Repr::Closure(closure) => closure.call(ctx, &mut args)?, + Repr::Native(native) => (native.func)(vm, &mut args)?, + Repr::Closure(closure) => closure.call(vm, &mut args)?, Repr::With(wrapped, applied) => { args.items.splice(.. 0, applied.items.iter().cloned()); - return wrapped.call(ctx, args); + return wrapped.call(vm, args); } }; args.finish()?; Ok(value) } + /// Call the function without an existing virtual machine. + pub fn call_detached(&self, ctx: &mut Context, args: Args) -> TypResult<Value> { + let mut vm = Machine::new(ctx, vec![], Scopes::new(None)); + self.call(&mut vm, args) + } + /// Execute the function's set rule. pub fn set(&self, mut args: Args) -> TypResult<StyleMap> { let styles = match self.0.as_ref() { @@ -138,7 +144,7 @@ struct Native { /// The name of the function. pub name: &'static str, /// The function pointer. - pub func: fn(&mut Context, &mut Args) -> TypResult<Value>, + pub func: fn(&mut Machine, &mut Args) -> TypResult<Value>, /// The set rule. pub set: Option<fn(&mut Args) -> TypResult<StyleMap>>, /// The id of the node to customize with this function's show rule. @@ -163,7 +169,7 @@ pub trait Node: 'static { /// /// This is passed only the arguments that remain after execution of the /// node's set rule. - fn construct(ctx: &mut Context, args: &mut Args) -> TypResult<Content>; + fn construct(vm: &mut Machine, args: &mut Args) -> TypResult<Content>; /// Parse the arguments into style properties for this node. /// @@ -192,7 +198,7 @@ pub struct Closure { impl Closure { /// Call the function in the context with the arguments. - pub fn call(&self, ctx: &mut Context, args: &mut Args) -> TypResult<Value> { + pub fn call(&self, vm: &mut Machine, args: &mut Args) -> TypResult<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); @@ -213,24 +219,20 @@ impl Closure { scopes.top.def_mut(sink, args.take()); } - // Set the new route if we are detached. - let detached = ctx.route.is_empty(); - if detached { - ctx.route = self.location.into_iter().collect(); - } + // 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() + }; // Evaluate the body. - let mut vm = Machine::new(ctx, scopes); - let result = self.body.eval(&mut vm); - let flow = vm.flow; - - // Restore the old route. - if detached { - ctx.route.clear(); - } + let mut sub = Machine::new(vm.ctx, route, scopes); + let result = self.body.eval(&mut sub); // Handle control flow. - match flow { + match sub.flow { Some(Flow::Return(_, Some(explicit))) => return Ok(explicit), Some(Flow::Return(_, None)) => {} Some(flow) => return Err(flow.forbidden())?, diff --git a/src/eval/machine.rs b/src/eval/machine.rs index 168cedcb..14633978 100644 --- a/src/eval/machine.rs +++ b/src/eval/machine.rs @@ -1,12 +1,18 @@ +use std::path::PathBuf; + use super::{Scopes, Value}; -use crate::diag::TypError; +use crate::diag::{StrResult, TypError}; +use crate::source::SourceId; use crate::syntax::Span; +use crate::util::PathExt; use crate::Context; /// A virtual machine. pub struct Machine<'a> { /// The core context. pub ctx: &'a mut Context, + /// The route of source ids at which the machine is located. + pub route: Vec<SourceId>, /// The stack of scopes. pub scopes: Scopes<'a>, /// A control flow event that is currently happening. @@ -15,8 +21,24 @@ pub struct Machine<'a> { impl<'a> Machine<'a> { /// Create a new virtual machine. - pub fn new(ctx: &'a mut Context, scopes: Scopes<'a>) -> Self { - Self { ctx, scopes, flow: None } + pub fn new(ctx: &'a mut Context, route: Vec<SourceId>, scopes: Scopes<'a>) -> Self { + Self { ctx, route, 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(path) = path.strip_prefix('/') { + return Ok(self.ctx.config.root.join(path).normalize()); + } + + if let Some(dir) = self.ctx.sources.get(id).path().parent() { + return Ok(dir.join(path).normalize()); + } + } + + return Err("cannot access file system from here".into()); } } diff --git a/src/eval/methods.rs b/src/eval/methods.rs index 88f173c7..f6de614f 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -1,14 +1,13 @@ //! Methods on values. -use super::{Args, Regex, StrExt, Value}; +use super::{Args, Machine, Regex, StrExt, Value}; use crate::diag::{At, TypResult}; use crate::syntax::Span; use crate::util::EcoString; -use crate::Context; /// Call a method on a value. pub fn call( - ctx: &mut Context, + vm: &mut Machine, value: Value, method: &str, mut args: Args, @@ -35,8 +34,8 @@ pub fn call( } Value::Array(array.slice(start, end).at(span)?) } - "map" => Value::Array(array.map(ctx, args.expect("function")?)?), - "filter" => Value::Array(array.filter(ctx, args.expect("function")?)?), + "map" => Value::Array(array.map(vm, args.expect("function")?)?), + "filter" => Value::Array(array.filter(vm, args.expect("function")?)?), "flatten" => Value::Array(array.flatten()), "find" => array.find(args.expect("value")?).map_or(Value::None, Value::Int), "join" => { @@ -52,7 +51,7 @@ pub fn call( "len" => Value::Int(dict.len()), "keys" => Value::Array(dict.keys()), "values" => Value::Array(dict.values()), - "pairs" => Value::Array(dict.map(ctx, args.expect("function")?)?), + "pairs" => Value::Array(dict.map(vm, args.expect("function")?)?), _ => missing()?, }, diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 2b853586..702a76b2 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -43,13 +43,63 @@ use crate::syntax::{Span, Spanned}; use crate::util::EcoString; use crate::Context; -/// Evaluate an expression. -pub trait Eval { - /// The output of evaluating the expression. - type Output; +/// Evaluate a source file and return the resulting module. +/// +/// 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( + ctx: &mut Context, + id: SourceId, + mut route: Vec<SourceId>, +) -> TypResult<Module> { + // Prevent cyclic evaluation. + if route.contains(&id) { + let path = ctx.sources.get(id).path().display(); + panic!("Tried to cyclicly evaluate {}", path); + } - /// Evaluate the expression to the output value. - fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output>; + // Check whether the module was already evaluated. + if let Some(module) = ctx.modules.get(&id) { + if module.valid(&ctx.sources) { + return Ok(module.clone()); + } else { + ctx.modules.remove(&id); + } + } + + route.push(id); + + // Parse the file. + let source = ctx.sources.get(id); + let ast = source.ast()?; + + // Save the old dependencies. + let prev_deps = std::mem::replace(&mut ctx.deps, vec![(id, source.rev())]); + + // Evaluate the module. + let std = ctx.config.std.clone(); + let scopes = Scopes::new(Some(&std)); + let mut vm = Machine::new(ctx, route, scopes); + let result = ast.eval(&mut vm); + let scope = vm.scopes.top; + let flow = vm.flow; + + // Restore the and dependencies. + let deps = std::mem::replace(&mut ctx.deps, prev_deps); + + // Handle control flow. + if let Some(flow) = flow { + return Err(flow.forbidden()); + } + + // Assemble the module. + let module = Module { scope, content: result?, deps }; + + // Save the evaluated module. + ctx.modules.insert(id, module.clone()); + + Ok(module) } /// An evaluated module, ready for importing or layouting. @@ -70,6 +120,15 @@ impl Module { } } +/// Evaluate an expression. +pub trait Eval { + /// The output of evaluating the expression. + type Output; + + /// Evaluate the expression to the output value. + fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output>; +} + impl Eval for Markup { type Output = Content; @@ -553,7 +612,7 @@ impl Eval for FuncCall { Value::Dict(dict) => dict.get(&args.into_key()?).at(self.span())?.clone(), Value::Func(func) => { let point = || Tracepoint::Call(func.name().map(ToString::to_string)); - func.call(vm.ctx, args).trace(point, self.span())? + func.call(vm, args).trace(point, self.span())? } v => bail!( @@ -581,7 +640,7 @@ impl Eval for MethodCall { } else { let value = self.receiver().eval(vm)?; let args = self.args().eval(vm)?; - methods::call(vm.ctx, value, &method, args, span).trace(point, span)? + methods::call(vm, value, &method, args, span).trace(point, span)? }) } } @@ -672,7 +731,7 @@ impl Eval for ClosureExpr { // Define the actual function. Ok(Value::Func(Func::from_closure(Closure { - location: vm.ctx.route.last().copied(), + location: vm.route.last().copied(), name, captured, params, @@ -731,7 +790,7 @@ impl Eval for ShowExpr { let body = self.body(); let span = body.span(); let func = Func::from_closure(Closure { - location: vm.ctx.route.last().copied(), + location: vm.route.last().copied(), name: None, captured, params, @@ -875,7 +934,7 @@ impl Eval for ImportExpr { fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> { let span = self.path().span(); let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?; - let module = import(vm.ctx, &path, span)?; + let module = import(vm, &path, span)?; match self.imports() { Imports::Wildcard => { @@ -904,16 +963,16 @@ impl Eval for IncludeExpr { fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> { let span = self.path().span(); let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?; - let module = import(vm.ctx, &path, span)?; + let module = import(vm, &path, span)?; Ok(module.content.clone()) } } /// Process an import of a module relative to the current location. -fn import(ctx: &mut Context, path: &str, span: Span) -> TypResult<Module> { +fn import(vm: &mut Machine, path: &str, span: Span) -> TypResult<Module> { // Load the source file. - let full = ctx.locate(&path).at(span)?; - let id = ctx.sources.load(&full).map_err(|err| match err.kind() { + let full = vm.locate(&path).at(span)?; + let id = vm.ctx.sources.load(&full).map_err(|err| match err.kind() { std::io::ErrorKind::NotFound => { error!(span, "file not found (searched at {})", full.display()) } @@ -921,13 +980,14 @@ fn import(ctx: &mut Context, path: &str, span: Span) -> TypResult<Module> { })?; // Prevent cyclic importing. - if ctx.route.contains(&id) { + if vm.route.contains(&id) { bail!(span, "cyclic import"); } // Evaluate the file. - let module = ctx.evaluate(id).trace(|| Tracepoint::Import, span)?; - ctx.deps.extend(module.deps.iter().cloned()); + let route = vm.route.clone(); + let module = evaluate(vm.ctx, id, route).trace(|| Tracepoint::Import, span)?; + vm.ctx.deps.extend(module.deps.iter().cloned()); Ok(module) } diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 8acaa431..8a0b8165 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -6,10 +6,9 @@ use std::sync::Arc; use parking_lot::RwLock; -use super::{Args, Func, Node, Value}; +use super::{Args, Func, Machine, Node, Value}; use crate::diag::TypResult; use crate::util::EcoString; -use crate::Context; /// A slot where a variable is stored. pub type Slot = Arc<RwLock<Value>>; @@ -89,7 +88,7 @@ impl Scope { pub fn def_fn( &mut self, name: &'static str, - func: fn(&mut Context, &mut Args) -> TypResult<Value>, + func: fn(&mut Machine, &mut Args) -> TypResult<Value>, ) { self.def_const(name, Func::from_fn(name, func)); } |
