summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/args.rs7
-rw-r--r--src/eval/array.rs11
-rw-r--r--src/eval/dict.rs11
-rw-r--r--src/eval/func.rs46
-rw-r--r--src/eval/machine.rs28
-rw-r--r--src/eval/methods.rs11
-rw-r--r--src/eval/mod.rs96
-rw-r--r--src/eval/scope.rs5
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));
}