diff --git a/LICENSE.txt b/LICENSE.txt index bb545cf68..38935ea57 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -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 diff --git a/src/eval/capture.rs b/src/eval/capture.rs index 163aa24ef..05760594f 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_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(); } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index c66f2ad28..f30ee7a7c 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::(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 0991564fc..c0926c0cc 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -13,11 +13,11 @@ pub type Slot = Rc>; #[derive(Debug, Default, Clone, PartialEq)] pub struct Scopes<'a> { /// The active scope. - top: Scope, + pub top: Scope, /// The stack of lower scopes. - scopes: Vec, + pub scopes: Vec, /// 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, } diff --git a/src/eval/value.rs b/src/eval/value.rs index d910155a1..7f31ea138 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, f: Rc Value>, } impl ValueFunc { /// Create a new function value from a rust function or closure. - pub fn new(name: impl Into, f: F) -> Self + pub fn new(name: Option, 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() } } diff --git a/src/library/mod.rs b/src/library/mod.rs index 59198846d..d34b338ca 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -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)) diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 327a99f33..298015277 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -173,21 +173,37 @@ fn primary(p: &mut Parser) -> Option { } 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 { 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 { 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, 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) -> Vec { + 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 { 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 diff --git a/src/pretty.rs b/src/pretty.rs index 86919ac8a..3f4205486 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -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("'); } } @@ -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 { "[*]", ); - // Function and arguments. - test_value(ValueFunc::new("nil", |_, _| Value::None), ""); + // Function. + test_value(ValueFunc::new(None, |_, _| Value::None), ""); + test_value( + ValueFunc::new(Some("nil".into()), |_, _| Value::None), + "", + ); + + // Arguments. test_value( ValueArgs { span: Span::ZERO, diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 638d9dd31..d76ada69e 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -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>, + /// The body of the closure. + pub body: Rc, +} + /// 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>, } diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs index 1bf260c76..15613233a 100644 --- a/src/syntax/visit.rs +++ b/src/syntax/visit.rs @@ -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); } diff --git a/tests/ref/control/let.png b/tests/ref/control/let.png index 8960f8f51..693a8d4f1 100644 Binary files a/tests/ref/control/let.png and b/tests/ref/control/let.png differ diff --git a/tests/typ/control/for.typ b/tests/typ/control/for.typ index 36bce447b..5eaa6ae84 100644 --- a/tests/typ/control/for.typ +++ b/tests/typ/control/for.typ @@ -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 {} diff --git a/tests/typ/control/let.typ b/tests/typ/control/let.typ index 8df29b116..7752ec901 100644 --- a/tests/typ/control/let.typ +++ b/tests/typ/control/let.typ @@ -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. diff --git a/tests/typ/control/while.typ b/tests/typ/control/while.typ index 7ad703720..acf7951e3 100644 --- a/tests/typ/control/while.typ +++ b/tests/typ/control/while.typ @@ -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] diff --git a/tests/typ/expr/closure.typ b/tests/typ/expr/closure.typ new file mode 100644 index 000000000..d05acaa44 --- /dev/null +++ b/tests/typ/expr/closure.typ @@ -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) +} diff --git a/tests/typeset.rs b/tests/typeset.rs index e1e55087a..c56655f51 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -315,8 +315,9 @@ fn register_helpers(scope: &mut Scope, panics: Rc>>) { } }; - 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) { diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index c10c0de5f..867f21098 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -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",