mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Capture variable by slot instead of value 🎣
This commit is contained in:
parent
67047047e8
commit
5943f552e5
@ -1,3 +1,5 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::syntax::visit::*;
|
use crate::syntax::visit::*;
|
||||||
|
|
||||||
@ -25,7 +27,7 @@ impl<'a> Visitor<'a> for CapturesVisitor<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn visit_def(&mut self, id: &mut Ident) {
|
fn visit_def(&mut self, id: &mut Ident) {
|
||||||
self.internal.define(id.as_str(), Value::None);
|
self.internal.def_mut(id.as_str(), Value::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_expr(&mut self, expr: &'a mut Expr) {
|
fn visit_expr(&mut self, expr: &'a mut Expr) {
|
||||||
@ -34,7 +36,7 @@ impl<'a> Visitor<'a> for CapturesVisitor<'a> {
|
|||||||
// captured, and if so, replace it with its value.
|
// captured, and if so, replace it with its value.
|
||||||
if self.internal.get(ident).is_none() {
|
if self.internal.get(ident).is_none() {
|
||||||
if let Some(value) = self.external.get(ident) {
|
if let Some(value) = self.external.get(ident) {
|
||||||
*expr = Expr::CapturedValue(value.clone());
|
*expr = Expr::Captured(Rc::clone(&value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -160,7 +160,7 @@ impl Eval for Spanned<&Expr> {
|
|||||||
match self.v {
|
match self.v {
|
||||||
Expr::None => Value::None,
|
Expr::None => Value::None,
|
||||||
Expr::Ident(v) => match ctx.scopes.get(v) {
|
Expr::Ident(v) => match ctx.scopes.get(v) {
|
||||||
Some(value) => value.clone(),
|
Some(slot) => slot.borrow().clone(),
|
||||||
None => {
|
None => {
|
||||||
ctx.diag(error!(self.span, "unknown variable"));
|
ctx.diag(error!(self.span, "unknown variable"));
|
||||||
Value::Error
|
Value::Error
|
||||||
@ -185,7 +185,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(),
|
Expr::Captured(v) => v.borrow().clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -341,30 +341,29 @@ 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;
|
||||||
|
|
||||||
match &self.v.lhs.v {
|
let slot = match &self.v.lhs.v {
|
||||||
Expr::Ident(id) => {
|
Expr::Ident(id) => match ctx.scopes.get(id) {
|
||||||
if let Some(slot) = ctx.scopes.get_mut(id) {
|
Some(slot) => slot,
|
||||||
*slot = op(std::mem::take(slot), rhs);
|
None => {
|
||||||
return Value::None;
|
|
||||||
} else if ctx.scopes.is_const(id) {
|
|
||||||
ctx.diag(error!(span, "cannot assign to a constant"));
|
|
||||||
} else {
|
|
||||||
ctx.diag(error!(span, "unknown variable"));
|
ctx.diag(error!(span, "unknown variable"));
|
||||||
|
return Value::Error;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
Expr::CapturedValue(_) => {
|
Expr::Captured(slot) => slot,
|
||||||
ctx.diag(error!(
|
|
||||||
span,
|
|
||||||
"cannot assign to captured expression in a template",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
ctx.diag(error!(span, "cannot assign to this expression"));
|
ctx.diag(error!(span, "cannot assign to this expression"));
|
||||||
|
return Value::Error;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(mut slot) = slot.try_borrow_mut() {
|
||||||
|
*slot = op(std::mem::take(&mut slot), rhs);
|
||||||
|
return Value::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.diag(error!(span, "cannot assign to a constant"));
|
||||||
Value::Error
|
Value::Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -377,7 +376,7 @@ impl Eval for Spanned<&ExprLet> {
|
|||||||
Some(expr) => expr.eval(ctx),
|
Some(expr) => expr.eval(ctx),
|
||||||
None => Value::None,
|
None => Value::None,
|
||||||
};
|
};
|
||||||
ctx.scopes.define(self.v.pat.v.as_str(), value);
|
ctx.scopes.def_mut(self.v.pat.v.as_str(), value);
|
||||||
Value::None
|
Value::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -418,7 +417,7 @@ impl Eval for Spanned<&ExprFor> {
|
|||||||
(for ($($binding:ident => $value:ident),*) in $iter:expr) => {
|
(for ($($binding:ident => $value:ident),*) in $iter:expr) => {
|
||||||
#[allow(unused_parens)]
|
#[allow(unused_parens)]
|
||||||
for ($($value),*) in $iter {
|
for ($($value),*) in $iter {
|
||||||
$(ctx.scopes.define($binding.as_str(), $value);)*
|
$(ctx.scopes.def_mut($binding.as_str(), $value);)*
|
||||||
|
|
||||||
if let Value::Template(new) = self.v.body.eval(ctx) {
|
if let Value::Template(new) = self.v.body.eval(ctx) {
|
||||||
output.extend(new);
|
output.extend(new);
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::Value;
|
use super::Value;
|
||||||
|
|
||||||
|
/// A slot where a variable is stored.
|
||||||
|
pub type Slot = Rc<RefCell<Value>>;
|
||||||
|
|
||||||
/// A stack of scopes.
|
/// A stack of scopes.
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
#[derive(Debug, Default, Clone, PartialEq)]
|
||||||
pub struct Scopes<'a> {
|
pub struct Scopes<'a> {
|
||||||
@ -34,38 +39,29 @@ impl<'a> Scopes<'a> {
|
|||||||
self.top = self.scopes.pop().expect("no pushed scope");
|
self.top = self.scopes.pop().expect("no pushed scope");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a variable in the active scope.
|
/// Define a constant variable in the active scope.
|
||||||
pub fn define(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
pub fn def_const(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
||||||
self.top.define(var, value);
|
self.top.def_const(var, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the value of a variable.
|
/// Define a mutable variable in the active scope.
|
||||||
pub fn get(&self, var: &str) -> Option<&Value> {
|
pub fn def_mut(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
||||||
|
self.top.def_mut(var, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look up the slot of a variable.
|
||||||
|
pub fn get(&self, var: &str) -> Option<&Slot> {
|
||||||
iter::once(&self.top)
|
iter::once(&self.top)
|
||||||
.chain(self.scopes.iter().rev())
|
.chain(self.scopes.iter().rev())
|
||||||
.chain(self.base.into_iter())
|
.chain(self.base.into_iter())
|
||||||
.find_map(|scope| scope.get(var))
|
.find_map(|scope| scope.get(var))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to a variable.
|
|
||||||
pub fn get_mut(&mut self, var: &str) -> Option<&mut Value> {
|
|
||||||
iter::once(&mut self.top)
|
|
||||||
.chain(self.scopes.iter_mut().rev())
|
|
||||||
.find_map(|scope| scope.get_mut(var))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return whether the variable is constant (not writable).
|
|
||||||
///
|
|
||||||
/// Defaults to `false` if the variable does not exist.
|
|
||||||
pub fn is_const(&self, var: &str) -> bool {
|
|
||||||
self.base.map_or(false, |base| base.get(var).is_some())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A map from variable names to values.
|
/// A map from variable names to variable slots.
|
||||||
#[derive(Default, Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
values: HashMap<String, Value>,
|
values: HashMap<String, Slot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
@ -74,20 +70,26 @@ impl Scope {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a new variable.
|
/// Define a constant variable.
|
||||||
pub fn define(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
pub fn def_const(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
||||||
self.values.insert(var.into(), value.into());
|
let cell = RefCell::new(value.into());
|
||||||
|
|
||||||
|
// Make it impossible to write to this value again.
|
||||||
|
// FIXME: Use Ref::leak once stable.
|
||||||
|
std::mem::forget(cell.borrow());
|
||||||
|
|
||||||
|
self.values.insert(var.into(), Rc::new(cell));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define a mutable variable.
|
||||||
|
pub fn def_mut(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
||||||
|
self.values.insert(var.into(), Rc::new(RefCell::new(value.into())));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the value of a variable.
|
/// Look up the value of a variable.
|
||||||
pub fn get(&self, var: &str) -> Option<&Value> {
|
pub fn get(&self, var: &str) -> Option<&Slot> {
|
||||||
self.values.get(var)
|
self.values.get(var)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to a variable.
|
|
||||||
pub fn get_mut(&mut self, var: &str) -> Option<&mut Value> {
|
|
||||||
self.values.get_mut(var)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Scope {
|
impl Debug for Scope {
|
||||||
|
@ -23,10 +23,10 @@ pub fn new() -> Scope {
|
|||||||
let mut std = Scope::new();
|
let mut std = Scope::new();
|
||||||
macro_rules! set {
|
macro_rules! set {
|
||||||
(func: $name:expr, $func:expr) => {
|
(func: $name:expr, $func:expr) => {
|
||||||
std.define($name, ValueFunc::new($name, $func))
|
std.def_const($name, ValueFunc::new($name, $func))
|
||||||
};
|
};
|
||||||
(any: $var:expr, $any:expr) => {
|
(any: $var:expr, $any:expr) => {
|
||||||
std.define($var, ValueAny::new($any))
|
std.def_const($var, ValueAny::new($any))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::color::RgbaColor;
|
use crate::color::RgbaColor;
|
||||||
use crate::eval::Value;
|
use crate::eval::Slot;
|
||||||
use crate::geom::{AngularUnit, LengthUnit};
|
use crate::geom::{AngularUnit, LengthUnit};
|
||||||
|
|
||||||
/// An expression.
|
/// An expression.
|
||||||
@ -51,11 +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.
|
/// A captured variable slot.
|
||||||
///
|
///
|
||||||
/// This node is never created by parsing. It only results from an in-place
|
/// This node is never created by parsing. It only results from an in-place
|
||||||
/// transformation of an identifier to a captured value.
|
/// transformation of an identifier to a captured variable.
|
||||||
CapturedValue(Value),
|
Captured(Slot),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pretty for Expr {
|
impl Pretty for Expr {
|
||||||
@ -88,7 +88,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),
|
Self::Captured(v) => v.borrow().pretty(p),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ pub fn walk_expr<'a, V: Visitor<'a>>(v: &mut V, expr: &'a mut Expr) {
|
|||||||
Expr::Let(e) => v.visit_let(e),
|
Expr::Let(e) => v.visit_let(e),
|
||||||
Expr::If(e) => v.visit_if(e),
|
Expr::If(e) => v.visit_if(e),
|
||||||
Expr::For(e) => v.visit_for(e),
|
Expr::For(e) => v.visit_for(e),
|
||||||
Expr::CapturedValue(_) => {}
|
Expr::Captured(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,8 +317,8 @@ fn register_helpers(scope: &mut Scope, panics: Rc<RefCell<Vec<Panic>>>) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
scope.define("f", ValueFunc::new("f", f));
|
scope.def_const("f", ValueFunc::new("f", f));
|
||||||
scope.define("test", ValueFunc::new("test", test));
|
scope.def_const("test", ValueFunc::new("test", test));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_diag(diag: &Spanned<Diag>, map: &LineMap, lines: u32) {
|
fn print_diag(diag: &Spanned<Diag>, map: &LineMap, lines: u32) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user