Capture variables in templates 🔍

This commit is contained in:
Laurenz 2021-01-27 15:05:18 +01:00
parent 2641c2d20e
commit 2036663ed2
9 changed files with 291 additions and 23 deletions

View File

@ -40,7 +40,7 @@ impl<'a> EvalContext<'a> {
pub fn new(env: &'a mut Env, scope: &'a Scope, state: State) -> Self { pub fn new(env: &'a mut Env, scope: &'a Scope, state: State) -> Self {
Self { Self {
env, env,
scopes: Scopes::new(scope), scopes: Scopes::new(Some(scope)),
state, state,
groups: vec![], groups: vec![],
inner: vec![], inner: vec![],

View File

@ -7,6 +7,7 @@ mod context;
mod ops; mod ops;
mod scope; mod scope;
mod state; mod state;
mod template;
pub use call::*; pub use call::*;
pub use context::*; pub use context::*;
@ -174,7 +175,7 @@ impl Eval for Spanned<&Expr> {
Expr::Str(v) => Value::Str(v.clone()), Expr::Str(v) => Value::Str(v.clone()),
Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)), Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)),
Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)), Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)),
Expr::Template(v) => Value::Template(v.clone()), Expr::Template(v) => v.with_span(self.span).eval(ctx),
Expr::Group(v) => v.eval(ctx), Expr::Group(v) => v.eval(ctx),
Expr::Block(v) => v.with_span(self.span).eval(ctx), Expr::Block(v) => v.with_span(self.span).eval(ctx),
Expr::Call(v) => v.with_span(self.span).eval(ctx), Expr::Call(v) => v.with_span(self.span).eval(ctx),
@ -183,6 +184,7 @@ impl Eval for Spanned<&Expr> {
Expr::Let(v) => v.with_span(self.span).eval(ctx), Expr::Let(v) => v.with_span(self.span).eval(ctx),
Expr::If(v) => v.with_span(self.span).eval(ctx), Expr::If(v) => v.with_span(self.span).eval(ctx),
Expr::For(v) => v.with_span(self.span).eval(ctx), Expr::For(v) => v.with_span(self.span).eval(ctx),
Expr::CapturedValue(v) => v.clone(),
} }
} }
} }
@ -327,18 +329,28 @@ impl Spanned<&ExprBinary> {
let rhs = self.v.rhs.eval(ctx); let rhs = self.v.rhs.eval(ctx);
let span = self.v.lhs.span; let span = self.v.lhs.span;
if let Expr::Ident(id) = &self.v.lhs.v { match &self.v.lhs.v {
if let Some(slot) = ctx.scopes.get_mut(id) { Expr::Ident(id) => {
let lhs = std::mem::replace(slot, Value::None); if let Some(slot) = ctx.scopes.get_mut(id) {
*slot = op(lhs, rhs); *slot = op(std::mem::take(slot), rhs);
return Value::None; return Value::None;
} else if ctx.scopes.is_const(id) { } else if ctx.scopes.is_const(id) {
ctx.diag(error!(span, "cannot assign to constant")); ctx.diag(error!(span, "cannot assign to a constant"));
} else { } else {
ctx.diag(error!(span, "unknown variable")); ctx.diag(error!(span, "unknown variable"));
}
}
Expr::CapturedValue(_) => {
ctx.diag(error!(
span,
"cannot assign to captured expression in a template",
));
}
_ => {
ctx.diag(error!(span, "cannot assign to this expression"));
} }
} else {
ctx.diag(error!(span, "cannot assign to this expression"));
} }
Value::Error Value::Error
@ -421,7 +433,7 @@ impl Eval for Spanned<&ExprFor> {
(ForPattern::KeyValue(..), Value::Str(_)) (ForPattern::KeyValue(..), Value::Str(_))
| (ForPattern::KeyValue(..), Value::Array(_)) => { | (ForPattern::KeyValue(..), Value::Array(_)) => {
ctx.diag(error!(self.v.pat.span, "mismatched pattern",)); ctx.diag(error!(self.v.pat.span, "mismatched pattern"));
} }
(_, Value::Error) => {} (_, Value::Error) => {}

View File

@ -5,19 +5,19 @@ use std::iter;
use super::Value; use super::Value;
/// A stack of scopes. /// A stack of scopes.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Default, Clone, PartialEq)]
pub struct Scopes<'a> { pub struct Scopes<'a> {
/// The active scope. /// The active scope.
top: Scope, top: Scope,
/// The stack of lower scopes. /// The stack of lower scopes.
scopes: Vec<Scope>, scopes: Vec<Scope>,
/// The base scope. /// The base scope.
base: &'a Scope, base: Option<&'a Scope>,
} }
impl<'a> Scopes<'a> { impl<'a> Scopes<'a> {
/// Create a new hierarchy of scopes. /// Create a new hierarchy of scopes.
pub fn new(base: &'a Scope) -> Self { pub fn new(base: Option<&'a Scope>) -> Self {
Self { top: Scope::new(), scopes: vec![], base } Self { top: Scope::new(), scopes: vec![], base }
} }
@ -43,7 +43,7 @@ impl<'a> Scopes<'a> {
pub fn get(&self, var: &str) -> Option<&Value> { pub fn get(&self, var: &str) -> Option<&Value> {
iter::once(&self.top) iter::once(&self.top)
.chain(self.scopes.iter().rev()) .chain(self.scopes.iter().rev())
.chain(iter::once(self.base)) .chain(self.base.into_iter())
.find_map(|scope| scope.get(var)) .find_map(|scope| scope.get(var))
} }
@ -58,7 +58,7 @@ impl<'a> Scopes<'a> {
/// ///
/// Defaults to `false` if the variable does not exist. /// Defaults to `false` if the variable does not exist.
pub fn is_const(&self, var: &str) -> bool { pub fn is_const(&self, var: &str) -> bool {
self.base.get(var).is_some() self.base.map_or(false, |base| base.get(var).is_some())
} }
} }

56
src/eval/template.rs Normal file
View File

@ -0,0 +1,56 @@
use super::*;
use crate::syntax::visit::*;
impl Eval for Spanned<&ExprTemplate> {
type Output = Value;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let mut template = self.v.clone();
let mut visitor = CapturesVisitor::new(ctx);
visitor.visit_template(&mut template);
Value::Template(template)
}
}
/// A visitor that replaces all captured variables with their values.
struct CapturesVisitor<'a> {
external: &'a Scopes<'a>,
internal: Scopes<'a>,
}
impl<'a> CapturesVisitor<'a> {
fn new(ctx: &'a EvalContext) -> Self {
Self {
external: &ctx.scopes,
internal: Scopes::default(),
}
}
}
impl<'a> Visitor<'a> for CapturesVisitor<'a> {
fn visit_scope_pre(&mut self) {
self.internal.push();
}
fn visit_scope_post(&mut self) {
self.internal.pop();
}
fn visit_def(&mut self, id: &mut Ident) {
self.internal.define(id.as_str(), Value::None);
}
fn visit_expr(&mut self, expr: &'a mut Expr) {
if let Expr::Ident(ident) = expr {
// Find out whether the identifier is not locally defined, but
// captured, and if so, replace it with it's value.
if self.internal.get(ident).is_none() {
if let Some(value) = self.external.get(ident) {
*expr = Expr::CapturedValue(value.clone());
}
}
} else {
walk_expr(self, expr);
}
}
}

View File

@ -87,7 +87,14 @@ impl Eval for &Value {
ctx.push(ctx.make_text_node(match self { ctx.push(ctx.make_text_node(match self {
Value::None => return, Value::None => return,
Value::Str(s) => s.clone(), Value::Str(s) => s.clone(),
Value::Template(tree) => return tree.eval(ctx), Value::Template(tree) => {
// We do not want to allow the template access to the current
// scopes.
let prev = std::mem::take(&mut ctx.scopes);
tree.eval(ctx);
ctx.scopes = prev;
return;
}
other => pretty(other), other => pretty(other),
})); }));
} }
@ -195,7 +202,7 @@ impl Deref for ValueFunc {
impl Pretty for ValueFunc { impl Pretty for ValueFunc {
fn pretty(&self, p: &mut Printer) { fn pretty(&self, p: &mut Printer) {
write!(p, "(function {})", self.name).unwrap(); p.push_str(&self.name);
} }
} }
@ -515,7 +522,7 @@ mod tests {
test_pretty(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101"); test_pretty(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
test_pretty("hello", r#""hello""#); test_pretty("hello", r#""hello""#);
test_pretty(vec![Spanned::zero(Node::Strong)], "[*]"); test_pretty(vec![Spanned::zero(Node::Strong)], "[*]");
test_pretty(ValueFunc::new("nil", |_, _| Value::None), "(function nil)"); test_pretty(ValueFunc::new("nil", |_, _| Value::None), "nil");
test_pretty(ValueAny::new(1), "1"); test_pretty(ValueAny::new(1), "1");
test_pretty(Value::Error, "(error)"); test_pretty(Value::Error, "(error)");
} }

View File

@ -1,5 +1,6 @@
use super::*; use super::*;
use crate::color::RgbaColor; use crate::color::RgbaColor;
use crate::eval::Value;
use crate::geom::{AngularUnit, LengthUnit}; use crate::geom::{AngularUnit, LengthUnit};
/// An expression. /// An expression.
@ -50,6 +51,11 @@ pub enum Expr {
If(ExprIf), If(ExprIf),
/// A for expression: `#for x #in y { z }`. /// A for expression: `#for x #in y { z }`.
For(ExprFor), For(ExprFor),
/// A captured value.
///
/// This node is never created by parsing. It only results from an in-place
/// transformation of an identifier to a captured value.
CapturedValue(Value),
} }
impl Pretty for Expr { impl Pretty for Expr {
@ -86,6 +92,7 @@ impl Pretty for Expr {
Self::Let(v) => v.pretty(p), Self::Let(v) => v.pretty(p),
Self::If(v) => v.pretty(p), Self::If(v) => v.pretty(p),
Self::For(v) => v.pretty(p), Self::For(v) => v.pretty(p),
Self::CapturedValue(v) => v.pretty(p),
} }
} }
} }

View File

@ -5,6 +5,7 @@ mod ident;
mod node; mod node;
mod span; mod span;
mod token; mod token;
pub mod visit;
pub use expr::*; pub use expr::*;
pub use ident::*; pub use ident::*;

185
src/syntax/visit.rs Normal file
View File

@ -0,0 +1,185 @@
//! Syntax tree traversal.
use super::*;
/// Visits syntax tree nodes in a depth-first manner.
pub trait Visitor<'a>: Sized {
/// Visit a variable definition.
fn visit_def(&mut self, _ident: &'a mut Ident) {}
/// Visit the start of a scope.
fn visit_scope_pre(&mut self) {}
/// Visit the end of a scope.
fn visit_scope_post(&mut self) {}
fn visit_node(&mut self, node: &'a mut Node) {
walk_node(self, node)
}
fn visit_expr(&mut self, expr: &'a mut Expr) {
walk_expr(self, expr)
}
fn visit_array(&mut self, array: &'a mut ExprArray) {
walk_array(self, array)
}
fn visit_dict(&mut self, dict: &'a mut ExprDict) {
walk_dict(self, dict)
}
fn visit_template(&mut self, template: &'a mut ExprTemplate) {
walk_template(self, template)
}
fn visit_group(&mut self, group: &'a mut ExprGroup) {
walk_group(self, group)
}
fn visit_block(&mut self, block: &'a mut ExprBlock) {
walk_block(self, block)
}
fn visit_binary(&mut self, binary: &'a mut ExprBinary) {
walk_binary(self, binary)
}
fn visit_unary(&mut self, unary: &'a mut ExprUnary) {
walk_unary(self, unary)
}
fn visit_call(&mut self, call: &'a mut ExprCall) {
walk_call(self, call)
}
fn visit_arg(&mut self, arg: &'a mut Argument) {
walk_arg(self, arg)
}
fn visit_let(&mut self, expr_let: &'a mut ExprLet) {
walk_let(self, expr_let)
}
fn visit_if(&mut self, expr_if: &'a mut ExprIf) {
walk_if(self, expr_if)
}
fn visit_for(&mut self, expr_for: &'a mut ExprFor) {
walk_for(self, expr_for)
}
}
pub fn walk_node<'a, V: Visitor<'a>>(v: &mut V, node: &'a mut Node) {
match node {
Node::Strong => {}
Node::Emph => {}
Node::Space => {}
Node::Linebreak => {}
Node::Parbreak => {}
Node::Text(_) => {}
Node::Heading(_) => {}
Node::Raw(_) => {}
Node::Expr(expr) => v.visit_expr(expr),
}
}
pub fn walk_expr<'a, V: Visitor<'a>>(v: &mut V, expr: &'a mut Expr) {
match expr {
Expr::None => {}
Expr::Ident(_) => {}
Expr::Bool(_) => {}
Expr::Int(_) => {}
Expr::Float(_) => {}
Expr::Length(_, _) => {}
Expr::Angle(_, _) => {}
Expr::Percent(_) => {}
Expr::Color(_) => {}
Expr::Str(_) => {}
Expr::Array(e) => v.visit_array(e),
Expr::Dict(e) => v.visit_dict(e),
Expr::Template(e) => v.visit_template(e),
Expr::Group(e) => v.visit_group(e),
Expr::Block(e) => v.visit_block(e),
Expr::Unary(e) => v.visit_unary(e),
Expr::Binary(e) => v.visit_binary(e),
Expr::Call(e) => v.visit_call(e),
Expr::Let(e) => v.visit_let(e),
Expr::If(e) => v.visit_if(e),
Expr::For(e) => v.visit_for(e),
Expr::CapturedValue(_) => {}
}
}
pub fn walk_array<'a, V: Visitor<'a>>(v: &mut V, array: &'a mut ExprArray) {
for expr in array {
v.visit_expr(&mut expr.v);
}
}
pub fn walk_dict<'a, V: Visitor<'a>>(v: &mut V, dict: &'a mut ExprDict) {
for named in dict {
v.visit_expr(&mut named.expr.v);
}
}
pub fn walk_template<'a, V: Visitor<'a>>(v: &mut V, template: &'a mut ExprTemplate) {
v.visit_scope_pre();
for node in template {
v.visit_node(&mut node.v);
}
v.visit_scope_post();
}
pub fn walk_group<'a, V: Visitor<'a>>(v: &mut V, group: &'a mut ExprGroup) {
v.visit_expr(&mut group.v);
}
pub fn walk_block<'a, V: Visitor<'a>>(v: &mut V, block: &'a mut ExprBlock) {
if block.scopes {
v.visit_scope_pre();
}
for expr in &mut block.exprs {
v.visit_expr(&mut expr.v);
}
if block.scopes {
v.visit_scope_post();
}
}
pub fn walk_binary<'a, V: Visitor<'a>>(v: &mut V, binary: &'a mut ExprBinary) {
v.visit_expr(&mut binary.lhs.v);
v.visit_expr(&mut binary.rhs.v);
}
pub fn walk_unary<'a, V: Visitor<'a>>(v: &mut V, unary: &'a mut ExprUnary) {
v.visit_expr(&mut unary.expr.v);
}
pub fn walk_call<'a, V: Visitor<'a>>(v: &mut V, call: &'a mut ExprCall) {
v.visit_expr(&mut call.callee.v);
for arg in &mut call.args.v {
v.visit_arg(arg);
}
}
pub fn walk_arg<'a, V: Visitor<'a>>(v: &mut V, arg: &'a mut Argument) {
match arg {
Argument::Pos(expr) => v.visit_expr(&mut expr.v),
Argument::Named(named) => v.visit_expr(&mut named.expr.v),
}
}
pub fn walk_let<'a, V: Visitor<'a>>(v: &mut V, expr_let: &'a mut ExprLet) {
v.visit_def(&mut expr_let.pat.v);
if let Some(init) = &mut expr_let.init {
v.visit_expr(&mut init.v);
}
}
pub fn walk_if<'a, V: Visitor<'a>>(v: &mut V, expr_if: &'a mut ExprIf) {
v.visit_expr(&mut expr_if.condition.v);
v.visit_expr(&mut expr_if.if_body.v);
if let Some(body) = &mut expr_if.else_body {
v.visit_expr(&mut body.v);
}
}
pub fn walk_for<'a, V: Visitor<'a>>(v: &mut V, expr_for: &'a mut ExprFor) {
match &mut expr_for.pat.v {
ForPattern::Value(value) => v.visit_def(value),
ForPattern::KeyValue(key, value) => {
v.visit_def(key);
v.visit_def(value);
}
}
v.visit_expr(&mut expr_for.iter.v);
v.visit_expr(&mut expr_for.body.v);
}

View File

@ -86,7 +86,7 @@
// Error: 1:3-1:8 cannot assign to this expression // Error: 1:3-1:8 cannot assign to this expression
{ 1 + 2 = 3} { 1 + 2 = 3}
// Error: 1:3-1:6 cannot assign to constant // Error: 1:3-1:6 cannot assign to a constant
{ box = "hi" } { box = "hi" }
// Works if we define box before (since then it doesn't resolve to the standard // Works if we define box before (since then it doesn't resolve to the standard