diff --git a/src/eval/mod.rs b/src/eval/mod.rs index a43219e75..1a2116704 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -317,17 +317,11 @@ impl Spanned<&ExprBinary> { return Value::Error; } - let lhty = lhs.type_name(); - let rhty = rhs.type_name(); + let (l, r) = (lhs.type_name(), rhs.type_name()); + let out = op(lhs, rhs); if out == Value::Error { - ctx.diag(error!( - self.span, - "cannot apply '{}' to {} and {}", - self.v.op.v.as_str(), - lhty, - rhty, - )); + self.error(ctx, l, r); } out @@ -358,13 +352,41 @@ impl Spanned<&ExprBinary> { } }; - if let Ok(mut slot) = slot.try_borrow_mut() { - *slot = op(std::mem::take(&mut slot), rhs); - return Value::None; + let (constant, err, value) = if let Ok(mut inner) = slot.try_borrow_mut() { + let lhs = std::mem::take(&mut *inner); + let types = (lhs.type_name(), rhs.type_name()); + + *inner = op(lhs, rhs); + if *inner == Value::Error { + (false, Some(types), Value::Error) + } else { + (false, None, Value::None) + } + } else { + (true, None, Value::Error) + }; + + if constant { + ctx.diag(error!(span, "cannot assign to a constant")); } - ctx.diag(error!(span, "cannot assign to a constant")); - Value::Error + if let Some((l, r)) = err { + self.error(ctx, l, r); + } + + value + } + + fn error(&self, ctx: &mut EvalContext, l: &str, r: &str) { + let op = self.v.op.v.as_str(); + let message = match self.v.op.v { + BinOp::Add => format!("cannot add {} and {}", l, r), + BinOp::Sub => format!("cannot subtract {1} from {0}", l, r), + BinOp::Mul => format!("cannot multiply {} with {}", l, r), + BinOp::Div => format!("cannot divide {} by {}", l, r), + _ => format!("cannot apply '{}' to {} and {}", op, l, r), + }; + ctx.diag(error!(self.span, "{}", message)); } } diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 56d6687b4..c52a62ca7 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -44,10 +44,16 @@ pub fn add(lhs: Value, rhs: Value) -> Value { (Linear(a), Length(b)) => Linear(a + b), (Linear(a), Relative(b)) => Linear(a + b), (Linear(a), Linear(b)) => Linear(a + b), + (Str(a), Str(b)) => Str(a + &b), (Array(a), Array(b)) => Array(concat(a, b)), (Dict(a), Dict(b)) => Dict(concat(a, b)), + + // TODO: Add string and template. (Template(a), Template(b)) => Template(concat(a, b)), + (Template(a), None) => Template(a), + (None, Template(b)) => Template(b), + _ => Error, } } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 2ca8eb102..b77677727 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -205,7 +205,7 @@ impl<'s> Parser<'s> { pub fn expect(&mut self, t: Token) -> bool { let eaten = self.eat_if(t); if !eaten { - self.expected(t.name()); + self.expected_at(t.name(), self.last_end); } eaten } diff --git a/tests/lang/ref/arrays.png b/tests/lang/ref/array.png similarity index 100% rename from tests/lang/ref/arrays.png rename to tests/lang/ref/array.png diff --git a/tests/lang/ref/block-invalid.png b/tests/lang/ref/block-invalid.png new file mode 100644 index 000000000..56471574b Binary files /dev/null and b/tests/lang/ref/block-invalid.png differ diff --git a/tests/lang/ref/block-value.png b/tests/lang/ref/block-value.png new file mode 100644 index 000000000..a3c03698e Binary files /dev/null and b/tests/lang/ref/block-value.png differ diff --git a/tests/lang/ref/blocks.png b/tests/lang/ref/blocks.png deleted file mode 100644 index 8dd7f9404..000000000 Binary files a/tests/lang/ref/blocks.png and /dev/null differ diff --git a/tests/lang/ref/bracket-call.png b/tests/lang/ref/bracket-call.png deleted file mode 100644 index fe7f24346..000000000 Binary files a/tests/lang/ref/bracket-call.png and /dev/null differ diff --git a/tests/lang/ref/call-args.png b/tests/lang/ref/call-args.png new file mode 100644 index 000000000..0ce9000a6 Binary files /dev/null and b/tests/lang/ref/call-args.png differ diff --git a/tests/lang/ref/call-bracket.png b/tests/lang/ref/call-bracket.png new file mode 100644 index 000000000..2f020629d Binary files /dev/null and b/tests/lang/ref/call-bracket.png differ diff --git a/tests/lang/ref/call-chain.png b/tests/lang/ref/call-chain.png new file mode 100644 index 000000000..5c90dbd80 Binary files /dev/null and b/tests/lang/ref/call-chain.png differ diff --git a/tests/lang/ref/call-invalid.png b/tests/lang/ref/call-invalid.png new file mode 100644 index 000000000..e03d84c11 Binary files /dev/null and b/tests/lang/ref/call-invalid.png differ diff --git a/tests/lang/ref/comment.png b/tests/lang/ref/comment.png new file mode 100644 index 000000000..79f438b2f Binary files /dev/null and b/tests/lang/ref/comment.png differ diff --git a/tests/lang/ref/comments.png b/tests/lang/ref/comments.png deleted file mode 100644 index df5e5b9cb..000000000 Binary files a/tests/lang/ref/comments.png and /dev/null differ diff --git a/tests/lang/ref/dict.png b/tests/lang/ref/dict.png new file mode 100644 index 000000000..45d23ad39 Binary files /dev/null and b/tests/lang/ref/dict.png differ diff --git a/tests/lang/ref/dictionaries.png b/tests/lang/ref/dictionaries.png deleted file mode 100644 index aa40549e6..000000000 Binary files a/tests/lang/ref/dictionaries.png and /dev/null differ diff --git a/tests/lang/ref/emph-strong.png b/tests/lang/ref/emph-strong.png deleted file mode 100644 index 36392f8c0..000000000 Binary files a/tests/lang/ref/emph-strong.png and /dev/null differ diff --git a/tests/lang/ref/emph.png b/tests/lang/ref/emph.png new file mode 100644 index 000000000..a3aae7269 Binary files /dev/null and b/tests/lang/ref/emph.png differ diff --git a/tests/lang/ref/escape.png b/tests/lang/ref/escape.png new file mode 100644 index 000000000..aafe09eb2 Binary files /dev/null and b/tests/lang/ref/escape.png differ diff --git a/tests/lang/ref/escaping.png b/tests/lang/ref/escaping.png deleted file mode 100644 index 575a4b2fc..000000000 Binary files a/tests/lang/ref/escaping.png and /dev/null differ diff --git a/tests/lang/ref/expr-binary.png b/tests/lang/ref/expr-binary.png new file mode 100644 index 000000000..6c94ef9b0 Binary files /dev/null and b/tests/lang/ref/expr-binary.png differ diff --git a/tests/lang/ref/for-invalid.png b/tests/lang/ref/for-invalid.png new file mode 100644 index 000000000..fa65f4957 Binary files /dev/null and b/tests/lang/ref/for-invalid.png differ diff --git a/tests/lang/ref/for-loop.png b/tests/lang/ref/for-loop.png new file mode 100644 index 000000000..2f13985ad Binary files /dev/null and b/tests/lang/ref/for-loop.png differ diff --git a/tests/lang/ref/for-value.png b/tests/lang/ref/for-value.png new file mode 100644 index 000000000..fa323edcd Binary files /dev/null and b/tests/lang/ref/for-value.png differ diff --git a/tests/lang/ref/heading.png b/tests/lang/ref/heading.png new file mode 100644 index 000000000..f72806ec9 Binary files /dev/null and b/tests/lang/ref/heading.png differ diff --git a/tests/lang/ref/headings.png b/tests/lang/ref/headings.png deleted file mode 100644 index b1ad33666..000000000 Binary files a/tests/lang/ref/headings.png and /dev/null differ diff --git a/tests/lang/ref/if-branch.png b/tests/lang/ref/if-branch.png new file mode 100644 index 000000000..8bb852968 Binary files /dev/null and b/tests/lang/ref/if-branch.png differ diff --git a/tests/lang/ref/if-invalid.png b/tests/lang/ref/if-invalid.png new file mode 100644 index 000000000..319fbdbdf Binary files /dev/null and b/tests/lang/ref/if-invalid.png differ diff --git a/tests/lang/ref/if.png b/tests/lang/ref/if.png deleted file mode 100644 index b763034f2..000000000 Binary files a/tests/lang/ref/if.png and /dev/null differ diff --git a/tests/lang/ref/let-invalid.png b/tests/lang/ref/let-invalid.png new file mode 100644 index 000000000..19d4d545d Binary files /dev/null and b/tests/lang/ref/let-invalid.png differ diff --git a/tests/lang/ref/let-terminated.png b/tests/lang/ref/let-terminated.png new file mode 100644 index 000000000..24f20c693 Binary files /dev/null and b/tests/lang/ref/let-terminated.png differ diff --git a/tests/lang/ref/let.png b/tests/lang/ref/let.png deleted file mode 100644 index c555a9a07..000000000 Binary files a/tests/lang/ref/let.png and /dev/null differ diff --git a/tests/lang/ref/linebreak.png b/tests/lang/ref/linebreak.png new file mode 100644 index 000000000..4769e2913 Binary files /dev/null and b/tests/lang/ref/linebreak.png differ diff --git a/tests/lang/ref/linebreaks.png b/tests/lang/ref/linebreaks.png deleted file mode 100644 index 5268ed64c..000000000 Binary files a/tests/lang/ref/linebreaks.png and /dev/null differ diff --git a/tests/lang/ref/raw.png b/tests/lang/ref/raw.png index 2a5b083db..0da49c1b8 100644 Binary files a/tests/lang/ref/raw.png and b/tests/lang/ref/raw.png differ diff --git a/tests/lang/ref/repr.png b/tests/lang/ref/repr.png new file mode 100644 index 000000000..f3bf781eb Binary files /dev/null and b/tests/lang/ref/repr.png differ diff --git a/tests/lang/ref/spacing.png b/tests/lang/ref/spacing.png new file mode 100644 index 000000000..5c3acf9b2 Binary files /dev/null and b/tests/lang/ref/spacing.png differ diff --git a/tests/lang/ref/strong.png b/tests/lang/ref/strong.png new file mode 100644 index 000000000..eb5e4d8e7 Binary files /dev/null and b/tests/lang/ref/strong.png differ diff --git a/tests/lang/ref/text.png b/tests/lang/ref/text.png index 54479229c..88ce95e33 100644 Binary files a/tests/lang/ref/text.png and b/tests/lang/ref/text.png differ diff --git a/tests/lang/ref/values.png b/tests/lang/ref/values.png deleted file mode 100644 index df46bd2d9..000000000 Binary files a/tests/lang/ref/values.png and /dev/null differ diff --git a/tests/lang/typ/arrays.typ b/tests/lang/typ/array.typ similarity index 85% rename from tests/lang/typ/arrays.typ rename to tests/lang/typ/array.typ index c91a5ac18..f80cc0cd4 100644 --- a/tests/lang/typ/arrays.typ +++ b/tests/lang/typ/array.typ @@ -1,3 +1,6 @@ +// Test arrays. + +--- // Empty. {()} @@ -16,24 +19,19 @@ , #003 ,)} -// Missing closing paren. // Error: 3-3 expected closing paren {(} -// Not an array. // Error: 2-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: 6-8 expected expression, found invalid token {(1, 1u 2)} -// Leading comma is not an expression. // Error: 3-4 expected expression, found comma {(,1)} diff --git a/tests/lang/typ/block-invalid.typ b/tests/lang/typ/block-invalid.typ new file mode 100644 index 000000000..cf51b91b0 --- /dev/null +++ b/tests/lang/typ/block-invalid.typ @@ -0,0 +1,37 @@ +// Test invalid code block syntax. + +--- +// Multiple unseparated expressions in one line. + +// Error: 2-4 expected expression, found invalid token +{1u} + +// Should output `1`. +// Error: 3-3 expected semicolon or line break +{0 1} + +// Should output `2`. +// Error: 2:13-2:13 expected semicolon or line break +// Error: 1:24-1:24 expected semicolon or line break +{#let x = -1 #let y = 3 x + y} + +// Should output `3`. +{ + // Error: 10-13 expected identifier, found string + #for "v" + + // Error: 11-11 expected keyword `#in` + #for v #let z = 1 + 2 + + z +} + +--- +// Ref: false +// Error: 3:1-3:1 expected closing brace +{ + +--- +// Ref: false +// Error: 1-2 unexpected closing brace +} diff --git a/tests/lang/typ/block-scoping.typ b/tests/lang/typ/block-scoping.typ new file mode 100644 index 000000000..bd1f58675 --- /dev/null +++ b/tests/lang/typ/block-scoping.typ @@ -0,0 +1,35 @@ +// Test scoping with blocks. +// Ref: false + +--- +// Block in template does not create a scope. +{ #let x = 1 } +#[test x, 1] + +--- +// Block in expression does create a scope. +#let a = { + #let b = 1 + b +} + +#[test a, 1] + +// Error: 2-3 unknown variable +{b} + +--- +// Multiple nested scopes. +{ + #let a = "a1" + { + #let a = "a2" + { + test(a, "a2") + #let a = "a3" + test(a, "a3") + } + test(a, "a2") + } + test(a, "a1") +} diff --git a/tests/lang/typ/block-value.typ b/tests/lang/typ/block-value.typ new file mode 100644 index 000000000..62934ce67 --- /dev/null +++ b/tests/lang/typ/block-value.typ @@ -0,0 +1,38 @@ +// Test return value of code blocks. + +--- +All none + +// Nothing evaluates to none. +{} + +// Let evaluates to none. +{ #let v = 0 } + +// Trailing none evaluates to none. +{ + type("") + none +} + +--- +// Evaluates to single expression. +{ "Hello" } + +// Evaluates to trailing expression. +{ #let x = "Hel"; x + "lo" } + +// Evaluates to concatenation of for loop bodies. +{ + #let parts = ("Hel", "lo") + #for s #in parts [{s}] +} + +--- +// Works the same way in code environment. +// Ref: false +#[test { + #let x = 1 + #let y = 2 + x + y +}, 3] diff --git a/tests/lang/typ/blocks.typ b/tests/lang/typ/blocks.typ deleted file mode 100644 index b2691ffb1..000000000 --- a/tests/lang/typ/blocks.typ +++ /dev/null @@ -1,17 +0,0 @@ -// Empty. -{} - -// Basic expression. -{1} - -// Bad expression. -// Error: 2-4 expected expression, found invalid token -{1u} - -// Missing closing brace in nested block. -// Error: 5-5 expected closing brace -{({1) + 1} - -// Missing closing bracket in template expression. -// Error: 11-11 expected closing bracket -{[_] + [3_} diff --git a/tests/lang/typ/bracket-call.typ b/tests/lang/typ/bracket-call.typ deleted file mode 100644 index a2d563c68..000000000 --- a/tests/lang/typ/bracket-call.typ +++ /dev/null @@ -1,107 +0,0 @@ -// Basic call, whitespace insignificant. -#[f], #[ f ], #[ - f -] - -#[f bold] - -#[f 1,] - -#[f a:2] - -#[f 1, a: (3, 4), 2, b: "5"] - ---- -// Body and no body. -#[f][#[f]] - -// Lots of potential bodies. -#[f][f]#[f] - -// Multi-paragraph body. -#[box][ - First - - Second -] - ---- -// Chained. -#[f | f] - -// Multi-chain. -#[f|f|f] - -// With body. -// Error: 7-8 expected identifier, found integer -#[f | 1 | box][💕] - -// Error: 2:3-2:3 expected identifier -// Error: 1:4-1:4 expected identifier -#[||f true] - -// Error: 7-7 expected identifier -#[f 1|] - -// Error: 2:3-2:3 expected identifier -// Error: 1:4-1:4 expected identifier -#[|][Nope] - -// Error: 2:6-2:6 expected closing paren -// Error: 1:9-1:10 expected expression, found closing paren -#[f (|f )] - -// With actual functions. -#[box width: 1cm | image "res/rhino.png"] - ---- -// Error: 5-7 expected expression, found end of block comment -#[f */] - -// Error: 8-9 expected expression, found colon -#[f a:1:] - -// Error: 6-6 expected comma -#[f 1 2] - -// Error: 2:5-2:6 expected identifier -// Error: 1:7-1:7 expected expression -#[f 1:] - -// Error: 5-6 expected identifier -#[f 1:2] - -// Error: 5-8 expected identifier -#[f (x):1] - ---- -// Ref: false -// Error: 2:3-2:4 expected function, found string -#let x = "string" -#[x] - -// Error: 3-4 expected identifier, found invalid token -#[# 1] - -// Error: 4:1-4:1 expected identifier -// Error: 3:1-3:1 expected closing bracket -#[ - ---- -// Ref: false -// Error: 2:3-2:4 expected identifier, found closing paren -// Error: 3:1-3:1 expected closing bracket -#[) - ---- -// Error: 3:1-3:1 expected closing bracket -#[f [*] - ---- -// Error: 3:1-3:1 expected closing bracket -#[f][`a]` - ---- -// Error: 3:1-3:1 expected quote -// Error: 2:1-2:1 expected closing bracket -#[f "] diff --git a/tests/lang/typ/call-args.typ b/tests/lang/typ/call-args.typ new file mode 100644 index 000000000..cf79c1f02 --- /dev/null +++ b/tests/lang/typ/call-args.typ @@ -0,0 +1,34 @@ +// Test function call arguments. + +--- +// One argument. +#[f bold] + +// One argument and trailing comma. +#[f 1,] + +// One named argument. +#[f a:2] + +// Mixed arguments. +{f(1, a: (3, 4), 2, b: "5")} + +--- +// Error: 5-6 expected expression, found colon +#[f :] + +// Error: 8-10 expected expression, found end of block comment +#[f a:1*/] + +// Error: 6-6 expected comma +#[f 1 2] + +// Error: 2:5-2:6 expected identifier +// Error: 1:7-1:7 expected expression +#[f 1:] + +// Error: 5-6 expected identifier +#[f 1:2] + +// Error: 4-7 expected identifier +{f((x):1)} diff --git a/tests/lang/typ/call-bracket.typ b/tests/lang/typ/call-bracket.typ new file mode 100644 index 000000000..2ee2c5d41 --- /dev/null +++ b/tests/lang/typ/call-bracket.typ @@ -0,0 +1,18 @@ +// Test bracketed function calls. + +--- +// Whitespace insignificant. +#[f], #[ f ] + +// Body and no body. +#[f][#[f]] + +// Tight functions. +#[f]#[f] + +// Multi-paragraph body. +#[align right][ + First + + Second +] diff --git a/tests/lang/typ/call-chain.typ b/tests/lang/typ/call-chain.typ new file mode 100644 index 000000000..72899f958 --- /dev/null +++ b/tests/lang/typ/call-chain.typ @@ -0,0 +1,31 @@ +// Test bracket call chaining. + +--- +// Chained once. +#[f | f] + +// Chained twice. +#[f|f|f] + +// With body. +// Error: 7-8 expected identifier, found integer +#[f | 1 | box][💕] + +// With actual functions. +#[box width: 1cm | image "res/rhino.png"] + +--- +// Error: 8-8 expected identifier +#[f 1 |] + +// Error: 4-4 expected identifier +#[ | f true] + +// Error: 2:3-2:3 expected identifier +// Error: 1:4-1:4 expected identifier +#[|][Nope] + +// Pipe wins over parens. +// Error: 2:6-2:6 expected closing paren +// Error: 1:9-1:10 expected expression, found closing paren +#[f (|f )] diff --git a/tests/lang/typ/call-invalid.typ b/tests/lang/typ/call-invalid.typ new file mode 100644 index 000000000..8e3efa328 --- /dev/null +++ b/tests/lang/typ/call-invalid.typ @@ -0,0 +1,36 @@ +// Test invalid function calls. + +--- +// Error: 4-4 expected closing paren +{f(} + +--- +// Error: 4:1-4:1 expected identifier +// Error: 3:1-3:1 expected closing bracket +#[ + +--- +// Error: 3-3 expected identifier +#[] + +// Error: 3-6 expected identifier, found string +#["f"] + +// Error: 2:3-2:4 expected identifier, found opening paren +// Error: 1:5-1:6 expected expression, found closing paren +#[(f)] + +--- +#let x = "string" + +// Error: 3-4 expected function, found string +#[x] + +--- +// Error: 3:1-3:1 expected closing bracket +#[f][`a]` + +--- +// Error: 3:1-3:1 expected quote +// Error: 2:1-2:1 expected closing bracket +#[f "] diff --git a/tests/lang/typ/call-paren.typ b/tests/lang/typ/call-paren.typ new file mode 100644 index 000000000..482e20e83 --- /dev/null +++ b/tests/lang/typ/call-paren.typ @@ -0,0 +1,11 @@ +// Test parenthesized function calls. +// Ref: false + +--- +// Whitespace insignificant. +#[test type(1), "integer"] +#[test type (1), "integer"] + +// From variable. +#let alias = type +#[test alias(alias), "function"] diff --git a/tests/lang/typ/comments.typ b/tests/lang/typ/comment.typ similarity index 65% rename from tests/lang/typ/comments.typ rename to tests/lang/typ/comment.typ index 3cabb5869..524a24e3c 100644 --- a/tests/lang/typ/comments.typ +++ b/tests/lang/typ/comment.typ @@ -1,3 +1,6 @@ +// Test line and block comments. + +--- // Line comment acts as spacing. A// you B @@ -7,12 +10,13 @@ C/* /* */ */D -// Works in headers. +// Works in code. #[f /*1*/ a: "b" // , 1] +--- // End should not appear without start. -// Error: 7-9 unexpected end of block comment +// Error: 1:7-1:9 unexpected end of block comment /* */ */ // Unterminated is okay. diff --git a/tests/lang/typ/dictionaries.typ b/tests/lang/typ/dict.typ similarity index 67% rename from tests/lang/typ/dictionaries.typ rename to tests/lang/typ/dict.typ index 4f9e0f608..b12dbd590 100644 --- a/tests/lang/typ/dictionaries.typ +++ b/tests/lang/typ/dict.typ @@ -1,14 +1,18 @@ +// Test dictionaries. + +--- // Empty {(:)} // Two pairs. {(one: 1, two: 2)} -// Simple expression after this is already identified as a dictionary. +--- +// Simple expression after already being identified as a dictionary. // Error: 9-10 expected named pair, found expression {(a: 1, b)} -// Identified as dictionary by initial colon. +// Identified as dictionary due to 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 deleted file mode 100644 index 2f77c7bbe..000000000 --- a/tests/lang/typ/emph-strong.typ +++ /dev/null @@ -1,11 +0,0 @@ -// 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/emph.typ b/tests/lang/typ/emph.typ new file mode 100644 index 000000000..8e5812a87 --- /dev/null +++ b/tests/lang/typ/emph.typ @@ -0,0 +1,14 @@ +// Test emphasis toggle. + +--- +// Basic. +_Emphasized!_ + +// Inside of words. +Partly em_phas_ized. + +// Scoped to body. +#[box][_Scoped] to body. + +// Unterminated is fine. +_The End diff --git a/tests/lang/typ/escaping.typ b/tests/lang/typ/escape.typ similarity index 76% rename from tests/lang/typ/escaping.typ rename to tests/lang/typ/escape.typ index 57d8ec689..3c0f02f38 100644 --- a/tests/lang/typ/escaping.typ +++ b/tests/lang/typ/escape.typ @@ -1,8 +1,11 @@ +// Test escape sequences. + +--- // Escapable symbols. -\\ \/ \[ \] \{ \} \* \_ \# \~ \` \$ +\\ \/ \[ \] \{ \} \# \* \_ \= \~ \` \$ // No need to escape. -( ) = ; +( ) ; < > // Unescapable. \a \: \; \( \) @@ -12,7 +15,7 @@ \/\* \*\/ \/* \*/ * -// Test unicode escape sequence. +// Unicode escape sequence. \u{1F3D5} == 🏕 // Escaped escape sequence. diff --git a/tests/lang/typ/expr-assoc.typ b/tests/lang/typ/expr-assoc.typ new file mode 100644 index 000000000..64db98c68 --- /dev/null +++ b/tests/lang/typ/expr-assoc.typ @@ -0,0 +1,18 @@ +// Test operator associativity. +// Ref: false + +--- +// Math operators are left-associative. +#[test 10 / 2 / 2 == (10 / 2) / 2, true] +#[test 10 / 2 / 2 == 10 / (2 / 2), false] +#[test 1 / 2 * 3, 1.5] + +--- +// Assignment is right-associative. +{ + #let x = 1 + #let y = 2 + x = y = "ok" + test(x, none) + test(y, "ok") +} diff --git a/tests/lang/typ/expr-binary.typ b/tests/lang/typ/expr-binary.typ new file mode 100644 index 000000000..b02269981 --- /dev/null +++ b/tests/lang/typ/expr-binary.typ @@ -0,0 +1,132 @@ +// Test binary expressions. +// Ref: false + +--- +// Test template addition. +// Ref: true +{[*Hello ] + [world!*]} + +--- +// Test math operators. + +// Addition. +#[test 2 + 4, 6] +#[test "a" + "b", "ab"] +#[test (1, 2) + (3, 4), (1, 2, 3, 4)] +#[test (a: 1) + (b: 2, c: 3), (a: 1, b: 2, c: 3)] + +// Subtraction. +#[test 1-4, 3*-1] +#[test 4cm - 2cm, 2cm] +#[test 1e+2-1e-2, 99.99] + +// Multiplication. +#[test 2 * 4, 8] + +// Division. +#[test 12pt/.4, 30pt] +#[test 7 / 2, 3.5] + +// Combination. +#[test 3-4 * 5 < -10, true] +#[test { #let x; x = 1 + 4*5 >= 21 and { x = "a"; x + "b" == "ab" }; x }, true] + +// Mathematical identities. +#let nums = (1, 3.14, 12pt, 45deg, 90%, 13% + 10pt) +#for v #in nums { + // Test plus and minus. + test(v + v - v, v) + test(v - v - v, -v) + + // Test plus/minus and multiplication. + test(v - v, 0 * v) + test(v + v, 2 * v) + + // Integer addition does not give a float. + #if type(v) != "integer" { + test(v + v, 2.0 * v) + } + + // Linears cannot be divided by themselves. + #if type(v) != "linear" { + test(v / v, 1.0) + test(v / v == 1, true) + } +} + + +// Make sure length, relative and linear +// - can all be added to / subtracted from each other, +// - multiplied with integers and floats, +// - divided by floats. +#let dims = (10pt, 30%, 50% + 3cm) +#for a #in dims { + #for b #in dims { + test(type(a + b), type(a - b)) + } + + #for b #in (7, 3.14) { + test(type(a * b), type(a)) + test(type(b * a), type(a)) + test(type(a / b), type(a)) + } +} + +--- +// Test boolean operators. + +// And. +#[test false and false, false] +#[test false and true, false] +#[test true and false, false] +#[test true and true, true] + +// Or. +#[test false or false, false] +#[test false or true, true] +#[test true or false, true] +#[test true or true, true] + +// Short-circuiting. +#[test false and dont-care, false] +#[test true or dont-care, true] + +--- +// Test equality operators. + +#[test 1 == "hi", false] +#[test 1 == 1.0, true] +#[test 30% == 30% + 0cm, true] +#[test 1in == 0% + 72pt, true] +#[test 30% == 30% + 1cm, false] +#[test "ab" == "a" + "b", true] +#[test () == (1,), false] +#[test (1, 2, 3) == (1, 2.0) + (3,), true] +#[test (:) == (a: 1), false] +#[test (a: 2 - 1.0, b: 2) == (b: 2, a: 1), true] +#[test [*Hi*] == [*Hi*], true] + +#[test "a" != "a", false] +#[test [*] != [_], true] + +--- +// Test comparison operators. + +#[test 13 * 3 < 14 * 4, true] +#[test 5 < 10, true] +#[test 5 > 5, false] +#[test 5 <= 5, true] +#[test 5 <= 4, false] +#[test 45deg < 1rad, true] + +--- +// Test assignment operators. + +#let x = 0 +{ x = 10 } #[test x, 10] +{ x -= 5 } #[test x, 5] +{ x += 1 } #[test x, 6] +{ x *= x } #[test x, 36] +{ x /= 2.0 } #[test x, 18.0] +{ x = "some" } #[test x, "some"] +{ x += "thing" } #[test x, "something"] diff --git a/tests/lang/typ/expr-invalid.typ b/tests/lang/typ/expr-invalid.typ new file mode 100644 index 000000000..2d16034bb --- /dev/null +++ b/tests/lang/typ/expr-invalid.typ @@ -0,0 +1,59 @@ +// Test invalid expressions. +// Ref: false + +--- +// Missing expressions. + +// Error: 3-3 expected expression +{-} + +// Error: 11-11 expected expression +#[test {1+}, 1] + +// Error: 11-11 expected expression +#[test {2*}, 2] + +--- +// Mismatched types. + +// Error: 2-12 cannot apply '+' to template +{+([] + [])} + +// Error: 2-5 cannot apply '-' to string +{-""} + +// Error: 2-8 cannot apply 'not' to array +{not ()} + +// Error: 1:2-1:12 cannot apply '<=' to relative and relative +{30% <= 40%} + +// Special messages for +, -, * and /. +// Error: 4:03-4:10 cannot add integer and string +// Error: 3:12-3:19 cannot subtract integer from relative +// Error: 2:21-2:29 cannot multiply integer with boolean +// Error: 1:31-1:39 cannot divide integer by length +{(1 + "2", 40% - 1, 2 * true, 3 / 12pt)} + +// Error: 15-23 cannot apply '+=' to integer and string +{ #let x = 1; x += "2" } + +--- +// Bad left-hand sides of assignment. + +// Error: 1:3-1:6 cannot assign to this expression +{ (x) = "" } + +// Error: 1:3-1:8 cannot assign to this expression +{ 1 + 2 += 3 } + +// Error: 1:3-1:4 unknown variable +{ z = 1 } + +// Error: 1:3-1:6 cannot assign to a constant +{ box = "hi" } + +// Works if we define box beforehand +// (since then it doesn't resolve to the standard library version anymore). +#let box = "" +{ box = "hi" } diff --git a/tests/lang/typ/expr-prec.typ b/tests/lang/typ/expr-prec.typ new file mode 100644 index 000000000..fea5f7949 --- /dev/null +++ b/tests/lang/typ/expr-prec.typ @@ -0,0 +1,30 @@ +// Test operator precedence. +// Ref: false + +--- +// Multiplication binds stronger than addition. +#[test 1+2*-3, -5] + +// Subtraction binds stronger than comparison. +#[test 3 == 5 - 2, true] + +// Boolean operations bind stronger than '=='. +#[test "a" == "a" and 2 < 3, true] +#[test not "b" == "b", false] + +// Assignment binds stronger than boolean operations. +// Error: 2-7 cannot assign to this expression +{not x = "a"} + +--- +// Parentheses override precedence. +#[test (1), 1] +#[test (1+2)*-3, -9] + +// Error: 15-15 expected closing paren +#[test {(1 + 1}, 2] + +--- +// Precedence doesn't matter for chained unary operators. +// Error: 2-11 cannot apply '-' to boolean +{-not true} diff --git a/tests/lang/typ/expr-unary.typ b/tests/lang/typ/expr-unary.typ new file mode 100644 index 000000000..a1d97a494 --- /dev/null +++ b/tests/lang/typ/expr-unary.typ @@ -0,0 +1,23 @@ +// Test unary expressions. +// Ref: false + +--- +// Test plus and minus. +#for v #in (1, 3.14, 12pt, 45deg, 90%, 13% + 10pt) { + // Test plus. + test(+v, v) + + // Test minus. + test(-v, -1 * v) + test(--v, v) + + // Test combination. + test(-++ --v, -v) +} + +#[test -(4 + 2), 6-12] + +--- +// Test not. +#[test not true, false] +#[test not false, true] diff --git a/tests/lang/typ/expressions.typ b/tests/lang/typ/expressions.typ deleted file mode 100644 index 167d0d1a7..000000000 --- a/tests/lang/typ/expressions.typ +++ /dev/null @@ -1,120 +0,0 @@ -// Ref: false - -#let a = 2 -#let b = 4 - -// Error: 14-17 cannot apply '+' to string -#let error = +"" - -// Paren call. -#[test f(1), "f(1)"] -#[test type(1), "integer"] - -// Unary operations. -#[test +1, 1] -#[test -1, 1-2] -#[test --1, 1] - -// Math operations. -#[test "a" + "b", "ab"] -#[test 1-4, 3*-1] -#[test a * b, 8] -#[test 12pt/.4, 30pt] -#[test 1e+2-1e-2, 99.99] - -// Associativity. -#[test 1+2+3, 6] -#[test 1/2*3, 1.5] - -// Precedence. -#[test 1+2*-3, -5] - -// Short-circuiting logical operators. -#[test not "a" == "b", true] -#[test not 7 < 4 and 10 == 10, true] -#[test 3 < 2 or 4 < 5, true] -#[test false and false or true, true] - -// Right-hand side not even evaluated. -#[test false and dont-care, false] -#[test true or dont-care, true] - -// Equality and inequality. -#[test "ab" == "a" + "b", true] -#[test [*Hi*] == [*Hi*], true] -#[test "a" != "a", false] -#[test [*] != [_], true] -#[test (1, 2, 3) == (1, 2) + (3,), true] -#[test () == (1,), false] -#[test (a: 1, b: 2) == (b: 2, a: 1), true] -#[test (:) == (a: 1), false] -#[test 1 == "hi", false] -#[test 1 == 1.0, true] -#[test 30% == 30% + 0cm, true] -#[test 1in == 0% + 72pt, true] -#[test 30% == 30% + 1cm, false] - -// Comparisons. -#[test 13 * 3 < 14 * 4, true] -#[test 5 < 10, true] -#[test 5 > 5, false] -#[test 5 <= 5, true] -#[test 5 <= 4, false] -#[test 45deg < 1rad, true] - -// Assignment. -#let x = "some" -#let y = "some" -#[test (x = y = "") == none and x == none and y == "", true] - -// Modify-assign operators. -#let x = 0 -{ x = 10 } #[test x, 10] -{ x -= 5 } #[test x, 5] -{ x += 1 } #[test x, 6] -{ x *= x } #[test x, 36] -{ x /= 2.0 } #[test x, 18.0] -{ x = "some" } #[test x, "some"] -{ x += "thing" } #[test x, "something"] - -// Error: 3-4 unknown variable -{ z = 1 } - -// Error: 3-6 cannot assign to this expression -{ (x) = "" } - -// Error: 3-8 cannot assign to this expression -{ 1 + 2 = 3} - -// Error: 3-6 cannot assign to a constant -{ box = "hi" } - -// Works if we define box before (since then it doesn't resolve to the standard -// library version anymore). -#let box = ""; { box = "hi" } - -// Parentheses. -#[test (a), 2] -#[test (2), 2] -#[test (1+2)*3, 9] - -// Error: 3-3 expected expression -{-} - -// Error: 11-11 expected expression -#[test {1+}, 1] - -// Error: 11-11 expected expression -#[test {2*}, 2] - -// Error: 8-17 cannot apply '-' to boolean -#[test -not true, error] - -// Error: 2-8 cannot apply 'not' to array -{not ()} - -// Error: 3-10 cannot apply '+' to integer and string -{(1 + "2")} - -// Error: 2-12 cannot apply '<=' to relative and relative -{30% <= 40%} diff --git a/tests/lang/typ/for-invalid.typ b/tests/lang/typ/for-invalid.typ new file mode 100644 index 000000000..ca83649ab --- /dev/null +++ b/tests/lang/typ/for-invalid.typ @@ -0,0 +1,35 @@ +// Test invalid for loop syntax. + +--- +// Error: 5-5 expected identifier +#for + +// Error: 7-7 expected keyword `#in` +#for v + +// Error: 11-11 expected expression +#for v #in + +// Error: 16-16 expected body +#for v #in iter + +--- +// Should output `v iter`. +// Error: 2:5-2:5 expected identifier +// Error: 2:3-2:6 unexpected keyword `#in` +#for +v #in iter {} + +// Should output `A thing`. +// Error: 7-10 expected identifier, found string +A#for "v" thing. + +// Should output `iter`. +// Error: 2:6-2:9 expected identifier, found string +// Error: 1:10-1:13 unexpected keyword `#in` +#for "v" #in iter {} + +// Should output `+ b iter`. +// Error: 2:7-2:7 expected keyword `#in` +// Error: 1:12-1:15 unexpected keyword `#in` +#for a + b #in iter {} diff --git a/tests/lang/typ/for-loop.typ b/tests/lang/typ/for-loop.typ new file mode 100644 index 000000000..944cef1ea --- /dev/null +++ b/tests/lang/typ/for-loop.typ @@ -0,0 +1,44 @@ +// Test which things are iterable. +// Ref: false + +--- +// Array. + +#for x #in () {} + +#let sum = 0 +#for x #in (1, 2, 3, 4, 5) { + sum += x +} +#[test sum, 15] + +--- +// Dictionary. +// Ref: true +(\ #for k, v #in (name: "Typst", age: 2) [ + #[h 0.5cm] {k}: {v}, \ +]) + +--- +// String. +{ + #let out = "" + #let first = true + #for c #in "abc" { + #if not first { + out += ", " + } + first = false + out += c + } + test(out, "a, b, c") +} + +--- +// Uniterable expression. +// Error: 12-16 cannot loop over boolean +#for v #in true {} + +// Make sure that we don't complain twice. +// Error: 12-19 cannot add integer and string +#for v #in 1 + "2" {} diff --git a/tests/lang/typ/for-pattern.typ b/tests/lang/typ/for-pattern.typ new file mode 100644 index 000000000..87bf603fe --- /dev/null +++ b/tests/lang/typ/for-pattern.typ @@ -0,0 +1,30 @@ +// Test for loop patterns. +// Ref: false + +--- +#let out = () + +// Values of array. +#for v #in (1, 2, 3) { + out += (v,) +} + +// Values of dictionary. +#for v #in (a: 4, b: 5) { + out += (v,) +} + +// Keys and values of dictionary. +#for k, v #in (a: 6, b: 7) { + out += (k,) + out += (v,) +} + +#[test out, (1, 2, 3, 4, 5, "a", 6, "b", 7)] + +--- +// Keys and values of array. +// Error: 6-10 mismatched pattern +#for k, v #in (-1, -2, -3) { + dont-care +} diff --git a/tests/lang/typ/for-value.typ b/tests/lang/typ/for-value.typ new file mode 100644 index 000000000..f0705fc5f --- /dev/null +++ b/tests/lang/typ/for-value.typ @@ -0,0 +1,20 @@ +// Test return value of for loops. + +--- +// Template body yields template. +// Should output `234`. +#for v #in (1, 2, 3, 4) [#if v >= 2 [{v}]] + +--- +// Block body yields template. +// Should output `[1st, 2nd, 3rd, 4th, 5th, 6th]`. +{ + [\[] + #for v #in (1, 2, 3, 4, 5, 6) { + (#if v > 1 [, ] + + [{v}] + + #if v == 1 [st] + + #if v == 2 [nd] + + #if v == 3 [rd] + + #if v >= 4 [th]) + } + [\]] +} diff --git a/tests/lang/typ/headings.typ b/tests/lang/typ/heading.typ similarity index 56% rename from tests/lang/typ/headings.typ rename to tests/lang/typ/heading.typ index 2fd2d2aca..9bff2e6e8 100644 --- a/tests/lang/typ/headings.typ +++ b/tests/lang/typ/heading.typ @@ -1,41 +1,44 @@ -// Test different numbers of hashtags. - -// Valid levels. -= One -=== Three -====== Six - -// Too many hashtags. -// Warning: 1-8 should not exceed depth 6 -======= Seven +// Test headings. --- -// Test heading vs. no heading. +// Different number of hashtags. -// Parsed as headings if at start of the context. -/**/ = Heading -{[== Heading]} -#[box][=== Heading] +// Valid levels. +=1 +===2 +======6 -// Not at the start of the context. -Text = with=sign - -// Escaped. -\= No heading +// Too many hashtags. +// Warning: 1:1-1:8 should not exceed depth 6 +=======7 --- // Heading continuation over linebreak. // Code blocks continue heading. -= This { - "continues" += A{ + "B" } // Function call continues heading. = #[box][ - This, -] too + A +] B // Without some kind of block, headings end at a line break. -= This -not += A +B + +--- +// Heading vs. no heading. + +// Parsed as headings if at start of the context. +/**/ = Ok +{[== Ok]} +#[box][=== Ok] + +// Not at the start of the context. +No = heading + +// Escaped. +\= No heading diff --git a/tests/lang/typ/if-branch.typ b/tests/lang/typ/if-branch.typ new file mode 100644 index 000000000..8399d6741 --- /dev/null +++ b/tests/lang/typ/if-branch.typ @@ -0,0 +1,45 @@ +// Test conditions of if-else expressions. + +--- +// Test condition evaluation. +#if 1 < 2 [ + Ok. +] + +#if true == false [ + Bad, but we {dont-care}! +] + +--- +// Brace in condition. +#if {true} [ + Ok. +] + +// Multi-line condition with parens. +#if ( + 1 + 1 + == 1 +) { + nope +} #else { + "Ok." +} + +// Multiline. +#if false [ + Bad. +] #else { + #let pt = "." + "Ok" + pt +} + +--- +// Condition must be boolean. +// If it isn't, neither branch is evaluated. +// Error: 5-14 expected boolean, found string +#if "a" + "b" { nope } #else { nope } + +// Make sure that we don't complain twice. +// Error: 5-12 cannot add integer and string +#if 1 + "2" {} diff --git a/tests/lang/typ/if-invalid.typ b/tests/lang/typ/if-invalid.typ new file mode 100644 index 000000000..c7ead226c --- /dev/null +++ b/tests/lang/typ/if-invalid.typ @@ -0,0 +1,28 @@ +// Test invalid if syntax. + +--- +// Error: 4-4 expected expression +#if + +// Error: 5-5 expected expression +{#if} + +// Error: 6-6 expected body +#if x + +// Error: 1-6 unexpected keyword `#else` +#else {} + +--- +// Should output `x`. +// Error: 4-4 expected expression +#if +x {} + +// Should output `something`. +// Error: 6-6 expected body +#if x something + +// Should output `A thing.` +// Error: 20-20 expected body +A#if false {} #else thing diff --git a/tests/lang/typ/if-value.typ b/tests/lang/typ/if-value.typ new file mode 100644 index 000000000..f9e95ff6c --- /dev/null +++ b/tests/lang/typ/if-value.typ @@ -0,0 +1,21 @@ +// Test return value of if expressions. +// Ref: false + +--- +{ + #let x = 1 + #let y = 2 + #let z + + // Returns if branch. + z = #if x < y { "ok" } + test(z, "ok") + + // Returns else branch. + z = #if x > y { "bad" } #else { "ok" } + test(z, "ok") + + // Missing else evaluates to none. + z = #if x > y { "bad" } + test(z, none) +} diff --git a/tests/lang/typ/if.typ b/tests/lang/typ/if.typ deleted file mode 100644 index 0e0d03d17..000000000 --- a/tests/lang/typ/if.typ +++ /dev/null @@ -1,64 +0,0 @@ -#let x = true - -// The two different bodies. -#if true [_1_,] #if x {"2"} - -// Braced condition is fine. -#if {true} {"3"} - -// Newlines. -#if false [ - -] #else [ - 4 -] - -// Multiline (condition needs parens because it's terminated by the line break, -// just like the right-hand side of a let-binding). -#if ( - x -) { - "Fi" + "ve" -} - -// Spacing is somewhat delicate. We only want to have spacing in the output if -// there was whitespace before/after the full if-else statement. In particular, -// spacing after a simple if should be retained, but spacing between the first -// body and the else should be ignored. -a#if true[b]c \ -a#if true[b] c \ -a #if true{"b"}c \ -a #if true{"b"} c \ -a#if false [?] #else [b]c \ -a#if true {"b"} #else {"?"} c \ - -// Body not evaluated at all if condition is false. -#if false { dont-care-about-undefined-variables } - ---- -#let x = true - -// Needs condition. -// Error: 6-7 expected expression, found closing brace -a#if } - -// Needs if-body. -// Error: 2:7-2:7 expected body -// Error: 1:16-1:16 expected body -a#if x b#if (x)c - -// Needs else-body. -// Error: 20-20 expected body -a#if true [b] #else c - -// Lone else. -// Error: 1-6 unexpected keyword `#else` -#else [] - -// Condition must be boolean. If it isn't, neither branch is evaluated. -// Error: 5-14 expected boolean, found string -#if "a" + "b" { "nope" } #else { "nope" } - -// No coercing from empty array or or stuff like that. -// Error: 5-7 expected boolean, found array -#if () { "nope" } #else { "nope" } diff --git a/tests/lang/typ/let-invalid.typ b/tests/lang/typ/let-invalid.typ new file mode 100644 index 000000000..3e32e2cf4 --- /dev/null +++ b/tests/lang/typ/let-invalid.typ @@ -0,0 +1,20 @@ +// Test invalid let binding syntax. + +--- +// Error: 5-5 expected identifier +#let + +// Error: 6-9 expected identifier, found string +#let "v" + +// Should output `1`. +// Error: 7-7 expected semicolon or line break +#let v 1 + +// Error: 9-9 expected expression +#let v = + +--- +// Should output `= 1`. +// Error: 6-9 expected identifier, found string +#let "v" = 1 diff --git a/tests/lang/typ/let-terminated.typ b/tests/lang/typ/let-terminated.typ new file mode 100644 index 000000000..6dda7bb21 --- /dev/null +++ b/tests/lang/typ/let-terminated.typ @@ -0,0 +1,28 @@ +// Test termination of let statements. + +--- +// Terminated by line break. +#let v1 = 1 +One + +// Terminated by semicolon. +#let v2 = 2; Two + +// Terminated by semicolon and line break. +#let v3 = 3; +Three + +// Terminated because expression ends. +// Error: 12-12 expected semicolon or line break +#let v4 = 4 Four + +// Terminated by semicolon even though we are in a paren group. +// Error: 2:19-2:19 expected expression +// Error: 1:19-1:19 expected closing paren +#let v5 = (1, 2 + ; Five + +#[test v1, 1] +#[test v2, 2] +#[test v3, 3] +#[test v4, 4] +#[test v5, (1, 2)] diff --git a/tests/lang/typ/let-value.typ b/tests/lang/typ/let-value.typ new file mode 100644 index 000000000..12e1ba78c --- /dev/null +++ b/tests/lang/typ/let-value.typ @@ -0,0 +1,11 @@ +// Test value of let binding. +// Ref: false + +--- +// Automatically initialized with none. +#let x +#[test x, none] + +// Manually initialized with one. +#let x = 1 +#[test x, 1] diff --git a/tests/lang/typ/let.typ b/tests/lang/typ/let.typ deleted file mode 100644 index 4f42e44c6..000000000 --- a/tests/lang/typ/let.typ +++ /dev/null @@ -1,66 +0,0 @@ -// Automatically initialized with `none`. -#let x -#[test x, none] - -// Initialized with `1`. -#let y = 1 -#[test y, 1] - -// Initialize with template, not terminated by semicolon in template. -#let v = [Hello; there] - -// Not terminated by line break due to parens. -#let x = ( - 1, - 2, - 3, -) -#[test x, (1, 2, 3)] - -// Multiple bindings in one line. -#let x = "a"; #let y = "b"; #[test x + y, "ab"] - -// Invalid name. -// Error: 6-7 expected identifier, found integer -#let 1 - -// Invalid name. -// Error: 6-7 expected identifier, found integer -#let 1 = 2 - -// Missing binding name. -// Error: 5-5 expected identifier -#let -x = 5 - -// Missing right-hand side. -// Error: 9-9 expected expression -#let a = - -// No name at all. -// Error: 11-11 expected identifier -The Fi#let;rst - -// Terminated with just a line break. -#let v = "a" -The Second #[test v, "a"] - -// Terminated with semicolon + line break. -#let v = "a"; -The Third #[test v, "a"] - -// Terminated with just a semicolon. -The#let v = "a"; Fourth #[test v, "a"] - -// Terminated by semicolon even though we are in a paren group. -// Error: 2:25-2:25 expected expression -// Error: 1:25-1:25 expected closing paren -The#let array = (1, 2 + ;Fifth #[test array, (1, 2)] - -// Not terminated. -// Error: 16-16 expected semicolon or line break -The#let v = "a"Sixth #[test v, "a"] - -// Not terminated. -// Error: 16-16 expected semicolon or line break -The#let v = "a" #[test v, "a"] Seventh diff --git a/tests/lang/typ/linebreaks.typ b/tests/lang/typ/linebreak.typ similarity index 75% rename from tests/lang/typ/linebreaks.typ rename to tests/lang/typ/linebreak.typ index ee2f453ae..e63929924 100644 --- a/tests/lang/typ/linebreaks.typ +++ b/tests/lang/typ/linebreak.typ @@ -1,6 +1,6 @@ -// Leading line break. -\ Leading +// Test forced line breaks. +--- // Directly after word. Line\ Break @@ -10,10 +10,14 @@ Line \ Break // Directly before word does not work. No \Break -// Trailing before paragraph break. -Paragraph 1 \ +--- +// Leading line break. +\ Leading -Paragraph 2 +// Trailing before paragraph break. +Trailing 1 \ + +Trailing 2 // Trailing before end of document. -Paragraph 3 \ +Trailing 3 \ diff --git a/tests/lang/typ/nbsp.typ b/tests/lang/typ/nbsp.typ index b2f099503..5af6c84f9 100644 --- a/tests/lang/typ/nbsp.typ +++ b/tests/lang/typ/nbsp.typ @@ -1,2 +1,5 @@ +// Test the non breaking space. + +--- // 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 36149dc89..753f1a09c 100644 --- a/tests/lang/typ/raw.typ +++ b/tests/lang/typ/raw.typ @@ -1,12 +1,17 @@ -#[font 8pt] +// Test raw blocks. +--- // No extra space. `A``B` +--- // Typst syntax inside. `#let x = 1` \ `#[f 1]` +--- +// Trimming. + // Space between "rust" and "let" is trimmed. The keyword ```rust let```. @@ -19,15 +24,17 @@ The keyword ```rust let```. ```py import this -def say_hi(): - print("Hi!") +def hi(): + print("Hi!") ``` +--- // Lots of backticks inside. ```` ```backticks``` ```` +--- // Unterminated. // Error: 2:1-2:1 expected backtick(s) `endless diff --git a/tests/lang/typ/values.typ b/tests/lang/typ/repr.typ similarity index 95% rename from tests/lang/typ/values.typ rename to tests/lang/typ/repr.typ index 7260cad10..1975a8e68 100644 --- a/tests/lang/typ/values.typ +++ b/tests/lang/typ/repr.typ @@ -1,10 +1,12 @@ // Test representation of values in the document. +--- +// Variables. + #let name = "Typst" #let ke-bab = "Kebab!" #let α = "Alpha" -// Variables. {name} \ {ke-bab} \ {α} \ @@ -12,11 +14,13 @@ // Error: 2-3 unknown variable {_} +--- // Literal values. {none} (empty) \ {true} \ {false} \ +--- // Numerical values. {1} \ {1.0e-4} \ @@ -29,12 +33,15 @@ {2.5rad} \ {45deg} \ +--- // Colors. {#f7a20500} \ +--- // Strings and escaping. {"hi"} \ {"a\n[]\"\u{1F680}string"} \ +--- // Templates. {[*{"Hi"} #[f 1]*]} diff --git a/tests/lang/typ/spacing.typ b/tests/lang/typ/spacing.typ new file mode 100644 index 000000000..54e49f2d7 --- /dev/null +++ b/tests/lang/typ/spacing.typ @@ -0,0 +1,27 @@ +// Test spacing around control flow structures. + +--- +// Spacing around let. + +// Error: 6-6 expected identifier +A#let;B \ +A#let x = 1;B #[test x, 1] \ +A #let x = 2;B #[test x, 2] \ +A#let x = 3; B #[test x, 3] \ + +--- +// Spacing around if-else. + +A#if true[B]C \ +A#if true[B] C \ +A #if true{"B"}C \ +A #if true{"B"} C \ +A#if false [] #else [B]C \ +A#if true [B] #else [] C \ + +--- +// Spacing around for loop. + +A#for _ #in (none,) [B]C \ +A#for _ #in (none,) [B] C \ +A #for _ #in (none,) [B]C \ diff --git a/tests/lang/typ/strong.typ b/tests/lang/typ/strong.typ new file mode 100644 index 000000000..63e6eb35b --- /dev/null +++ b/tests/lang/typ/strong.typ @@ -0,0 +1,14 @@ +// Test strong toggle. + +--- +// Basic. +*Strong!* + +// Inside of words. +Partly str*ength*ened. + +// Scoped to body. +#[box][*Scoped] to body. + +// Unterminated is fine. +*The End diff --git a/tests/lang/typ/text.typ b/tests/lang/typ/text.typ index 9c1e79770..d86f48957 100644 --- a/tests/lang/typ/text.typ +++ b/tests/lang/typ/text.typ @@ -1 +1,8 @@ +// Test simple text. + +--- Hello 🌏! + +--- +// Some code stuff in text. +let f() , ; : | + - /= == 12 "string" diff --git a/tests/typeset.rs b/tests/typeset.rs index 76436ae84..63831c229 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -141,12 +141,29 @@ fn test( let mut ok = true; let mut frames = vec![]; - let mut lines = 0; - for (i, part) in src.split("---").enumerate() { - let (part_ok, part_frames) = test_part(part, i, lines, env); - ok &= part_ok; - frames.extend(part_frames); + let mut compare_ref = true; + + let parts: Vec<_> = src.split("---").collect(); + for (i, part) in parts.iter().enumerate() { + let is_header = i == 0 + && parts.len() > 1 + && part + .lines() + .all(|s| s.starts_with("//") || s.chars().all(|c| c.is_whitespace())); + + if is_header { + for line in part.lines() { + if line.starts_with("// Ref: false") { + compare_ref = false; + } + } + } else { + let (part_ok, part_frames) = test_part(part, i, compare_ref, lines, env); + ok &= part_ok; + frames.extend(part_frames); + } + lines += part.lines().count() as u32; } @@ -179,9 +196,16 @@ fn test( ok } -fn test_part(src: &str, i: usize, lines: u32, env: &mut Env) -> (bool, Vec) { +fn test_part( + src: &str, + i: usize, + compare_ref: bool, + lines: u32, + env: &mut Env, +) -> (bool, Vec) { let map = LineMap::new(src); - let (compare_ref, ref_diags) = parse_metadata(src, &map); + let (local_compare_ref, ref_diags) = parse_metadata(src, &map); + let compare_ref = local_compare_ref.unwrap_or(compare_ref); let mut scope = library::new(); @@ -242,12 +266,20 @@ fn test_part(src: &str, i: usize, lines: u32, env: &mut Env) -> (bool, Vec (bool, SpanVec) { +fn parse_metadata(src: &str, map: &LineMap) -> (Option, SpanVec) { let mut diags = vec![]; - let mut compare_ref = true; + let mut compare_ref = None; for (i, line) in src.lines().enumerate() { - compare_ref &= !line.starts_with("// Ref: false"); + let line = line.trim(); + + if line.starts_with("// Ref: false") { + compare_ref = Some(false); + } + + if line.starts_with("// Ref: true") { + compare_ref = Some(true); + } let (level, rest) = if let Some(rest) = line.strip_prefix("// Warning: ") { (Level::Warning, rest)