summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/capture.rs43
-rw-r--r--src/eval/mod.rs45
-rw-r--r--src/eval/scope.rs17
-rw-r--r--src/eval/value.rs10
4 files changed, 62 insertions, 53 deletions
diff --git a/src/eval/capture.rs b/src/eval/capture.rs
index 163aa24e..05760594 100644
--- a/src/eval/capture.rs
+++ b/src/eval/capture.rs
@@ -25,16 +25,11 @@ impl<'a> CapturesVisitor<'a> {
pub fn finish(self) -> Scope {
self.captures
}
-
- /// Define an internal variable.
- fn define(&mut self, ident: &Ident) {
- self.internal.def_mut(ident.as_str(), Value::None);
- }
}
impl<'ast> Visit<'ast> for CapturesVisitor<'_> {
- fn visit_expr(&mut self, item: &'ast Expr) {
- match item {
+ fn visit_expr(&mut self, node: &'ast Expr) {
+ match node {
Expr::Ident(ident) => {
// Find out whether the identifier is not locally defined, but
// captured, and if so, replace it with its value.
@@ -48,37 +43,15 @@ impl<'ast> Visit<'ast> for CapturesVisitor<'_> {
}
}
- fn visit_block(&mut self, item: &'ast ExprBlock) {
- // Blocks create a scope except if directly in a template.
- if item.scoping {
- self.internal.push();
- }
- visit_block(self, item);
- if item.scoping {
- self.internal.pop();
- }
- }
-
- fn visit_template(&mut self, item: &'ast ExprTemplate) {
- // Templates always create a scope.
- self.internal.push();
- visit_template(self, item);
- self.internal.pop();
+ fn visit_binding(&mut self, id: &'ast Ident) {
+ self.internal.def_mut(id.as_str(), Value::None);
}
- fn visit_let(&mut self, item: &'ast ExprLet) {
- self.define(&item.binding);
- visit_let(self, item);
+ fn visit_enter(&mut self) {
+ self.internal.enter();
}
- fn visit_for(&mut self, item: &'ast ExprFor) {
- match &item.pattern {
- ForPattern::Value(value) => self.define(value),
- ForPattern::KeyValue(key, value) => {
- self.define(key);
- self.define(value);
- }
- }
- visit_for(self, item);
+ fn visit_exit(&mut self) {
+ self.internal.exit();
}
}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index c66f2ad2..f30ee7a7 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -114,6 +114,7 @@ impl Eval for Expr {
Self::Group(v) => v.eval(ctx),
Self::Block(v) => v.eval(ctx),
Self::Call(v) => v.eval(ctx),
+ Self::Closure(v) => v.eval(ctx),
Self::Unary(v) => v.eval(ctx),
Self::Binary(v) => v.eval(ctx),
Self::Let(v) => v.eval(ctx),
@@ -184,7 +185,7 @@ impl Eval for ExprBlock {
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
if self.scoping {
- ctx.scopes.push();
+ ctx.scopes.enter();
}
let mut output = Value::None;
@@ -193,7 +194,7 @@ impl Eval for ExprBlock {
}
if self.scoping {
- ctx.scopes.pop();
+ ctx.scopes.exit();
}
output
@@ -386,6 +387,40 @@ impl Eval for ExprArg {
}
}
+impl Eval for ExprClosure {
+ type Output = Value;
+
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ let params = Rc::clone(&self.params);
+ let body = Rc::clone(&self.body);
+
+ // Collect the captured variables.
+ let captured = {
+ let mut visitor = CapturesVisitor::new(&ctx.scopes);
+ visitor.visit_closure(self);
+ visitor.finish()
+ };
+
+ Value::Func(ValueFunc::new(None, move |ctx, args| {
+ // Don't leak the scopes from the call site. Instead, we use the
+ // scope of captured variables we collected earlier.
+ let prev = std::mem::take(&mut ctx.scopes);
+ ctx.scopes.top = captured.clone();
+
+ for param in params.iter() {
+ // Set the parameter to `none` if the argument is missing.
+ let value =
+ args.require::<Value>(ctx, param.as_str()).unwrap_or_default();
+ ctx.scopes.def_mut(param.as_str(), value);
+ }
+
+ let value = body.eval(ctx);
+ ctx.scopes = prev;
+ value
+ }))
+ }
+}
+
impl Eval for ExprLet {
type Output = Value;
@@ -464,7 +499,7 @@ impl Eval for ExprFor {
macro_rules! iter {
(for ($($binding:ident => $value:ident),*) in $iter:expr) => {{
let mut output = vec![];
- ctx.scopes.push();
+ ctx.scopes.enter();
#[allow(unused_parens)]
for ($($value),*) in $iter {
@@ -474,14 +509,14 @@ impl Eval for ExprFor {
Value::Template(v) => output.extend(v),
Value::Str(v) => output.push(TemplateNode::Str(v)),
Value::Error => {
- ctx.scopes.pop();
+ ctx.scopes.exit();
return Value::Error;
}
_ => {}
}
}
- ctx.scopes.pop();
+ ctx.scopes.exit();
Value::Template(output)
}};
}
diff --git a/src/eval/scope.rs b/src/eval/scope.rs
index 0991564f..c0926c0c 100644
--- a/src/eval/scope.rs
+++ b/src/eval/scope.rs
@@ -13,11 +13,11 @@ pub type Slot = Rc<RefCell<Value>>;
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Scopes<'a> {
/// The active scope.
- top: Scope,
+ pub top: Scope,
/// The stack of lower scopes.
- scopes: Vec<Scope>,
+ pub scopes: Vec<Scope>,
/// The base scope.
- base: Option<&'a Scope>,
+ pub base: Option<&'a Scope>,
}
impl<'a> Scopes<'a> {
@@ -39,16 +39,16 @@ impl<'a> Scopes<'a> {
}
}
- /// Push a new scope.
- pub fn push(&mut self) {
+ /// Enter a new scope.
+ pub fn enter(&mut self) {
self.scopes.push(std::mem::take(&mut self.top));
}
- /// Pop the topmost scope.
+ /// Exit the topmost scope.
///
/// # Panics
- /// Panics if no scope was pushed.
- pub fn pop(&mut self) {
+ /// Panics if no scope was entered.
+ pub fn exit(&mut self) {
self.top = self.scopes.pop().expect("no pushed scope");
}
@@ -74,6 +74,7 @@ impl<'a> Scopes<'a> {
/// A map from variable names to variable slots.
#[derive(Default, Clone, PartialEq)]
pub struct Scope {
+ /// The mapping from names to slots.
values: HashMap<String, Slot>,
}
diff --git a/src/eval/value.rs b/src/eval/value.rs
index d910155a..7f31ea13 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -172,22 +172,22 @@ impl Debug for TemplateFunc {
/// A wrapper around a reference-counted executable function.
#[derive(Clone)]
pub struct ValueFunc {
- name: String,
+ name: Option<String>,
f: Rc<dyn Fn(&mut EvalContext, &mut ValueArgs) -> Value>,
}
impl ValueFunc {
/// Create a new function value from a rust function or closure.
- pub fn new<F>(name: impl Into<String>, f: F) -> Self
+ pub fn new<F>(name: Option<String>, f: F) -> Self
where
F: Fn(&mut EvalContext, &mut ValueArgs) -> Value + 'static,
{
- Self { name: name.into(), f: Rc::new(f) }
+ Self { name, f: Rc::new(f) }
}
/// The name of the function.
- pub fn name(&self) -> &str {
- &self.name
+ pub fn name(&self) -> Option<&str> {
+ self.name.as_deref()
}
}