diff options
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/capture.rs | 43 | ||||
| -rw-r--r-- | src/eval/mod.rs | 45 | ||||
| -rw-r--r-- | src/eval/scope.rs | 17 | ||||
| -rw-r--r-- | src/eval/value.rs | 10 |
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() } } |
