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:
parent
4d90a066f1
commit
c94a18833f
@ -107,7 +107,7 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
The GUST Font License Version 1.0 applies to:
|
||||
|
||||
* 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.
|
||||
% (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
|
||||
Copyright 2016 Brad Erickson
|
||||
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.
|
||||
Copyright 2020 Twitter, Inc and other contributors
|
||||
https://github.com/twitter/twemoji
|
||||
(https://github.com/twitter/twemoji)
|
||||
|
||||
Attribution 4.0 International
|
||||
|
||||
|
@ -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_binding(&mut self, id: &'ast Ident) {
|
||||
self.internal.def_mut(id.as_str(), Value::None);
|
||||
}
|
||||
|
||||
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_enter(&mut self) {
|
||||
self.internal.enter();
|
||||
}
|
||||
|
||||
fn visit_let(&mut self, item: &'ast ExprLet) {
|
||||
self.define(&item.binding);
|
||||
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);
|
||||
fn visit_exit(&mut self) {
|
||||
self.internal.exit();
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}};
|
||||
}
|
||||
|
@ -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>,
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ pub fn new() -> Scope {
|
||||
let mut std = Scope::new();
|
||||
macro_rules! set {
|
||||
(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) => {
|
||||
std.def_const($var, ValueAny::new($any))
|
||||
|
@ -173,21 +173,37 @@ fn primary(p: &mut Parser) -> Option<Expr> {
|
||||
}
|
||||
|
||||
match p.peek() {
|
||||
// Function or identifier.
|
||||
// Things that start with an identifier.
|
||||
Some(Token::Ident(string)) => {
|
||||
let ident = Ident {
|
||||
span: p.eat_span(),
|
||||
string: string.into(),
|
||||
};
|
||||
|
||||
match p.peek_direct() {
|
||||
Some(Token::LeftParen) | Some(Token::LeftBracket) => Some(call(p, ident)),
|
||||
_ => Some(Expr::Ident(ident)),
|
||||
// Parenthesis or bracket means this is a function call.
|
||||
if matches!(
|
||||
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.
|
||||
Some(Token::LeftParen) => Some(parenthesized(p)),
|
||||
Some(Token::LeftParen) => parenthesized(p),
|
||||
Some(Token::LeftBracket) => Some(template(p)),
|
||||
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 }))
|
||||
}
|
||||
|
||||
/// Parse a parenthesized expression, which can be either of:
|
||||
/// Parse something that starts with a parenthesis, which can be either of:
|
||||
/// - Array literal
|
||||
/// - Dictionary literal
|
||||
/// - 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);
|
||||
let colon = p.eat_if(Token::Colon);
|
||||
let (items, has_comma) = collection(p);
|
||||
let span = p.end_group();
|
||||
|
||||
// Leading colon makes this a dictionary.
|
||||
if colon {
|
||||
// Leading colon makes this a dictionary.
|
||||
return dict(p, items, span);
|
||||
return Some(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.
|
||||
match items.as_slice() {
|
||||
Some(match items.as_slice() {
|
||||
[] => array(p, items, span),
|
||||
[ExprArg::Pos(_)] if !has_comma => match items.into_iter().next() {
|
||||
Some(ExprArg::Pos(expr)) => {
|
||||
@ -254,7 +283,7 @@ pub fn parenthesized(p: &mut Parser) -> Expr {
|
||||
},
|
||||
[ExprArg::Pos(_), ..] => array(p, items, span),
|
||||
[ExprArg::Named(_), ..] => dict(p, items, span),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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() })
|
||||
}
|
||||
|
||||
/// 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: `[...]`.
|
||||
fn template(p: &mut Parser) -> Expr {
|
||||
p.start_group(Group::Bracket, TokenMode::Markup);
|
||||
@ -340,7 +382,7 @@ fn template(p: &mut Parser) -> Expr {
|
||||
}
|
||||
|
||||
/// 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);
|
||||
let mut exprs = vec![];
|
||||
while !p.eof() {
|
||||
@ -355,7 +397,7 @@ fn block(p: &mut Parser, scopes: bool) -> Expr {
|
||||
p.skip_white();
|
||||
}
|
||||
let span = p.end_group();
|
||||
Expr::Block(ExprBlock { span, exprs, scoping: scopes })
|
||||
Expr::Block(ExprBlock { span, exprs, scoping })
|
||||
}
|
||||
|
||||
/// Parse an expression.
|
||||
@ -445,16 +487,38 @@ fn expr_let(p: &mut Parser) -> Option<Expr> {
|
||||
|
||||
let mut expr_let = None;
|
||||
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;
|
||||
if p.eat_if(Token::Eq) {
|
||||
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 {
|
||||
span: p.span(start),
|
||||
binding,
|
||||
init: init.map(Box::new),
|
||||
}))
|
||||
}));
|
||||
}
|
||||
|
||||
expr_let
|
||||
|
@ -219,6 +219,7 @@ impl Pretty for Expr {
|
||||
Self::Unary(v) => v.pretty(p),
|
||||
Self::Binary(v) => v.pretty(p),
|
||||
Self::Call(v) => v.pretty(p),
|
||||
Self::Closure(v) => v.pretty(p),
|
||||
Self::Let(v) => v.pretty(p),
|
||||
Self::If(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 {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str("let ");
|
||||
@ -529,8 +539,11 @@ impl Pretty for TemplateFunc {
|
||||
|
||||
impl Pretty for ValueFunc {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str("<function ");
|
||||
p.push_str(self.name());
|
||||
p.push_str("<function");
|
||||
if let Some(name) = self.name() {
|
||||
p.push(' ');
|
||||
p.push_str(name);
|
||||
}
|
||||
p.push('>');
|
||||
}
|
||||
}
|
||||
@ -720,8 +733,12 @@ mod tests {
|
||||
roundtrip("#v(1, 2)[*Ok*]");
|
||||
roundtrip("#v(1, f[2])");
|
||||
|
||||
// Closures.
|
||||
roundtrip("{(a, b) => a + b}");
|
||||
|
||||
// Keywords.
|
||||
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]");
|
||||
roundtrip("#while x {y}");
|
||||
roundtrip("#for x in y {z}");
|
||||
@ -777,8 +794,14 @@ mod tests {
|
||||
"[*<node example>]",
|
||||
);
|
||||
|
||||
// Function and arguments.
|
||||
test_value(ValueFunc::new("nil", |_, _| Value::None), "<function nil>");
|
||||
// Function.
|
||||
test_value(ValueFunc::new(None, |_, _| Value::None), "<function>");
|
||||
test_value(
|
||||
ValueFunc::new(Some("nil".into()), |_, _| Value::None),
|
||||
"<function nil>",
|
||||
);
|
||||
|
||||
// Arguments.
|
||||
test_value(
|
||||
ValueArgs {
|
||||
span: Span::ZERO,
|
||||
|
@ -25,8 +25,10 @@ pub enum Expr {
|
||||
Unary(ExprUnary),
|
||||
/// A binary operation: `a + b`.
|
||||
Binary(ExprBinary),
|
||||
/// An invocation of a function: `foo(...)`.
|
||||
/// An invocation of a function: `f(x, y)`.
|
||||
Call(ExprCall),
|
||||
/// A closure expression: `(x, y) => { z }`.
|
||||
Closure(ExprClosure),
|
||||
/// A let expression: `let x = 1`.
|
||||
Let(ExprLet),
|
||||
/// An if expression: `if x { y } else { z }`.
|
||||
@ -51,6 +53,7 @@ impl Expr {
|
||||
Self::Unary(v) => v.span,
|
||||
Self::Binary(v) => v.span,
|
||||
Self::Call(v) => v.span,
|
||||
Self::Closure(v) => v.span,
|
||||
Self::Let(v) => v.span,
|
||||
Self::If(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 {
|
||||
matches!(self,
|
||||
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`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ExprLet {
|
||||
@ -418,7 +432,7 @@ pub struct ExprLet {
|
||||
pub span: Span,
|
||||
/// The binding to assign to.
|
||||
pub binding: Ident,
|
||||
/// The expression the pattern is initialized with.
|
||||
/// The expression the binding is initialized with.
|
||||
pub init: Option<Box<Expr>>,
|
||||
}
|
||||
|
||||
|
@ -3,27 +3,42 @@
|
||||
use super::*;
|
||||
|
||||
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.
|
||||
pub trait Visit<'ast> {
|
||||
$(fn $name(&mut self, $node: &'ast $ty) {
|
||||
$name(self, $node);
|
||||
$(fn $name(&mut self $(, $node: &'ast $ty)?) {
|
||||
$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! {
|
||||
@concat!("Walk a node of type [`", stringify!($ty), "`]."),
|
||||
pub fn $name<'ast, V>($v: &mut V, $node: &'ast $ty)
|
||||
@$(concat!("Walk a node of type [`", stringify!($ty), "`]."), )?
|
||||
pub fn $name<'ast, V>(
|
||||
#[allow(unused)] $v: &mut V
|
||||
$(, #[allow(unused)] $node: &'ast $ty)?
|
||||
)
|
||||
where
|
||||
V: Visit<'ast> + ?Sized
|
||||
$body
|
||||
})*
|
||||
};
|
||||
|
||||
(@$doc:expr, $($tts:tt)*) => {
|
||||
#[doc = $doc]
|
||||
$($tts)*
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
visit! {
|
||||
@ -59,6 +74,7 @@ visit! {
|
||||
Expr::Unary(e) => v.visit_unary(e),
|
||||
Expr::Binary(e) => v.visit_binary(e),
|
||||
Expr::Call(e) => v.visit_call(e),
|
||||
Expr::Closure(e) => v.visit_closure(e),
|
||||
Expr::Let(e) => v.visit_let(e),
|
||||
Expr::If(e) => v.visit_if(e),
|
||||
Expr::While(e) => v.visit_while(e),
|
||||
@ -79,7 +95,9 @@ visit! {
|
||||
}
|
||||
|
||||
fn visit_template(v, node: &ExprTemplate) {
|
||||
v.visit_enter();
|
||||
v.visit_tree(&node.tree);
|
||||
v.visit_exit();
|
||||
}
|
||||
|
||||
fn visit_group(v, node: &ExprGroup) {
|
||||
@ -87,9 +105,15 @@ visit! {
|
||||
}
|
||||
|
||||
fn visit_block(v, node: &ExprBlock) {
|
||||
if node.scoping {
|
||||
v.visit_enter();
|
||||
}
|
||||
for expr in &node.exprs {
|
||||
v.visit_expr(&expr);
|
||||
}
|
||||
if node.scoping {
|
||||
v.visit_exit();
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_binary(v, node: &ExprBinary) {
|
||||
@ -106,6 +130,13 @@ visit! {
|
||||
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) {
|
||||
for arg in &node.items {
|
||||
v.visit_arg(arg);
|
||||
@ -120,6 +151,7 @@ visit! {
|
||||
}
|
||||
|
||||
fn visit_let(v, node: &ExprLet) {
|
||||
v.visit_binding(&node.binding);
|
||||
if let Some(init) = &node.init {
|
||||
v.visit_expr(&init);
|
||||
}
|
||||
@ -139,6 +171,13 @@ visit! {
|
||||
}
|
||||
|
||||
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.body);
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.0 KiB |
@ -57,9 +57,6 @@
|
||||
#test(type(for v in () []), "template")
|
||||
|
||||
---
|
||||
// Error: 14-19 unknown variable
|
||||
#let error = error
|
||||
|
||||
// Uniterable expression.
|
||||
// Error: 11-15 cannot loop over boolean
|
||||
#for v in true {}
|
||||
|
@ -7,9 +7,31 @@
|
||||
#let x
|
||||
#test(x, none)
|
||||
|
||||
// Error: 9 expected expression
|
||||
#let y =
|
||||
#test(y, none)
|
||||
|
||||
// Manually initialized with one.
|
||||
#let x = 1
|
||||
#test(x, 1)
|
||||
#let z = 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.
|
||||
|
@ -26,9 +26,6 @@
|
||||
#test(type(while false []), "template")
|
||||
|
||||
---
|
||||
// Error: 14-19 unknown variable
|
||||
#let error = error
|
||||
|
||||
// Condition must be boolean.
|
||||
// Error: 8-14 expected boolean, found template
|
||||
#while [nope] [nope]
|
||||
|
66
tests/typ/expr/closure.typ
Normal file
66
tests/typ/expr/closure.typ
Normal 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)
|
||||
}
|
@ -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("test", ValueFunc::new("test", test));
|
||||
scope.def_const("error", Value::Error);
|
||||
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) {
|
||||
|
@ -304,7 +304,7 @@
|
||||
},
|
||||
{
|
||||
"name": "constant.other.color.typst",
|
||||
"match": "\\b#[0-9a-zA-Z]+\\b"
|
||||
"match": "#[0-9a-zA-Z]+\\b"
|
||||
},
|
||||
{
|
||||
"name": "string.quoted.double.typst",
|
||||
|
Loading…
x
Reference in New Issue
Block a user