diff --git a/src/eval/call.rs b/src/eval/call.rs index 8e75f17cc..5b0628a8a 100644 --- a/src/eval/call.rs +++ b/src/eval/call.rs @@ -73,7 +73,7 @@ impl Args { where T: Cast>, { - self.pos.iter_mut().find_map(move |slot| try_cast(ctx, slot)) + (0 .. self.pos.len()).find_map(move |i| try_cast(ctx, &mut self.pos, i)) } /// Find and remove the first convertible positional argument, producing an @@ -97,7 +97,16 @@ impl Args { where T: Cast>, { - self.pos.iter_mut().filter_map(move |slot| try_cast(ctx, slot)) + let mut i = 0; + std::iter::from_fn(move || { + while i < self.pos.len() { + if let Some(val) = try_cast(ctx, &mut self.pos, i) { + return Some(val); + } + i += 1; + } + None + }) } /// Convert and remove the value for the given named argument, producing an @@ -163,17 +172,26 @@ where /// Try to cast the value in the slot into `T`, putting it back if the /// conversion fails. -fn try_cast(ctx: &mut EvalContext, slot: &mut Spanned) -> Option +fn try_cast( + ctx: &mut EvalContext, + vec: &mut Vec>, + i: usize, +) -> Option where T: Cast>, { // Replace with error placeholder when conversion works since error values // are ignored when generating "unexpected argument" errors. - let value = std::mem::replace(slot, Spanned::zero(Value::Error)); + let slot = &mut vec[i]; + let value = std::mem::replace(slot, Spanned::zero(Value::None)); let span = value.span; match T::cast(value) { - CastResult::Ok(t) => Some(t), + CastResult::Ok(t) => { + vec.remove(i); + Some(t) + } CastResult::Warn(t, m) => { + vec.remove(i); ctx.diag(warning!(span, "{}", m)); Some(t) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index b6836d384..7c92185d5 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -117,7 +117,7 @@ fn heading(p: &mut Parser) -> NodeHeading { // Parse the heading contents. let mut contents = vec![]; - while p.check(|t| !matches!(t, Token::Space(n) if n > 0)) { + while p.check(|t| !matches!(t, Token::Space(n) if n >= 1)) { if let Some(node) = p.span_if(|p| node(p, &mut false)) { contents.push(node); } @@ -388,8 +388,8 @@ fn string(p: &mut Parser, token: TokenStr) -> String { /// Parse a let expresion. fn expr_let(p: &mut Parser) -> Option { p.push_mode(TokenMode::Code); - p.start_group(Group::Terminated); p.eat_assert(Token::Let); + p.start_group(Group::Expr); let pat = p.span_if(ident); let mut rhs = None; @@ -404,11 +404,11 @@ fn expr_let(p: &mut Parser) -> Option { p.diag_expected("identifier"); } - while !p.eof() { - p.diag_unexpected(); + p.pop_mode(); + if !p.eof() { + p.diag_expected("semicolon or line break"); } - p.pop_mode(); p.end_group(); pat.map(|pat| Expr::Let(ExprLet { pat, expr: rhs })) } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 2b5fe7206..8c27c8f7a 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -112,16 +112,14 @@ impl<'s> Parser<'s> { /// # Panics /// This panics if the next token does not start the given group. pub fn start_group(&mut self, group: Group) { + self.groups.push(group); match group { Group::Paren => self.eat_assert(Token::LeftParen), Group::Bracket => self.eat_assert(Token::LeftBracket), Group::Brace => self.eat_assert(Token::LeftBrace), - Group::Subheader => {} - Group::Terminated => {} + Group::Expr => self.repeek(), + Group::Subheader => self.repeek(), } - - self.groups.push(group); - self.repeek(); } /// Ends the parsing of a group and returns the span of the whole group. @@ -129,26 +127,21 @@ impl<'s> Parser<'s> { /// # Panics /// This panics if no group was started. pub fn end_group(&mut self) { - // Check that we are indeed at the end of the group. - debug_assert_eq!(self.peek(), None, "unfinished group"); - let group = self.groups.pop().expect("no started group"); self.repeek(); - let end = match group { - Group::Paren => Some(Token::RightParen), - Group::Bracket => Some(Token::RightBracket), - Group::Brace => Some(Token::RightBrace), - Group::Subheader => None, - Group::Terminated => Some(Token::Semicolon), + let (end, required) = match group { + Group::Paren => (Token::RightParen, true), + Group::Bracket => (Token::RightBracket, true), + Group::Brace => (Token::RightBrace, true), + Group::Expr => (Token::Semicolon, false), + Group::Subheader => return, }; - if let Some(token) = end { - if self.next == Some(token) { - self.bump(); - } else { - self.diag(error!(self.next_start, "expected {}", token.name())); - } + if self.next == Some(end) { + self.bump(); + } else if required { + self.diag(error!(self.next_start, "expected {}", end.name())); } } @@ -169,7 +162,7 @@ impl<'s> Parser<'s> { where F: FnOnce(&mut Self) -> Option, { - self.span(|p| f(p)).transpose() + self.span(f).transpose() } /// Consume the next token. @@ -269,17 +262,21 @@ impl<'s> Parser<'s> { match self.tokens.mode() { TokenMode::Markup => {} - TokenMode::Code => { - while matches!( - self.next, - Some(Token::Space(_)) | - Some(Token::LineComment(_)) | - Some(Token::BlockComment(_)) - ) { - self.next_start = self.tokens.pos(); - self.next = self.tokens.next(); + TokenMode::Code => loop { + match self.next { + Some(Token::Space(n)) => { + if n >= 1 && self.groups.last() == Some(&Group::Expr) { + break; + } + } + Some(Token::LineComment(_)) => {} + Some(Token::BlockComment(_)) => {} + _ => break, } - } + + self.next_start = self.tokens.pos(); + self.next = self.tokens.next(); + }, } self.repeek(); @@ -287,16 +284,22 @@ impl<'s> Parser<'s> { fn repeek(&mut self) { self.peeked = self.next; - if self.groups.contains(&match self.next { - Some(Token::RightParen) => Group::Paren, - Some(Token::RightBracket) => Group::Bracket, - Some(Token::RightBrace) => Group::Brace, - Some(Token::Pipe) => Group::Subheader, - Some(Token::Semicolon) => Group::Terminated, + let token = match self.next { + Some(token) => token, + None => return, + }; + + match token { + Token::RightParen if self.groups.contains(&Group::Paren) => {} + Token::RightBracket if self.groups.contains(&Group::Bracket) => {} + Token::RightBrace if self.groups.contains(&Group::Brace) => {} + Token::Semicolon if self.groups.contains(&Group::Expr) => {} + Token::Space(n) if n >= 1 && self.groups.last() == Some(&Group::Expr) => {} + Token::Pipe if self.groups.contains(&Group::Subheader) => {} _ => return, - }) { - self.peeked = None; } + + self.peeked = None; } } @@ -316,9 +319,9 @@ pub enum Group { Bracket, /// A curly-braced group: `{...}`. Brace, + /// A group ended by a semicolon or a line break: `;`, `\n`. + Expr, /// A group ended by a chained subheader or a closing bracket: /// `... >>`, `...]`. Subheader, - /// A group ended by a semicolon: `;`. - Terminated, } diff --git a/tests/lang/ref/arrays.png b/tests/lang/ref/arrays.png index 2d70a3f67..36845b5f2 100644 Binary files a/tests/lang/ref/arrays.png and b/tests/lang/ref/arrays.png differ diff --git a/tests/lang/ref/basics.png b/tests/lang/ref/basics.png deleted file mode 100644 index 3c3dd7a0c..000000000 Binary files a/tests/lang/ref/basics.png and /dev/null differ diff --git a/tests/lang/ref/blocks.png b/tests/lang/ref/blocks.png index ca826c1b9..22a5722ad 100644 Binary files a/tests/lang/ref/blocks.png and b/tests/lang/ref/blocks.png differ diff --git a/tests/lang/ref/emph-strong.png b/tests/lang/ref/emph-strong.png new file mode 100644 index 000000000..36392f8c0 Binary files /dev/null and b/tests/lang/ref/emph-strong.png differ diff --git a/tests/lang/ref/expressions.png b/tests/lang/ref/expressions.png deleted file mode 100644 index 309c32f1a..000000000 Binary files a/tests/lang/ref/expressions.png and /dev/null differ diff --git a/tests/lang/ref/headings.png b/tests/lang/ref/headings.png index 1f374cce6..c8d831650 100644 Binary files a/tests/lang/ref/headings.png and b/tests/lang/ref/headings.png differ diff --git a/tests/lang/ref/let.png b/tests/lang/ref/let.png index be9d8397f..d98333910 100644 Binary files a/tests/lang/ref/let.png and b/tests/lang/ref/let.png differ diff --git a/tests/lang/ref/linebreaks.png b/tests/lang/ref/linebreaks.png new file mode 100644 index 000000000..5268ed64c Binary files /dev/null and b/tests/lang/ref/linebreaks.png differ diff --git a/tests/lang/ref/nbsp.png b/tests/lang/ref/nbsp.png new file mode 100644 index 000000000..537a70ebd Binary files /dev/null and b/tests/lang/ref/nbsp.png differ diff --git a/tests/lang/ref/raw.png b/tests/lang/ref/raw.png index f88194c0b..8b68922a5 100644 Binary files a/tests/lang/ref/raw.png and b/tests/lang/ref/raw.png differ diff --git a/tests/lang/ref/text.png b/tests/lang/ref/text.png new file mode 100644 index 000000000..54479229c Binary files /dev/null and b/tests/lang/ref/text.png differ diff --git a/tests/lang/ref/values.png b/tests/lang/ref/values.png index 762ad64e3..d4411cc80 100644 Binary files a/tests/lang/ref/values.png and b/tests/lang/ref/values.png differ diff --git a/tests/lang/typ/arrays.typ b/tests/lang/typ/arrays.typ index 0b8fb866a..3c290e50d 100644 --- a/tests/lang/typ/arrays.typ +++ b/tests/lang/typ/arrays.typ @@ -1,6 +1,9 @@ // Empty. {()} +// Not an array, just a parenthesized expression. +{(1)} + // One item and trailing comma. {(-1,)} @@ -13,21 +16,31 @@ , #003 ,)} +// Missing closing paren. // Error: 1:3-1:3 expected closing paren {(} -// Error: 2:4-2:6 expected expression, found end of block comment -// Error: 1:4-1:4 expected comma +// Not an array. +// Error: 1:2-1:3 expected expression, found closing paren +{)} + +// Missing comma and bad expression. +// Error: 2:4-2:4 expected comma +// Error: 1:4-1:6 expected expression, found end of block comment {(1*/2)} +// Bad expression. // Error: 1:6-1:8 expected expression, found invalid token {(1, 1u 2)} +// Leading comma is not an expression. // Error: 1:3-1:4 expected expression, found comma {(,1)} +// Missing expression makes named pair incomplete, making this an empty array. // Error: 1:5-1:5 expected expression {(a:)} +// Named pair after this is already identified as an array. // Error: 1:6-1:10 expected expression, found named pair {(1, b: 2)} diff --git a/tests/lang/typ/basics.typ b/tests/lang/typ/basics.typ deleted file mode 100644 index 9220e3765..000000000 --- a/tests/lang/typ/basics.typ +++ /dev/null @@ -1,14 +0,0 @@ -Hello 🌏! - -_Emph_ and *strong*! - -The non-breaking~space does not work. - -// Directly after word. -Line\ Break - -// Spaces around. -Line \ Break - -// Directly before word does not work. -No \Break diff --git a/tests/lang/typ/blocks.typ b/tests/lang/typ/blocks.typ index cadd30dd1..faef0987f 100644 --- a/tests/lang/typ/blocks.typ +++ b/tests/lang/typ/blocks.typ @@ -1,21 +1,26 @@ +// Basic expression. {1} -// Function calls. -{f(1)} -{[[f 1]]} - // Error: 1:2-1:2 expected expression {} +// Bad expression. // Error: 1:2-1:4 expected expression, found invalid token {1u} +// Two expressions are not allowed. +// Error: 1:4-1:5 unexpected integer +{2 3} + +// Missing closing brace in nested block. // Error: 1:5-1:5 expected closing brace {({1) + 2} -// Error: 1:12-1:12 expected closing bracket -{[*] + [ok*} +// Missing closing bracket in template expression. +// Error: 1:11-1:11 expected closing bracket +{[_] + [4_} +// Opening brace is ignored after one expression is parsed. // Error: 2:4-2:5 unexpected hex value // Error: 1:5-1:6 unexpected opening brace -{1 #{} _end_ +{5 #{}*.* diff --git a/tests/lang/typ/comments.typ b/tests/lang/typ/comments.typ index c5b04967b..5118a05e3 100644 --- a/tests/lang/typ/comments.typ +++ b/tests/lang/typ/comments.typ @@ -1,18 +1,19 @@ -// Test whether line comment acts as spacing. +// Line comment acts as spacing. A// you B -// Test whether block comment acts as spacing. +// Block comment does not act as spacing. C/* /* */ */D -// Test in expressions. +// Works in headers. [f /*1*/ a: "b" // , 1] +// End should not appear without start. // Error: 1:7-1:9 unexpected end of block comment /* */ */ -// Unterminated block comment is okay. +// Unterminated is okay. /* diff --git a/tests/lang/typ/dictionaries.typ b/tests/lang/typ/dictionaries.typ index c729b92c5..63d9db9ec 100644 --- a/tests/lang/typ/dictionaries.typ +++ b/tests/lang/typ/dictionaries.typ @@ -4,9 +4,11 @@ // Two pairs. {(one: 1, two: 2)} +// Simple expression after this is already identified as a dictionary. // Error: 1:9-1:10 expected named pair, found expression {(a: 1, b)} +// Identified as dictionary by initial colon. // Error: 4:4-4:5 expected named pair, found expression // Error: 3:5-3:5 expected comma // Error: 2:12-2:16 expected identifier diff --git a/tests/lang/typ/emph-strong.typ b/tests/lang/typ/emph-strong.typ new file mode 100644 index 000000000..dcd173376 --- /dev/null +++ b/tests/lang/typ/emph-strong.typ @@ -0,0 +1,11 @@ +// Basic. +_Emph_ and *strong*! + +// Inside of words. +Pa_rtl_y emphasized or str*ength*ened. + +// Scoped to body. +[box][*Sco_ped] to body. + +// Unterminated is fine. +_The End diff --git a/tests/lang/typ/escaping.typ b/tests/lang/typ/escaping.typ index cb663612a..abf124ce9 100644 --- a/tests/lang/typ/escaping.typ +++ b/tests/lang/typ/escaping.typ @@ -18,8 +18,10 @@ // Escaped escape sequence. \u{41} vs. \\u\{41\} +// Unicode codepoint does not exist. // Error: 1:1-1:11 invalid unicode escape sequence \u{FFFFFF} +// Unterminated. // Error: 1:6-1:6 expected closing brace \u{41*Bold* diff --git a/tests/lang/typ/expressions.typ b/tests/lang/typ/expressions.typ index 017252892..74150c4bc 100644 --- a/tests/lang/typ/expressions.typ +++ b/tests/lang/typ/expressions.typ @@ -1,37 +1,43 @@ +// Ref: false + #let a = 2; #let b = 4; +// Paren call. +[eq f(1), "f(1)"] +[eq type(1), "integer"] + // Unary operations. -{+1} -{-1} -{--1} +[eq +1, 1] +[eq -1, 1-2] +[eq --1, 1] // Binary operations. -{"a"+"b"} -{1-2} -{a * b} -{12pt/.4} +[eq "a" + "b", "ab"] +[eq 1-4, 3*-1] +[eq a * b, 8] +[eq 12pt/.4, 30pt] // Associativity. -{1+2+3} -{1/2*3} +[eq 1+2+3, 6] +[eq 1/2*3, 1.5] // Precedence. -{1+2*-3} +[eq 1+2*-3, -5] // Parentheses. -{(a)} -{(2)} -{(1+2)*3} +[eq (a), 2] +[eq (2), 2] +[eq (1+2)*3, 9] // Confusion with floating-point literal. -{1e+2-1e-2} +[eq 1e+2-1e-2, 99.99] // Error: 1:3-1:3 expected expression {-} -// Error: 1:4-1:4 expected expression -{1+} +// Error: 1:8-1:8 expected expression +[eq {1+}, 1] -// Error: 1:4-1:4 expected expression -{2*} +// Error: 1:8-1:8 expected expression +[eq {2*}, 2] diff --git a/tests/lang/typ/headings.typ b/tests/lang/typ/headings.typ index f62f6534a..765d03770 100644 --- a/tests/lang/typ/headings.typ +++ b/tests/lang/typ/headings.typ @@ -1,38 +1,48 @@ // Test different numbers of hashtags. +// Valid levels. # One ### Three ###### Six +// Too many hashtags. // Warning: 1:1-1:8 section depth should not exceed 6 ####### Seven --- // Test heading vs. no heading. +// Parsed as headings if at start of the context. /**/ # Heading {[## Heading]} [box][### Heading] -\# No heading - +// Not at the start of the context. Text with # hashtag -Nr#1 +// Escaped. +\# No heading +// Keyword. // Error: 1:1-1:6 unexpected invalid token #nope +// Not parsed as a keyword, but neither as a heading. +Nr#1 + --- // Heading continuation over linebreak. +// Code blocks continue heading. # This { - "works" + "continues" } +// Function call continues heading. # [box][ - This + This, ] too +// Without some kind of block, headings end at a line break. # This -does not +not diff --git a/tests/lang/typ/let.typ b/tests/lang/typ/let.typ index c7bba7478..a966f2434 100644 --- a/tests/lang/typ/let.typ +++ b/tests/lang/typ/let.typ @@ -1,20 +1,53 @@ +// Ref: false + // Automatically initialized with `none`. -#let x; -{(x,)} +#let x +[eq x, none] -// Can start with underscore. -#let _y=1; -{_y} +// Initialized with `1`. +#let y = 1; +[eq y, 1] -// Multiline. -#let z = "world" - + " 🌏"; Hello, {z}! +// Multiple bindings in one line. +#let x = "a"; #let y = "b"; [eq x + y, "ab"] +// No name. // Error: 1:6-1:7 expected identifier, found integer -#let 1; +#let 1 -// Error: 4:1-4:3 unexpected identifier -// Error: 3:4-3:9 unexpected identifier -// Error: 3:1-3:1 expected semicolon -#let x = "" -Hi there +--- +// Terminated with just a line break. +#let v = "a" +First +[eq v, "a"] + +// Terminated with just a semicolon. +#let v = "a"; Second +[eq v, "a"] + +// Terminated with semicolon + line break. +#let v = "a"; +Third +[eq v, "a"] + +// Terminated by semicolon even though we are in a paren group. +// Error: 2:22-2:22 expected expression +// Error: 1:22-1:22 expected closing paren +#let array = (1, 2 + ;Fourth +[eq array, (1, 2)] + +// Not terminated. +// Error: 1:14-1:20 expected semicolon or line break, found identifier +#let v = "a" Unseen Fifth +[eq v, "a"] + +// Not terminated by semicolon in template. +#let v = [Hello; there] + +// Not terminated by line break due to parens. +#let x = ( + 1, + 2, + 3, +) +[eq x, (1, 2, 3)] diff --git a/tests/lang/typ/linebreaks.typ b/tests/lang/typ/linebreaks.typ new file mode 100644 index 000000000..ee2f453ae --- /dev/null +++ b/tests/lang/typ/linebreaks.typ @@ -0,0 +1,19 @@ +// Leading line break. +\ Leading + +// Directly after word. +Line\ Break + +// Spaces around. +Line \ Break + +// Directly before word does not work. +No \Break + +// Trailing before paragraph break. +Paragraph 1 \ + +Paragraph 2 + +// Trailing before end of document. +Paragraph 3 \ diff --git a/tests/lang/typ/nbsp.typ b/tests/lang/typ/nbsp.typ new file mode 100644 index 000000000..b2f099503 --- /dev/null +++ b/tests/lang/typ/nbsp.typ @@ -0,0 +1,2 @@ +// Parsed correctly, but not actually doing anything at the moment. +The non-breaking~space does not work. diff --git a/tests/lang/typ/raw.typ b/tests/lang/typ/raw.typ index 22eda139a..2c0890ba2 100644 --- a/tests/lang/typ/raw.typ +++ b/tests/lang/typ/raw.typ @@ -1,23 +1,28 @@ +[font 8pt] + +// Typst syntax inside. +`#let x = 1``[f 1]` + +// Space between "rust" and "let" is trimmed. The keyword ``rust let``. -`#let x = 1` -`[f 1]` - ---- -[font 6pt] +// Trimming depends on number backticks. +<` untrimmed `> \ +<`` trimmed ``> +// Multiline trimming. ``py import this def say_hi(): - print("Hello World!") + print("Hi!") `` ---- +// Lots of backticks inside. ```` ```backticks``` ```` ---- +// Unterminated. // Error: 2:1-2:1 expected backtick(s) `endless diff --git a/tests/lang/typ/text.typ b/tests/lang/typ/text.typ new file mode 100644 index 000000000..9c1e79770 --- /dev/null +++ b/tests/lang/typ/text.typ @@ -0,0 +1 @@ +Hello 🌏! diff --git a/tests/lang/typ/values.typ b/tests/lang/typ/values.typ index ce41fc433..51d0bd17b 100644 --- a/tests/lang/typ/values.typ +++ b/tests/lang/typ/values.typ @@ -1,13 +1,24 @@ +// Test representation of values in the document. + #let name = "Typst"; #let ke-bab = "Kebab!"; #let α = "Alpha"; +// Variables. {name} \ {ke-bab} \ {α} \ + +// Error: 1:1-1:4 unknown variable +{_} + +// Literal values. {none} (empty) \ {true} \ {false} \ + +// Numerical values. +{1} \ {1.0e-4} \ {3.15} \ {1e-10} \ @@ -17,17 +28,16 @@ {12e1pt} \ {2.5rad} \ {45deg} \ -{"hi"} \ -{"a\n[]\"\u{1F680}string"} \ -{#f7a20500} \ -{[*{"Hi"} [f 1]*]} \ -{{1}} -// Error: 1:1-1:4 unknown variable -{_} \ +// Colors. +{#f7a20500} \ // Error: 1:2-1:5 invalid color {#a5} -// Error: 1:2-1:4 expected expression, found invalid token -{1u} +// Strings and escaping. +{"hi"} \ +{"a\n[]\"\u{1F680}string"} \ + +// Templates. +{[*{"Hi"} [f 1]*]} diff --git a/tests/typeset.rs b/tests/typeset.rs index 10e6231e1..fd5f4c815 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -1,7 +1,9 @@ +use std::cell::RefCell; use std::env; use std::ffi::OsStr; use std::fs; use std::path::Path; +use std::rc::Rc; use fontdock::fs::FsIndex; use image::{GenericImageView, Rgba}; @@ -180,7 +182,8 @@ fn test_part(i: usize, src: &str, env: &mut Env) -> (bool, Vec) { let (compare_ref, ref_diags) = parse_metadata(src, &map); let mut scope = library::new(); - register_helpers(&mut scope); + let panicked = Rc::new(RefCell::new(false)); + register_helpers(&mut scope, Rc::clone(&panicked)); // We want to have "unbounded" pages, so we allow them to be infinitely // large and fit them to match their content. @@ -201,6 +204,10 @@ fn test_part(i: usize, src: &str, env: &mut Env) -> (bool, Vec) { diags.sort_by_key(|d| d.span); let mut ok = true; + if *panicked.borrow() { + ok = false; + } + if diags != ref_diags { println!(" Subtest {} does not match expected diagnostics. ❌", i); ok = false; @@ -259,7 +266,7 @@ fn parse_metadata(src: &str, map: &LineMap) -> (bool, SpanVec) { (compare_ref, diags) } -fn register_helpers(scope: &mut Scope) { +fn register_helpers(scope: &mut Scope, panicked: Rc>) { pub fn f(_: &mut EvalContext, args: &mut Args) -> Value { let (array, dict) = args.drain(); let iter = array @@ -281,7 +288,22 @@ fn register_helpers(scope: &mut Scope) { Value::Str(p.finish()) } + let eq = move |ctx: &mut EvalContext, args: &mut Args| -> Value { + let lhs = args.require::(ctx, "left-hand side"); + let rhs = args.require::(ctx, "right-hand side"); + if lhs != rhs { + *panicked.borrow_mut() = true; + println!(" Assertion failed ❌"); + println!(" left: {:?}", lhs); + println!(" right: {:?}", rhs); + Value::Str(format!("(panic)")) + } else { + Value::None + } + }; + scope.set("f", ValueFunc::new("f", f)); + scope.set("eq", ValueFunc::new("eq", eq)); } fn print_diag(diag: &Spanned, map: &LineMap) {