Assertions with [eq] + better tests 🩺
@ -73,7 +73,7 @@ impl Args {
|
|||||||
where
|
where
|
||||||
T: Cast<Spanned<Value>>,
|
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
|
/// Find and remove the first convertible positional argument, producing an
|
||||||
@ -97,7 +97,16 @@ impl Args {
|
|||||||
where
|
where
|
||||||
T: Cast<Spanned<Value>>,
|
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
|
/// 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
|
/// Try to cast the value in the slot into `T`, putting it back if the
|
||||||
/// conversion fails.
|
/// 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
|
where
|
||||||
T: Cast<Spanned<Value>>,
|
T: Cast<Spanned<Value>>,
|
||||||
{
|
{
|
||||||
// Replace with error placeholder when conversion works since error values
|
// Replace with error placeholder when conversion works since error values
|
||||||
// are ignored when generating "unexpected argument" errors.
|
// 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;
|
let span = value.span;
|
||||||
match T::cast(value) {
|
match T::cast(value) {
|
||||||
CastResult::Ok(t) => Some(t),
|
CastResult::Ok(t) => {
|
||||||
|
vec.remove(i);
|
||||||
|
Some(t)
|
||||||
|
}
|
||||||
CastResult::Warn(t, m) => {
|
CastResult::Warn(t, m) => {
|
||||||
|
vec.remove(i);
|
||||||
ctx.diag(warning!(span, "{}", m));
|
ctx.diag(warning!(span, "{}", m));
|
||||||
Some(t)
|
Some(t)
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ fn heading(p: &mut Parser) -> NodeHeading {
|
|||||||
|
|
||||||
// Parse the heading contents.
|
// Parse the heading contents.
|
||||||
let mut contents = vec![];
|
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)) {
|
if let Some(node) = p.span_if(|p| node(p, &mut false)) {
|
||||||
contents.push(node);
|
contents.push(node);
|
||||||
}
|
}
|
||||||
@ -388,8 +388,8 @@ fn string(p: &mut Parser, token: TokenStr) -> String {
|
|||||||
/// Parse a let expresion.
|
/// Parse a let expresion.
|
||||||
fn expr_let(p: &mut Parser) -> Option<Expr> {
|
fn expr_let(p: &mut Parser) -> Option<Expr> {
|
||||||
p.push_mode(TokenMode::Code);
|
p.push_mode(TokenMode::Code);
|
||||||
p.start_group(Group::Terminated);
|
|
||||||
p.eat_assert(Token::Let);
|
p.eat_assert(Token::Let);
|
||||||
|
p.start_group(Group::Expr);
|
||||||
|
|
||||||
let pat = p.span_if(ident);
|
let pat = p.span_if(ident);
|
||||||
let mut rhs = None;
|
let mut rhs = None;
|
||||||
@ -404,11 +404,11 @@ fn expr_let(p: &mut Parser) -> Option<Expr> {
|
|||||||
p.diag_expected("identifier");
|
p.diag_expected("identifier");
|
||||||
}
|
}
|
||||||
|
|
||||||
while !p.eof() {
|
p.pop_mode();
|
||||||
p.diag_unexpected();
|
if !p.eof() {
|
||||||
|
p.diag_expected("semicolon or line break");
|
||||||
}
|
}
|
||||||
|
|
||||||
p.pop_mode();
|
|
||||||
p.end_group();
|
p.end_group();
|
||||||
pat.map(|pat| Expr::Let(ExprLet { pat, expr: rhs }))
|
pat.map(|pat| Expr::Let(ExprLet { pat, expr: rhs }))
|
||||||
}
|
}
|
||||||
|
@ -112,16 +112,14 @@ impl<'s> Parser<'s> {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
/// This panics if the next token does not start the given group.
|
/// This panics if the next token does not start the given group.
|
||||||
pub fn start_group(&mut self, group: Group) {
|
pub fn start_group(&mut self, group: Group) {
|
||||||
|
self.groups.push(group);
|
||||||
match group {
|
match group {
|
||||||
Group::Paren => self.eat_assert(Token::LeftParen),
|
Group::Paren => self.eat_assert(Token::LeftParen),
|
||||||
Group::Bracket => self.eat_assert(Token::LeftBracket),
|
Group::Bracket => self.eat_assert(Token::LeftBracket),
|
||||||
Group::Brace => self.eat_assert(Token::LeftBrace),
|
Group::Brace => self.eat_assert(Token::LeftBrace),
|
||||||
Group::Subheader => {}
|
Group::Expr => self.repeek(),
|
||||||
Group::Terminated => {}
|
Group::Subheader => self.repeek(),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.groups.push(group);
|
|
||||||
self.repeek();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ends the parsing of a group and returns the span of the whole group.
|
/// Ends the parsing of a group and returns the span of the whole group.
|
||||||
@ -129,26 +127,21 @@ impl<'s> Parser<'s> {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
/// This panics if no group was started.
|
/// This panics if no group was started.
|
||||||
pub fn end_group(&mut self) {
|
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");
|
let group = self.groups.pop().expect("no started group");
|
||||||
self.repeek();
|
self.repeek();
|
||||||
|
|
||||||
let end = match group {
|
let (end, required) = match group {
|
||||||
Group::Paren => Some(Token::RightParen),
|
Group::Paren => (Token::RightParen, true),
|
||||||
Group::Bracket => Some(Token::RightBracket),
|
Group::Bracket => (Token::RightBracket, true),
|
||||||
Group::Brace => Some(Token::RightBrace),
|
Group::Brace => (Token::RightBrace, true),
|
||||||
Group::Subheader => None,
|
Group::Expr => (Token::Semicolon, false),
|
||||||
Group::Terminated => Some(Token::Semicolon),
|
Group::Subheader => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(token) = end {
|
if self.next == Some(end) {
|
||||||
if self.next == Some(token) {
|
self.bump();
|
||||||
self.bump();
|
} else if required {
|
||||||
} else {
|
self.diag(error!(self.next_start, "expected {}", end.name()));
|
||||||
self.diag(error!(self.next_start, "expected {}", token.name()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +162,7 @@ impl<'s> Parser<'s> {
|
|||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> Option<T>,
|
F: FnOnce(&mut Self) -> Option<T>,
|
||||||
{
|
{
|
||||||
self.span(|p| f(p)).transpose()
|
self.span(f).transpose()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume the next token.
|
/// Consume the next token.
|
||||||
@ -269,17 +262,21 @@ impl<'s> Parser<'s> {
|
|||||||
|
|
||||||
match self.tokens.mode() {
|
match self.tokens.mode() {
|
||||||
TokenMode::Markup => {}
|
TokenMode::Markup => {}
|
||||||
TokenMode::Code => {
|
TokenMode::Code => loop {
|
||||||
while matches!(
|
match self.next {
|
||||||
self.next,
|
Some(Token::Space(n)) => {
|
||||||
Some(Token::Space(_)) |
|
if n >= 1 && self.groups.last() == Some(&Group::Expr) {
|
||||||
Some(Token::LineComment(_)) |
|
break;
|
||||||
Some(Token::BlockComment(_))
|
}
|
||||||
) {
|
}
|
||||||
self.next_start = self.tokens.pos();
|
Some(Token::LineComment(_)) => {}
|
||||||
self.next = self.tokens.next();
|
Some(Token::BlockComment(_)) => {}
|
||||||
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
self.next_start = self.tokens.pos();
|
||||||
|
self.next = self.tokens.next();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
self.repeek();
|
self.repeek();
|
||||||
@ -287,16 +284,22 @@ impl<'s> Parser<'s> {
|
|||||||
|
|
||||||
fn repeek(&mut self) {
|
fn repeek(&mut self) {
|
||||||
self.peeked = self.next;
|
self.peeked = self.next;
|
||||||
if self.groups.contains(&match self.next {
|
let token = match self.next {
|
||||||
Some(Token::RightParen) => Group::Paren,
|
Some(token) => token,
|
||||||
Some(Token::RightBracket) => Group::Bracket,
|
None => return,
|
||||||
Some(Token::RightBrace) => Group::Brace,
|
};
|
||||||
Some(Token::Pipe) => Group::Subheader,
|
|
||||||
Some(Token::Semicolon) => Group::Terminated,
|
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,
|
_ => return,
|
||||||
}) {
|
|
||||||
self.peeked = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.peeked = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,9 +319,9 @@ pub enum Group {
|
|||||||
Bracket,
|
Bracket,
|
||||||
/// A curly-braced group: `{...}`.
|
/// A curly-braced group: `{...}`.
|
||||||
Brace,
|
Brace,
|
||||||
|
/// A group ended by a semicolon or a line break: `;`, `\n`.
|
||||||
|
Expr,
|
||||||
/// A group ended by a chained subheader or a closing bracket:
|
/// A group ended by a chained subheader or a closing bracket:
|
||||||
/// `... >>`, `...]`.
|
/// `... >>`, `...]`.
|
||||||
Subheader,
|
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.
|
// Empty.
|
||||||
{()}
|
{()}
|
||||||
|
|
||||||
|
// Not an array, just a parenthesized expression.
|
||||||
|
{(1)}
|
||||||
|
|
||||||
// One item and trailing comma.
|
// One item and trailing comma.
|
||||||
{(-1,)}
|
{(-1,)}
|
||||||
|
|
||||||
@ -13,21 +16,31 @@
|
|||||||
, #003
|
, #003
|
||||||
,)}
|
,)}
|
||||||
|
|
||||||
|
// Missing closing paren.
|
||||||
// Error: 1:3-1:3 expected closing paren
|
// Error: 1:3-1:3 expected closing paren
|
||||||
{(}
|
{(}
|
||||||
|
|
||||||
// Error: 2:4-2:6 expected expression, found end of block comment
|
// Not an array.
|
||||||
// Error: 1:4-1:4 expected comma
|
// 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)}
|
{(1*/2)}
|
||||||
|
|
||||||
|
// Bad expression.
|
||||||
// Error: 1:6-1:8 expected expression, found invalid token
|
// Error: 1:6-1:8 expected expression, found invalid token
|
||||||
{(1, 1u 2)}
|
{(1, 1u 2)}
|
||||||
|
|
||||||
|
// Leading comma is not an expression.
|
||||||
// Error: 1:3-1:4 expected expression, found comma
|
// Error: 1:3-1:4 expected expression, found comma
|
||||||
{(,1)}
|
{(,1)}
|
||||||
|
|
||||||
|
// Missing expression makes named pair incomplete, making this an empty array.
|
||||||
// Error: 1:5-1:5 expected expression
|
// Error: 1:5-1:5 expected expression
|
||||||
{(a:)}
|
{(a:)}
|
||||||
|
|
||||||
|
// Named pair after this is already identified as an array.
|
||||||
// Error: 1:6-1:10 expected expression, found named pair
|
// Error: 1:6-1:10 expected expression, found named pair
|
||||||
{(1, b: 2)}
|
{(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}
|
{1}
|
||||||
|
|
||||||
// Function calls.
|
|
||||||
{f(1)}
|
|
||||||
{[[f 1]]}
|
|
||||||
|
|
||||||
// Error: 1:2-1:2 expected expression
|
// Error: 1:2-1:2 expected expression
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
// Bad expression.
|
||||||
// Error: 1:2-1:4 expected expression, found invalid token
|
// Error: 1:2-1:4 expected expression, found invalid token
|
||||||
{1u}
|
{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
|
// Error: 1:5-1:5 expected closing brace
|
||||||
{({1) + 2}
|
{({1) + 2}
|
||||||
|
|
||||||
// Error: 1:12-1:12 expected closing bracket
|
// Missing closing bracket in template expression.
|
||||||
{[*] + [ok*}
|
// 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: 2:4-2:5 unexpected hex value
|
||||||
// Error: 1:5-1:6 unexpected opening brace
|
// 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
|
A// you
|
||||||
B
|
B
|
||||||
|
|
||||||
// Test whether block comment acts as spacing.
|
// Block comment does not act as spacing.
|
||||||
C/*
|
C/*
|
||||||
/* */
|
/* */
|
||||||
*/D
|
*/D
|
||||||
|
|
||||||
// Test in expressions.
|
// Works in headers.
|
||||||
[f /*1*/ a: "b" //
|
[f /*1*/ a: "b" //
|
||||||
, 1]
|
, 1]
|
||||||
|
|
||||||
|
// End should not appear without start.
|
||||||
// Error: 1:7-1:9 unexpected end of block comment
|
// Error: 1:7-1:9 unexpected end of block comment
|
||||||
/* */ */
|
/* */ */
|
||||||
|
|
||||||
// Unterminated block comment is okay.
|
// Unterminated is okay.
|
||||||
/*
|
/*
|
||||||
|
@ -4,9 +4,11 @@
|
|||||||
// Two pairs.
|
// Two pairs.
|
||||||
{(one: 1, two: 2)}
|
{(one: 1, two: 2)}
|
||||||
|
|
||||||
|
// Simple expression after this is already identified as a dictionary.
|
||||||
// Error: 1:9-1:10 expected named pair, found expression
|
// Error: 1:9-1:10 expected named pair, found expression
|
||||||
{(a: 1, b)}
|
{(a: 1, b)}
|
||||||
|
|
||||||
|
// Identified as dictionary by initial colon.
|
||||||
// Error: 4:4-4:5 expected named pair, found expression
|
// Error: 4:4-4:5 expected named pair, found expression
|
||||||
// Error: 3:5-3:5 expected comma
|
// Error: 3:5-3:5 expected comma
|
||||||
// Error: 2:12-2:16 expected identifier
|
// 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.
|
// Escaped escape sequence.
|
||||||
\u{41} vs. \\u\{41\}
|
\u{41} vs. \\u\{41\}
|
||||||
|
|
||||||
|
// Unicode codepoint does not exist.
|
||||||
// Error: 1:1-1:11 invalid unicode escape sequence
|
// Error: 1:1-1:11 invalid unicode escape sequence
|
||||||
\u{FFFFFF}
|
\u{FFFFFF}
|
||||||
|
|
||||||
|
// Unterminated.
|
||||||
// Error: 1:6-1:6 expected closing brace
|
// Error: 1:6-1:6 expected closing brace
|
||||||
\u{41*Bold*
|
\u{41*Bold*
|
||||||
|
@ -1,37 +1,43 @@
|
|||||||
|
// Ref: false
|
||||||
|
|
||||||
#let a = 2;
|
#let a = 2;
|
||||||
#let b = 4;
|
#let b = 4;
|
||||||
|
|
||||||
|
// Paren call.
|
||||||
|
[eq f(1), "f(1)"]
|
||||||
|
[eq type(1), "integer"]
|
||||||
|
|
||||||
// Unary operations.
|
// Unary operations.
|
||||||
{+1}
|
[eq +1, 1]
|
||||||
{-1}
|
[eq -1, 1-2]
|
||||||
{--1}
|
[eq --1, 1]
|
||||||
|
|
||||||
// Binary operations.
|
// Binary operations.
|
||||||
{"a"+"b"}
|
[eq "a" + "b", "ab"]
|
||||||
{1-2}
|
[eq 1-4, 3*-1]
|
||||||
{a * b}
|
[eq a * b, 8]
|
||||||
{12pt/.4}
|
[eq 12pt/.4, 30pt]
|
||||||
|
|
||||||
// Associativity.
|
// Associativity.
|
||||||
{1+2+3}
|
[eq 1+2+3, 6]
|
||||||
{1/2*3}
|
[eq 1/2*3, 1.5]
|
||||||
|
|
||||||
// Precedence.
|
// Precedence.
|
||||||
{1+2*-3}
|
[eq 1+2*-3, -5]
|
||||||
|
|
||||||
// Parentheses.
|
// Parentheses.
|
||||||
{(a)}
|
[eq (a), 2]
|
||||||
{(2)}
|
[eq (2), 2]
|
||||||
{(1+2)*3}
|
[eq (1+2)*3, 9]
|
||||||
|
|
||||||
// Confusion with floating-point literal.
|
// 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:3-1:3 expected expression
|
||||||
{-}
|
{-}
|
||||||
|
|
||||||
// Error: 1:4-1:4 expected expression
|
// Error: 1:8-1:8 expected expression
|
||||||
{1+}
|
[eq {1+}, 1]
|
||||||
|
|
||||||
// Error: 1:4-1:4 expected expression
|
// Error: 1:8-1:8 expected expression
|
||||||
{2*}
|
[eq {2*}, 2]
|
||||||
|
@ -1,38 +1,48 @@
|
|||||||
// Test different numbers of hashtags.
|
// Test different numbers of hashtags.
|
||||||
|
|
||||||
|
// Valid levels.
|
||||||
# One
|
# One
|
||||||
### Three
|
### Three
|
||||||
###### Six
|
###### Six
|
||||||
|
|
||||||
|
// Too many hashtags.
|
||||||
// Warning: 1:1-1:8 section depth should not exceed 6
|
// Warning: 1:1-1:8 section depth should not exceed 6
|
||||||
####### Seven
|
####### Seven
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test heading vs. no heading.
|
// Test heading vs. no heading.
|
||||||
|
|
||||||
|
// Parsed as headings if at start of the context.
|
||||||
/**/ # Heading
|
/**/ # Heading
|
||||||
{[## Heading]}
|
{[## Heading]}
|
||||||
[box][### Heading]
|
[box][### Heading]
|
||||||
|
|
||||||
\# No heading
|
// Not at the start of the context.
|
||||||
|
|
||||||
Text with # hashtag
|
Text with # hashtag
|
||||||
|
|
||||||
Nr#1
|
// Escaped.
|
||||||
|
\# No heading
|
||||||
|
|
||||||
|
// Keyword.
|
||||||
// Error: 1:1-1:6 unexpected invalid token
|
// Error: 1:1-1:6 unexpected invalid token
|
||||||
#nope
|
#nope
|
||||||
|
|
||||||
|
// Not parsed as a keyword, but neither as a heading.
|
||||||
|
Nr#1
|
||||||
|
|
||||||
---
|
---
|
||||||
// Heading continuation over linebreak.
|
// Heading continuation over linebreak.
|
||||||
|
|
||||||
|
// Code blocks continue heading.
|
||||||
# This {
|
# This {
|
||||||
"works"
|
"continues"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function call continues heading.
|
||||||
# [box][
|
# [box][
|
||||||
This
|
This,
|
||||||
] too
|
] too
|
||||||
|
|
||||||
|
// Without some kind of block, headings end at a line break.
|
||||||
# This
|
# This
|
||||||
does not
|
not
|
||||||
|
@ -1,20 +1,53 @@
|
|||||||
|
// Ref: false
|
||||||
|
|
||||||
// Automatically initialized with `none`.
|
// Automatically initialized with `none`.
|
||||||
#let x;
|
#let x
|
||||||
{(x,)}
|
[eq x, none]
|
||||||
|
|
||||||
// Can start with underscore.
|
// Initialized with `1`.
|
||||||
#let _y=1;
|
#let y = 1;
|
||||||
{_y}
|
[eq y, 1]
|
||||||
|
|
||||||
// Multiline.
|
// Multiple bindings in one line.
|
||||||
#let z = "world"
|
#let x = "a"; #let y = "b"; [eq x + y, "ab"]
|
||||||
+ " 🌏"; Hello, {z}!
|
|
||||||
|
|
||||||
|
// No name.
|
||||||
// Error: 1:6-1:7 expected identifier, found integer
|
// 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
|
// Terminated with just a line break.
|
||||||
// Error: 3:1-3:1 expected semicolon
|
#let v = "a"
|
||||||
#let x = ""
|
First
|
||||||
Hi there
|
[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``.
|
The keyword ``rust let``.
|
||||||
|
|
||||||
`#let x = 1`
|
// Trimming depends on number backticks.
|
||||||
`[f 1]`
|
<` untrimmed `> \
|
||||||
|
<`` trimmed ``>
|
||||||
---
|
|
||||||
[font 6pt]
|
|
||||||
|
|
||||||
|
// Multiline trimming.
|
||||||
``py
|
``py
|
||||||
import this
|
import this
|
||||||
|
|
||||||
def say_hi():
|
def say_hi():
|
||||||
print("Hello World!")
|
print("Hi!")
|
||||||
``
|
``
|
||||||
|
|
||||||
---
|
// Lots of backticks inside.
|
||||||
````
|
````
|
||||||
```backticks```
|
```backticks```
|
||||||
````
|
````
|
||||||
|
|
||||||
---
|
// Unterminated.
|
||||||
// Error: 2:1-2:1 expected backtick(s)
|
// Error: 2:1-2:1 expected backtick(s)
|
||||||
`endless
|
`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 name = "Typst";
|
||||||
#let ke-bab = "Kebab!";
|
#let ke-bab = "Kebab!";
|
||||||
#let α = "Alpha";
|
#let α = "Alpha";
|
||||||
|
|
||||||
|
// Variables.
|
||||||
{name} \
|
{name} \
|
||||||
{ke-bab} \
|
{ke-bab} \
|
||||||
{α} \
|
{α} \
|
||||||
|
|
||||||
|
// Error: 1:1-1:4 unknown variable
|
||||||
|
{_}
|
||||||
|
|
||||||
|
// Literal values.
|
||||||
{none} (empty) \
|
{none} (empty) \
|
||||||
{true} \
|
{true} \
|
||||||
{false} \
|
{false} \
|
||||||
|
|
||||||
|
// Numerical values.
|
||||||
|
{1} \
|
||||||
{1.0e-4} \
|
{1.0e-4} \
|
||||||
{3.15} \
|
{3.15} \
|
||||||
{1e-10} \
|
{1e-10} \
|
||||||
@ -17,17 +28,16 @@
|
|||||||
{12e1pt} \
|
{12e1pt} \
|
||||||
{2.5rad} \
|
{2.5rad} \
|
||||||
{45deg} \
|
{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
|
// Error: 1:2-1:5 invalid color
|
||||||
{#a5}
|
{#a5}
|
||||||
|
|
||||||
// Error: 1:2-1:4 expected expression, found invalid token
|
// Strings and escaping.
|
||||||
{1u}
|
{"hi"} \
|
||||||
|
{"a\n[]\"\u{1F680}string"} \
|
||||||
|
|
||||||
|
// Templates.
|
||||||
|
{[*{"Hi"} [f 1]*]}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use fontdock::fs::FsIndex;
|
use fontdock::fs::FsIndex;
|
||||||
use image::{GenericImageView, Rgba};
|
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 (compare_ref, ref_diags) = parse_metadata(src, &map);
|
||||||
|
|
||||||
let mut scope = library::new();
|
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
|
// We want to have "unbounded" pages, so we allow them to be infinitely
|
||||||
// large and fit them to match their content.
|
// 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);
|
diags.sort_by_key(|d| d.span);
|
||||||
|
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
if *panicked.borrow() {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
if diags != ref_diags {
|
if diags != ref_diags {
|
||||||
println!(" Subtest {} does not match expected diagnostics. ❌", i);
|
println!(" Subtest {} does not match expected diagnostics. ❌", i);
|
||||||
ok = false;
|
ok = false;
|
||||||
@ -259,7 +266,7 @@ fn parse_metadata(src: &str, map: &LineMap) -> (bool, SpanVec<Diag>) {
|
|||||||
(compare_ref, diags)
|
(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 {
|
pub fn f(_: &mut EvalContext, args: &mut Args) -> Value {
|
||||||
let (array, dict) = args.drain();
|
let (array, dict) = args.drain();
|
||||||
let iter = array
|
let iter = array
|
||||||
@ -281,7 +288,22 @@ fn register_helpers(scope: &mut Scope) {
|
|||||||
Value::Str(p.finish())
|
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("f", ValueFunc::new("f", f));
|
||||||
|
scope.set("eq", ValueFunc::new("eq", eq));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_diag(diag: &Spanned<Diag>, map: &LineMap) {
|
fn print_diag(diag: &Spanned<Diag>, map: &LineMap) {
|
||||||
|