Closures and function definitions 🚀

Supports:
- Closure syntax: `(x, y) => z`
- Shorthand for a single argument: `x => y`
- Function syntax: `let f(x) = y`
- Capturing of variables from the environment
- Error messages for too few / many passed arguments

Does not support:
- Named arguments
- Variadic arguments with `..`
This commit is contained in:
Laurenz 2021-03-03 17:53:40 +01:00
parent 4d90a066f1
commit c94a18833f
17 changed files with 329 additions and 97 deletions

View File

@ -107,7 +107,7 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
The GUST Font License Version 1.0 applies to: The GUST Font License Version 1.0 applies to:
* Latin Modern Math font in fonts/LatinModernMath.otf * Latin Modern Math font in fonts/LatinModernMath.otf
http://www.gust.org.pl/projects/e-foundry/lm-math (http://www.gust.org.pl/projects/e-foundry/lm-math)
% This is version 1.0, dated 22 June 2009, of the GUST Font License. % This is version 1.0, dated 22 June 2009, of the GUST Font License.
% (GUST is the Polish TeX Users Group, http://www.gust.org.pl) % (GUST is the Polish TeX Users Group, http://www.gust.org.pl)
@ -145,12 +145,12 @@ The Creative Commons Attribution 4.0 International License applies to:
* Twitter Color Emoji font in fonts/TwitterColorEmoji.ttf * Twitter Color Emoji font in fonts/TwitterColorEmoji.ttf
Copyright 2016 Brad Erickson Copyright 2016 Brad Erickson
Copyright 2016 Twitter, Inc. Copyright 2016 Twitter, Inc.
https://github.com/eosrei/twemoji-color-font (https://github.com/eosrei/twemoji-color-font)
* The SVG icons in tools/test-helper/images * SVG icons in tools/test-helper/images
These are slightly modified versions of emojis from the Twemoji emoji set. These are slightly modified versions of emojis from the Twemoji emoji set.
Copyright 2020 Twitter, Inc and other contributors Copyright 2020 Twitter, Inc and other contributors
https://github.com/twitter/twemoji (https://github.com/twitter/twemoji)
Attribution 4.0 International Attribution 4.0 International

View File

@ -25,16 +25,11 @@ impl<'a> CapturesVisitor<'a> {
pub fn finish(self) -> Scope { pub fn finish(self) -> Scope {
self.captures 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<'_> { impl<'ast> Visit<'ast> for CapturesVisitor<'_> {
fn visit_expr(&mut self, item: &'ast Expr) { fn visit_expr(&mut self, node: &'ast Expr) {
match item { match node {
Expr::Ident(ident) => { Expr::Ident(ident) => {
// Find out whether the identifier is not locally defined, but // Find out whether the identifier is not locally defined, but
// captured, and if so, replace it with its value. // 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) { fn visit_binding(&mut self, id: &'ast Ident) {
// Blocks create a scope except if directly in a template. self.internal.def_mut(id.as_str(), Value::None);
if item.scoping {
self.internal.push();
}
visit_block(self, item);
if item.scoping {
self.internal.pop();
}
} }
fn visit_template(&mut self, item: &'ast ExprTemplate) { fn visit_enter(&mut self) {
// Templates always create a scope. self.internal.enter();
self.internal.push();
visit_template(self, item);
self.internal.pop();
} }
fn visit_let(&mut self, item: &'ast ExprLet) { fn visit_exit(&mut self) {
self.define(&item.binding); self.internal.exit();
visit_let(self, item);
}
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);
} }
} }

View File

@ -114,6 +114,7 @@ impl Eval for Expr {
Self::Group(v) => v.eval(ctx), Self::Group(v) => v.eval(ctx),
Self::Block(v) => v.eval(ctx), Self::Block(v) => v.eval(ctx),
Self::Call(v) => v.eval(ctx), Self::Call(v) => v.eval(ctx),
Self::Closure(v) => v.eval(ctx),
Self::Unary(v) => v.eval(ctx), Self::Unary(v) => v.eval(ctx),
Self::Binary(v) => v.eval(ctx), Self::Binary(v) => v.eval(ctx),
Self::Let(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 { fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
if self.scoping { if self.scoping {
ctx.scopes.push(); ctx.scopes.enter();
} }
let mut output = Value::None; let mut output = Value::None;
@ -193,7 +194,7 @@ impl Eval for ExprBlock {
} }
if self.scoping { if self.scoping {
ctx.scopes.pop(); ctx.scopes.exit();
} }
output 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 { impl Eval for ExprLet {
type Output = Value; type Output = Value;
@ -464,7 +499,7 @@ impl Eval for ExprFor {
macro_rules! iter { macro_rules! iter {
(for ($($binding:ident => $value:ident),*) in $iter:expr) => {{ (for ($($binding:ident => $value:ident),*) in $iter:expr) => {{
let mut output = vec![]; let mut output = vec![];
ctx.scopes.push(); ctx.scopes.enter();
#[allow(unused_parens)] #[allow(unused_parens)]
for ($($value),*) in $iter { for ($($value),*) in $iter {
@ -474,14 +509,14 @@ impl Eval for ExprFor {
Value::Template(v) => output.extend(v), Value::Template(v) => output.extend(v),
Value::Str(v) => output.push(TemplateNode::Str(v)), Value::Str(v) => output.push(TemplateNode::Str(v)),
Value::Error => { Value::Error => {
ctx.scopes.pop(); ctx.scopes.exit();
return Value::Error; return Value::Error;
} }
_ => {} _ => {}
} }
} }
ctx.scopes.pop(); ctx.scopes.exit();
Value::Template(output) Value::Template(output)
}}; }};
} }

View File

@ -13,11 +13,11 @@ pub type Slot = Rc<RefCell<Value>>;
#[derive(Debug, Default, Clone, PartialEq)] #[derive(Debug, Default, Clone, PartialEq)]
pub struct Scopes<'a> { pub struct Scopes<'a> {
/// The active scope. /// The active scope.
top: Scope, pub top: Scope,
/// The stack of lower scopes. /// The stack of lower scopes.
scopes: Vec<Scope>, pub scopes: Vec<Scope>,
/// The base scope. /// The base scope.
base: Option<&'a Scope>, pub base: Option<&'a Scope>,
} }
impl<'a> Scopes<'a> { impl<'a> Scopes<'a> {
@ -39,16 +39,16 @@ impl<'a> Scopes<'a> {
} }
} }
/// Push a new scope. /// Enter a new scope.
pub fn push(&mut self) { pub fn enter(&mut self) {
self.scopes.push(std::mem::take(&mut self.top)); self.scopes.push(std::mem::take(&mut self.top));
} }
/// Pop the topmost scope. /// Exit the topmost scope.
/// ///
/// # Panics /// # Panics
/// Panics if no scope was pushed. /// Panics if no scope was entered.
pub fn pop(&mut self) { pub fn exit(&mut self) {
self.top = self.scopes.pop().expect("no pushed scope"); 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. /// A map from variable names to variable slots.
#[derive(Default, Clone, PartialEq)] #[derive(Default, Clone, PartialEq)]
pub struct Scope { pub struct Scope {
/// The mapping from names to slots.
values: HashMap<String, Slot>, values: HashMap<String, Slot>,
} }

View File

@ -172,22 +172,22 @@ impl Debug for TemplateFunc {
/// A wrapper around a reference-counted executable function. /// A wrapper around a reference-counted executable function.
#[derive(Clone)] #[derive(Clone)]
pub struct ValueFunc { pub struct ValueFunc {
name: String, name: Option<String>,
f: Rc<dyn Fn(&mut EvalContext, &mut ValueArgs) -> Value>, f: Rc<dyn Fn(&mut EvalContext, &mut ValueArgs) -> Value>,
} }
impl ValueFunc { impl ValueFunc {
/// Create a new function value from a rust function or closure. /// 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 where
F: Fn(&mut EvalContext, &mut ValueArgs) -> Value + 'static, 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. /// The name of the function.
pub fn name(&self) -> &str { pub fn name(&self) -> Option<&str> {
&self.name self.name.as_deref()
} }
} }

View File

@ -23,7 +23,7 @@ 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.def_const($name, ValueFunc::new($name, $func)) std.def_const($name, ValueFunc::new(Some($name.into()), $func))
}; };
(any: $var:expr, $any:expr) => { (any: $var:expr, $any:expr) => {
std.def_const($var, ValueAny::new($any)) std.def_const($var, ValueAny::new($any))

View File

@ -173,21 +173,37 @@ fn primary(p: &mut Parser) -> Option<Expr> {
} }
match p.peek() { match p.peek() {
// Function or identifier. // Things that start with an identifier.
Some(Token::Ident(string)) => { Some(Token::Ident(string)) => {
let ident = Ident { let ident = Ident {
span: p.eat_span(), span: p.eat_span(),
string: string.into(), string: string.into(),
}; };
match p.peek_direct() { // Parenthesis or bracket means this is a function call.
Some(Token::LeftParen) | Some(Token::LeftBracket) => Some(call(p, ident)), if matches!(
_ => Some(Expr::Ident(ident)), p.peek_direct(),
Some(Token::LeftParen) | Some(Token::LeftBracket),
) {
return Some(call(p, ident));
} }
// Arrow means this is closure's lone parameter.
if p.eat_if(Token::Arrow) {
return expr(p).map(|body| {
Expr::Closure(ExprClosure {
span: ident.span.join(body.span()),
params: Rc::new(vec![ident]),
body: Rc::new(body),
})
});
}
Some(Expr::Ident(ident))
} }
// Structures. // Structures.
Some(Token::LeftParen) => Some(parenthesized(p)), Some(Token::LeftParen) => parenthesized(p),
Some(Token::LeftBracket) => Some(template(p)), Some(Token::LeftBracket) => Some(template(p)),
Some(Token::LeftBrace) => Some(block(p, true)), Some(Token::LeftBrace) => Some(block(p, true)),
@ -228,23 +244,36 @@ fn literal(p: &mut Parser) -> Option<Expr> {
Some(Expr::Lit(Lit { span: p.eat_span(), kind })) Some(Expr::Lit(Lit { span: p.eat_span(), kind }))
} }
/// Parse a parenthesized expression, which can be either of: /// Parse something that starts with a parenthesis, which can be either of:
/// - Array literal /// - Array literal
/// - Dictionary literal /// - Dictionary literal
/// - Parenthesized expression /// - Parenthesized expression
pub fn parenthesized(p: &mut Parser) -> Expr { /// - Parameter list of closure expression
pub fn parenthesized(p: &mut Parser) -> Option<Expr> {
p.start_group(Group::Paren, TokenMode::Code); p.start_group(Group::Paren, TokenMode::Code);
let colon = p.eat_if(Token::Colon); let colon = p.eat_if(Token::Colon);
let (items, has_comma) = collection(p); let (items, has_comma) = collection(p);
let span = p.end_group(); let span = p.end_group();
// Leading colon makes this a dictionary.
if colon { if colon {
// Leading colon makes this a dictionary. return Some(dict(p, items, span));
return dict(p, items, span); }
// Arrow means this is closure's parameter list.
if p.eat_if(Token::Arrow) {
let params = params(p, items);
return expr(p).map(|body| {
Expr::Closure(ExprClosure {
span: span.join(body.span()),
params: Rc::new(params),
body: Rc::new(body),
})
});
} }
// Find out which kind of collection this is. // Find out which kind of collection this is.
match items.as_slice() { Some(match items.as_slice() {
[] => array(p, items, span), [] => array(p, items, span),
[ExprArg::Pos(_)] if !has_comma => match items.into_iter().next() { [ExprArg::Pos(_)] if !has_comma => match items.into_iter().next() {
Some(ExprArg::Pos(expr)) => { Some(ExprArg::Pos(expr)) => {
@ -254,7 +283,7 @@ pub fn parenthesized(p: &mut Parser) -> Expr {
}, },
[ExprArg::Pos(_), ..] => array(p, items, span), [ExprArg::Pos(_), ..] => array(p, items, span),
[ExprArg::Named(_), ..] => dict(p, items, span), [ExprArg::Named(_), ..] => dict(p, items, span),
} })
} }
/// Parse a collection. /// Parse a collection.
@ -331,6 +360,19 @@ fn dict(p: &mut Parser, items: Vec<ExprArg>, span: Span) -> Expr {
Expr::Dict(ExprDict { span, items: items.collect() }) Expr::Dict(ExprDict { span, items: items.collect() })
} }
/// Convert a collection into a parameter list, producing errors for anything
/// other than identifiers.
fn params(p: &mut Parser, items: Vec<ExprArg>) -> Vec<Ident> {
let items = items.into_iter().filter_map(|item| match item {
ExprArg::Pos(Expr::Ident(id)) => Some(id),
_ => {
p.diag(error!(item.span(), "expected identifier"));
None
}
});
items.collect()
}
// Parse a template value: `[...]`. // Parse a template value: `[...]`.
fn template(p: &mut Parser) -> Expr { fn template(p: &mut Parser) -> Expr {
p.start_group(Group::Bracket, TokenMode::Markup); p.start_group(Group::Bracket, TokenMode::Markup);
@ -340,7 +382,7 @@ fn template(p: &mut Parser) -> Expr {
} }
/// Parse a block expression: `{...}`. /// Parse a block expression: `{...}`.
fn block(p: &mut Parser, scopes: bool) -> Expr { fn block(p: &mut Parser, scoping: bool) -> Expr {
p.start_group(Group::Brace, TokenMode::Code); p.start_group(Group::Brace, TokenMode::Code);
let mut exprs = vec![]; let mut exprs = vec![];
while !p.eof() { while !p.eof() {
@ -355,7 +397,7 @@ fn block(p: &mut Parser, scopes: bool) -> Expr {
p.skip_white(); p.skip_white();
} }
let span = p.end_group(); let span = p.end_group();
Expr::Block(ExprBlock { span, exprs, scoping: scopes }) Expr::Block(ExprBlock { span, exprs, scoping })
} }
/// Parse an expression. /// Parse an expression.
@ -445,16 +487,38 @@ fn expr_let(p: &mut Parser) -> Option<Expr> {
let mut expr_let = None; let mut expr_let = None;
if let Some(binding) = ident(p) { if let Some(binding) = ident(p) {
// If a parenthesis follows, this is a function definition.
let mut parameters = None;
if p.peek_direct() == Some(Token::LeftParen) {
p.start_group(Group::Paren, TokenMode::Code);
let items = collection(p).0;
parameters = Some(params(p, items));
p.end_group();
}
let mut init = None; let mut init = None;
if p.eat_if(Token::Eq) { if p.eat_if(Token::Eq) {
init = expr(p); init = expr(p);
} else if parameters.is_some() {
// Function definitions must have a body.
p.expected_at("body", p.end());
}
// Rewrite into a closure expression if it's a function definition.
if let Some(params) = parameters {
let body = init?;
init = Some(Expr::Closure(ExprClosure {
span: binding.span.join(body.span()),
params: Rc::new(params),
body: Rc::new(body),
}));
} }
expr_let = Some(Expr::Let(ExprLet { expr_let = Some(Expr::Let(ExprLet {
span: p.span(start), span: p.span(start),
binding, binding,
init: init.map(Box::new), init: init.map(Box::new),
})) }));
} }
expr_let expr_let

View File

@ -219,6 +219,7 @@ impl Pretty for Expr {
Self::Unary(v) => v.pretty(p), Self::Unary(v) => v.pretty(p),
Self::Binary(v) => v.pretty(p), Self::Binary(v) => v.pretty(p),
Self::Call(v) => v.pretty(p), Self::Call(v) => v.pretty(p),
Self::Closure(v) => v.pretty(p),
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::While(v) => v.pretty(p), Self::While(v) => v.pretty(p),
@ -383,6 +384,15 @@ impl Pretty for ExprArg {
} }
} }
impl Pretty for ExprClosure {
fn pretty(&self, p: &mut Printer) {
p.push('(');
p.join(self.params.iter(), ", ", |item, p| item.pretty(p));
p.push_str(") => ");
self.body.pretty(p);
}
}
impl Pretty for ExprLet { impl Pretty for ExprLet {
fn pretty(&self, p: &mut Printer) { fn pretty(&self, p: &mut Printer) {
p.push_str("let "); p.push_str("let ");
@ -529,8 +539,11 @@ impl Pretty for TemplateFunc {
impl Pretty for ValueFunc { impl Pretty for ValueFunc {
fn pretty(&self, p: &mut Printer) { fn pretty(&self, p: &mut Printer) {
p.push_str("<function "); p.push_str("<function");
p.push_str(self.name()); if let Some(name) = self.name() {
p.push(' ');
p.push_str(name);
}
p.push('>'); p.push('>');
} }
} }
@ -720,8 +733,12 @@ mod tests {
roundtrip("#v(1, 2)[*Ok*]"); roundtrip("#v(1, 2)[*Ok*]");
roundtrip("#v(1, f[2])"); roundtrip("#v(1, f[2])");
// Closures.
roundtrip("{(a, b) => a + b}");
// Keywords. // Keywords.
roundtrip("#let x = 1 + 2"); roundtrip("#let x = 1 + 2");
test_parse("#let f(x) = y", "#let f = (x) => y");
test_parse("#if x [y] #else [z]", "#if x [y] else [z]"); test_parse("#if x [y] #else [z]", "#if x [y] else [z]");
roundtrip("#while x {y}"); roundtrip("#while x {y}");
roundtrip("#for x in y {z}"); roundtrip("#for x in y {z}");
@ -777,8 +794,14 @@ mod tests {
"[*<node example>]", "[*<node example>]",
); );
// Function and arguments. // Function.
test_value(ValueFunc::new("nil", |_, _| Value::None), "<function nil>"); test_value(ValueFunc::new(None, |_, _| Value::None), "<function>");
test_value(
ValueFunc::new(Some("nil".into()), |_, _| Value::None),
"<function nil>",
);
// Arguments.
test_value( test_value(
ValueArgs { ValueArgs {
span: Span::ZERO, span: Span::ZERO,

View File

@ -25,8 +25,10 @@ pub enum Expr {
Unary(ExprUnary), Unary(ExprUnary),
/// A binary operation: `a + b`. /// A binary operation: `a + b`.
Binary(ExprBinary), Binary(ExprBinary),
/// An invocation of a function: `foo(...)`. /// An invocation of a function: `f(x, y)`.
Call(ExprCall), Call(ExprCall),
/// A closure expression: `(x, y) => { z }`.
Closure(ExprClosure),
/// A let expression: `let x = 1`. /// A let expression: `let x = 1`.
Let(ExprLet), Let(ExprLet),
/// An if expression: `if x { y } else { z }`. /// An if expression: `if x { y } else { z }`.
@ -51,6 +53,7 @@ impl Expr {
Self::Unary(v) => v.span, Self::Unary(v) => v.span,
Self::Binary(v) => v.span, Self::Binary(v) => v.span,
Self::Call(v) => v.span, Self::Call(v) => v.span,
Self::Closure(v) => v.span,
Self::Let(v) => v.span, Self::Let(v) => v.span,
Self::If(v) => v.span, Self::If(v) => v.span,
Self::While(v) => v.span, Self::While(v) => v.span,
@ -58,7 +61,7 @@ impl Expr {
} }
} }
/// Whether the expression can be shorten in markup with a hashtag. /// Whether the expression can be shortened in markup with a hashtag.
pub fn has_short_form(&self) -> bool { pub fn has_short_form(&self) -> bool {
matches!(self, matches!(self,
Expr::Ident(_) Expr::Ident(_)
@ -411,6 +414,17 @@ impl ExprArg {
} }
} }
/// A closure expression: `(x, y) => { z }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprClosure {
/// The source code location.
pub span: Span,
/// The parameter bindings.
pub params: Rc<Vec<Ident>>,
/// The body of the closure.
pub body: Rc<Expr>,
}
/// A let expression: `let x = 1`. /// A let expression: `let x = 1`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ExprLet { pub struct ExprLet {
@ -418,7 +432,7 @@ pub struct ExprLet {
pub span: Span, pub span: Span,
/// The binding to assign to. /// The binding to assign to.
pub binding: Ident, pub binding: Ident,
/// The expression the pattern is initialized with. /// The expression the binding is initialized with.
pub init: Option<Box<Expr>>, pub init: Option<Box<Expr>>,
} }

View File

@ -3,27 +3,42 @@
use super::*; use super::*;
macro_rules! visit { macro_rules! visit {
($(fn $name:ident($v:ident, $node:ident: &$ty:ty) $body:block)*) => { ($(fn $name:ident($v:ident $(, $node:ident: &$ty:ty)?) $body:block)*) => {
/// Traverses the syntax tree. /// Traverses the syntax tree.
pub trait Visit<'ast> { pub trait Visit<'ast> {
$(fn $name(&mut self, $node: &'ast $ty) { $(fn $name(&mut self $(, $node: &'ast $ty)?) {
$name(self, $node); $name(self, $($node)?);
})* })*
/// Visit a definition of a binding.
///
/// Bindings are, for example, left-hand side of let expressions,
/// and key/value patterns in for loops.
fn visit_binding(&mut self, _: &'ast Ident) {}
/// Visit the entry into a scope.
fn visit_enter(&mut self) {}
/// Visit the exit from a scope.
fn visit_exit(&mut self) {}
} }
$(visit! { $(visit! {
@concat!("Walk a node of type [`", stringify!($ty), "`]."), @$(concat!("Walk a node of type [`", stringify!($ty), "`]."), )?
pub fn $name<'ast, V>($v: &mut V, $node: &'ast $ty) pub fn $name<'ast, V>(
#[allow(unused)] $v: &mut V
$(, #[allow(unused)] $node: &'ast $ty)?
)
where where
V: Visit<'ast> + ?Sized V: Visit<'ast> + ?Sized
$body $body
})* })*
}; };
(@$doc:expr, $($tts:tt)*) => { (@$doc:expr, $($tts:tt)*) => {
#[doc = $doc] #[doc = $doc]
$($tts)* $($tts)*
} };
} }
visit! { visit! {
@ -59,6 +74,7 @@ visit! {
Expr::Unary(e) => v.visit_unary(e), Expr::Unary(e) => v.visit_unary(e),
Expr::Binary(e) => v.visit_binary(e), Expr::Binary(e) => v.visit_binary(e),
Expr::Call(e) => v.visit_call(e), Expr::Call(e) => v.visit_call(e),
Expr::Closure(e) => v.visit_closure(e),
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::While(e) => v.visit_while(e), Expr::While(e) => v.visit_while(e),
@ -79,7 +95,9 @@ visit! {
} }
fn visit_template(v, node: &ExprTemplate) { fn visit_template(v, node: &ExprTemplate) {
v.visit_enter();
v.visit_tree(&node.tree); v.visit_tree(&node.tree);
v.visit_exit();
} }
fn visit_group(v, node: &ExprGroup) { fn visit_group(v, node: &ExprGroup) {
@ -87,9 +105,15 @@ visit! {
} }
fn visit_block(v, node: &ExprBlock) { fn visit_block(v, node: &ExprBlock) {
if node.scoping {
v.visit_enter();
}
for expr in &node.exprs { for expr in &node.exprs {
v.visit_expr(&expr); v.visit_expr(&expr);
} }
if node.scoping {
v.visit_exit();
}
} }
fn visit_binary(v, node: &ExprBinary) { fn visit_binary(v, node: &ExprBinary) {
@ -106,6 +130,13 @@ visit! {
v.visit_args(&node.args); v.visit_args(&node.args);
} }
fn visit_closure(v, node: &ExprClosure) {
for param in node.params.iter() {
v.visit_binding(param);
}
v.visit_expr(&node.body);
}
fn visit_args(v, node: &ExprArgs) { fn visit_args(v, node: &ExprArgs) {
for arg in &node.items { for arg in &node.items {
v.visit_arg(arg); v.visit_arg(arg);
@ -120,6 +151,7 @@ visit! {
} }
fn visit_let(v, node: &ExprLet) { fn visit_let(v, node: &ExprLet) {
v.visit_binding(&node.binding);
if let Some(init) = &node.init { if let Some(init) = &node.init {
v.visit_expr(&init); v.visit_expr(&init);
} }
@ -139,6 +171,13 @@ visit! {
} }
fn visit_for(v, node: &ExprFor) { fn visit_for(v, node: &ExprFor) {
match &node.pattern {
ForPattern::Value(value) => v.visit_binding(value),
ForPattern::KeyValue(key, value) => {
v.visit_binding(key);
v.visit_binding(value);
}
}
v.visit_expr(&node.iter); v.visit_expr(&node.iter);
v.visit_expr(&node.body); v.visit_expr(&node.body);
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -57,9 +57,6 @@
#test(type(for v in () []), "template") #test(type(for v in () []), "template")
--- ---
// Error: 14-19 unknown variable
#let error = error
// Uniterable expression. // Uniterable expression.
// Error: 11-15 cannot loop over boolean // Error: 11-15 cannot loop over boolean
#for v in true {} #for v in true {}

View File

@ -7,9 +7,31 @@
#let x #let x
#test(x, none) #test(x, none)
// Error: 9 expected expression
#let y =
#test(y, none)
// Manually initialized with one. // Manually initialized with one.
#let x = 1 #let z = 1
#test(x, 1) #test(z, 1)
---
// Syntax sugar for function definitions.
#let background = #239dad
#let box(body) = box(width: 2cm, height: 1cm, color: background, body)
#box[Hi!]
// Error: 13 expected body
#let func(x)
// Error: 2-6 unknown variable
{func}
// Error: 15 expected expression
#let func(x) =
// Error: 2-6 unknown variable
{func}
--- ---
// Termination. // Termination.

View File

@ -26,9 +26,6 @@
#test(type(while false []), "template") #test(type(while false []), "template")
--- ---
// Error: 14-19 unknown variable
#let error = error
// Condition must be boolean. // Condition must be boolean.
// Error: 8-14 expected boolean, found template // Error: 8-14 expected boolean, found template
#while [nope] [nope] #while [nope] [nope]

View File

@ -0,0 +1,66 @@
// Test closures.
---
// Ref: false
// Basic closure without captures.
{
let adder = (x, y) => x + y
test(adder(2, 3), 5)
}
// Pass closure as argument and return closure.
// Also uses shorthand syntax for a single argument.
{
let chain = (f, g) => (x) => f(g(x))
let f = x => x + 1
let g = x => 2 * x
let h = chain(f, g)
test(h(2), 5)
}
// Capture environment.
{
let mark = "?"
let greet = {
let hi = "Hi"
name => {
hi + ", " + name + mark
}
}
test(greet("Typst"), "Hi, Typst?")
mark = "!"
test(greet("Typst"), "Hi, Typst!")
}
// Don't leak environment.
{
// Error: 18-19 unknown variable
let func() = x
let x = "hi"
test(func(), error)
}
---
// Ref: false
// Too few arguments.
{
let types(x, y) = "[" + type(x) + ", " + type(y) + "]"
test(types(14%, 12pt), "[relative, length]")
// Error: 16-22 missing argument: y
test(types("nope"), "[string, none]")
}
// Too many arguments.
{
let f(x) = x + 1
// Error: 2:10-2:15 unexpected argument
// Error: 1:17-1:24 unexpected argument
f(1, "two", () => x)
}

View File

@ -315,8 +315,9 @@ fn register_helpers(scope: &mut Scope, panics: Rc<RefCell<Vec<Panic>>>) {
} }
}; };
scope.def_const("args", ValueFunc::new("args", args)); scope.def_const("error", Value::Error);
scope.def_const("test", ValueFunc::new("test", test)); scope.def_const("args", ValueFunc::new(Some("args".into()), args));
scope.def_const("test", ValueFunc::new(Some("test".into()), test));
} }
fn print_diag(diag: &Diag, map: &LineMap, lines: u32) { fn print_diag(diag: &Diag, map: &LineMap, lines: u32) {

View File

@ -304,7 +304,7 @@
}, },
{ {
"name": "constant.other.color.typst", "name": "constant.other.color.typst",
"match": "\\b#[0-9a-zA-Z]+\\b" "match": "#[0-9a-zA-Z]+\\b"
}, },
{ {
"name": "string.quoted.double.typst", "name": "string.quoted.double.typst",