Assertions with [eq] + better tests 🩺
@ -73,7 +73,7 @@ impl Args {
|
||||
where
|
||||
T: Cast<Spanned<Value>>,
|
||||
{
|
||||
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<Spanned<Value>>,
|
||||
{
|
||||
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<T>(ctx: &mut EvalContext, slot: &mut Spanned<Value>) -> Option<T>
|
||||
fn try_cast<T>(
|
||||
ctx: &mut EvalContext,
|
||||
vec: &mut Vec<Spanned<Value>>,
|
||||
i: usize,
|
||||
) -> Option<T>
|
||||
where
|
||||
T: Cast<Spanned<Value>>,
|
||||
{
|
||||
// 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)
|
||||
}
|
||||
|
@ -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<Expr> {
|
||||
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<Expr> {
|
||||
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 }))
|
||||
}
|
||||
|
@ -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) {
|
||||
if self.next == Some(end) {
|
||||
self.bump();
|
||||
} else {
|
||||
self.diag(error!(self.next_start, "expected {}", token.name()));
|
||||
}
|
||||
} 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<T>,
|
||||
{
|
||||
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(_))
|
||||
) {
|
||||
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,
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 799 B |
BIN
tests/lang/ref/emph-strong.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
tests/lang/ref/linebreaks.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
tests/lang/ref/nbsp.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.0 KiB |
BIN
tests/lang/ref/text.png
Normal file
After Width: | Height: | Size: 731 B |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.4 KiB |
@ -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)}
|
||||
|
@ -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
|
@ -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 #{}*.*
|
||||
|
@ -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.
|
||||
/*
|
||||
|
@ -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
|
||||
|
11
tests/lang/typ/emph-strong.typ
Normal file
@ -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
|
@ -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*
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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)]
|
||||
|
19
tests/lang/typ/linebreaks.typ
Normal file
@ -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 \
|
2
tests/lang/typ/nbsp.typ
Normal file
@ -0,0 +1,2 @@
|
||||
// Parsed correctly, but not actually doing anything at the moment.
|
||||
The non-breaking~space does not work.
|
@ -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
|
||||
|
1
tests/lang/typ/text.typ
Normal file
@ -0,0 +1 @@
|
||||
Hello 🌏!
|
@ -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]*]}
|
||||
|
@ -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<Frame>) {
|
||||
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<Frame>) {
|
||||
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<Diag>) {
|
||||
(compare_ref, diags)
|
||||
}
|
||||
|
||||
fn register_helpers(scope: &mut Scope) {
|
||||
fn register_helpers(scope: &mut Scope, panicked: Rc<RefCell<bool>>) {
|
||||
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::<Value>(ctx, "left-hand side");
|
||||
let rhs = args.require::<Value>(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<Diag>, map: &LineMap) {
|
||||
|