diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-04-13 10:39:45 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-04-13 08:39:45 +0000 |
| commit | 020294fca9a7065d4b9cf4e677f606ebaaa29b00 (patch) | |
| tree | c0027ad22046e2726c22298461327823d6b88d53 /tests/suite/scripting | |
| parent | 72dd79210602ecc799726fc096b078afbb47f299 (diff) | |
Better test runner (#3922)
Diffstat (limited to 'tests/suite/scripting')
23 files changed, 2813 insertions, 0 deletions
diff --git a/tests/suite/scripting/blocks.typ b/tests/suite/scripting/blocks.typ new file mode 100644 index 00000000..f139b8c6 --- /dev/null +++ b/tests/suite/scripting/blocks.typ @@ -0,0 +1,143 @@ +// Test code blocks. + +--- code-block-basic-syntax --- + +// Evaluates to join of none, [My ] and the two loop bodies. +#{ + let parts = ("my fri", "end.") + [Hello, ] + for s in parts [#s] +} + +// Evaluates to join of the content and strings. +#{ + [How] + if true { + " are" + } + [ ] + if false [Nope] + [you] + "?" +} + +--- code-block-empty --- +// Nothing evaluates to none. +#test({}, none) + +--- code-block-let --- +// Let evaluates to none. +#test({ let v = 0 }, none) + +--- code-block-single-expression --- +// Evaluates to single expression. +#test({ "hello" }, "hello") + +--- code-block-multiple-expressions-single-line --- +// Evaluates to string. +#test({ let x = "m"; x + "y" }, "my") + +--- code-block-join-let-with-expression --- +// Evaluated to int. +#test({ + let x = 1 + let y = 2 + x + y +}, 3) + +--- code-block-join-expression-with-none --- +// String is joined with trailing none, evaluates to string. +#test({ + type("") + none +}, str) + +--- code-block-join-int-with-content --- +// Some things can't be joined. +#{ + [A] + // Error: 3-4 cannot join content with integer + 1 + [B] +} + +--- code-block-scope-in-markup --- +// Block directly in markup also creates a scope. +#{ let x = 1 } + +// Error: 7-8 unknown variable: x +#test(x, 1) + +--- code-block-scope-in-let --- +// Block in expression does create a scope. +#let a = { + let b = 1 + b +} + +#test(a, 1) + +// Error: 3-4 unknown variable: b +#{b} + +--- code-block-double-scope --- +// Double block creates a scope. +#{{ + import "module.typ": b + test(b, 1) +}} + +// Error: 2-3 unknown variable: b +#b + +--- code-block-nested-scopes --- +// Multiple nested scopes. +#{ + let a = "a1" + { + let a = "a2" + { + test(a, "a2") + let a = "a3" + test(a, "a3") + } + test(a, "a2") + } + test(a, "a1") +} + +--- code-block-multiple-literals-without-semicolon --- +// Multiple unseparated expressions in one line. +// Error: 4 expected semicolon or line break +#{1 2} + +--- code-block-multiple-expressions-without-semicolon --- +// Error: 13 expected semicolon or line break +// Error: 23 expected semicolon or line break +#{let x = -1 let y = 3 x + y} + +--- code-block-incomplete-expressions --- +#{ + // Error: 7-10 expected pattern, found string + for "v" + + // Error: 8 expected keyword `in` + // Error: 22 expected block + for v let z = 1 + 2 + + z +} + +--- code-block-unclosed --- +// Error: 2-3 unclosed delimiter +#{ + +--- code-block-unopened --- +// Error: 2-3 unexpected closing brace +#} + +--- content-block-in-markup-scope --- +// Content blocks also create a scope. +#[#let x = 1] + +// Error: 2-3 unknown variable: x +#x diff --git a/tests/suite/scripting/call.typ b/tests/suite/scripting/call.typ new file mode 100644 index 00000000..e79fc949 --- /dev/null +++ b/tests/suite/scripting/call.typ @@ -0,0 +1,200 @@ +// Test function calls. + +--- call-basic --- + +// Omitted space. +#let f() = {} +#[#f()*Bold*] + +// Call return value of function with body. +#let f(x, body) = (y) => [#x] + body + [#y] +#f(1)[2](3) + +// Don't parse this as a function. +#test (it) + +#let f(body) = body +#f[A] +#f()[A] +#f([A]) + +#let g(a, b) = a + b +#g[A][B] +#g([A], [B]) +#g()[A][B] + +--- call-aliased-function --- +// Call function assigned to variable. +#let alias = type +#test(alias(alias), type) + +--- call-complex-callee-expression --- +// Callee expressions. +#{ + // Wrapped in parens. + test((type)("hi"), str) + + // Call the return value of a function. + let adder(dx) = x => x + dx + test(adder(2)(5), 7) +} + +--- call-bad-type-bool-literal --- +// Error: 2-6 expected function, found boolean +#true() + +--- call-bad-type-string-var --- +#let x = "x" + +// Error: 2-3 expected function, found string +#x() + +--- call-bad-type-int-expr --- +#let f(x) = x + +// Error: 2-6 expected function, found integer +#f(1)(2) + +--- call-bad-type-content-expr --- +#let f(x) = x + +// Error: 2-6 expected function, found content +#f[1](2) + +--- call-args-trailing-comma --- +// Trailing comma. +#test(1 + 1, 2,) + +--- call-args-duplicate --- +// Error: 26-30 duplicate argument: font +#set text(font: "Arial", font: "Helvetica") + +--- call-args-bad-positional-as-named --- +// Error: 4-15 the argument `amount` is positional +// Hint: 4-15 try removing `amount:` +#h(amount: 0.5) + +--- call-args-bad-colon --- +// Error: 7-8 unexpected colon +#func(:) + +--- call-args-bad-token --- +// Error: 10-12 unexpected end of block comment +#func(a:1*/) + +--- call-args-missing-comma --- +// Error: 8 expected comma +#func(1 2) + +--- call-args-bad-name-and-incomplete-pair --- +// Error: 7-8 expected identifier, found integer +// Error: 9 expected expression +#func(1:) + +--- call-args-bad-name-int --- +// Error: 7-8 expected identifier, found integer +#func(1:2) + +--- call-args-bad-name-string --- +// Error: 7-12 expected identifier, found string +#func("abc": 2) + +--- call-args-bad-name-group --- +// Error: 7-10 expected identifier, found group +#func((x):1) + +--- call-args-lone-underscore --- +// Test that lone underscore works. +#test((1, 2, 3).map(_ => {}).len(), 3) + +--- call-args-spread-override --- +// Test standard argument overriding. +#{ + let f(style: "normal", weight: "regular") = { + "(style: " + style + ", weight: " + weight + ")" + } + + let myf(..args) = f(weight: "bold", ..args) + test(myf(), "(style: normal, weight: bold)") + test(myf(weight: "black"), "(style: normal, weight: black)") + test(myf(style: "italic"), "(style: italic, weight: bold)") +} + +--- call-args-spread-forward --- +// Test multiple calls. +#{ + let f(b, c: "!") = b + c + let g(a, ..sink) = a + f(..sink) + test(g("a", "b", c: "c"), "abc") +} + +--- call-args-spread-type-repr --- +// Test doing things with arguments. +#{ + let save(..args) = { + test(type(args), arguments) + test(repr(args), "(three: true, 1, 2)") + } + + save(1, 2, three: true) +} + +--- call-args-spread-array-and-dict --- +// Test spreading array and dictionary. +#{ + let more = (3, -3, 6, 10) + test(calc.min(1, 2, ..more), -3) + test(calc.max(..more, 9), 10) + test(calc.max(..more, 11), 11) +} + +#{ + let more = (c: 3, d: 4) + let tostr(..args) = repr(args) + test(tostr(a: 1, ..more, b: 2), "(a: 1, c: 3, d: 4, b: 2)") +} + +--- call-args-spread-none --- +// None is spreadable. +#let f() = none +#f(..none) +#f(..if false {}) +#f(..for x in () []) + +--- call-args-spread-string-invalid --- +// Error: 11-19 cannot spread string +#calc.min(.."nope") + +--- call-args-content-block-unclosed --- +// Error: 6-7 unclosed delimiter +#func[`a]` + +--- issue-886-args-sink --- +// Test bugs with argument sinks. +#let foo(..body) = repr(body.pos()) +#foo(a: "1", b: "2", 1, 2, 3, 4, 5, 6) + +--- issue-3144-unexpected-arrow --- +#let f(a: 10) = a(1) + 1 +#test(f(a: _ => 5), 6) + +--- issue-3502-space-and-comments-around-destructuring-colon --- +#let ( key : /* hi */ binding ) = ( key: "ok" ) +#test(binding, "ok") + +--- issue-3502-space-around-dict-colon --- +#test(( key : "value" ).key, "value") + +--- issue-3502-space-around-param-colon --- +// Test that a space after a named parameter is permissible. +#let f( param : v ) = param +#test(f( param /* ok */ : 2 ), 2) + +--- call-args-unclosed --- +// Error: 7-8 unclosed delimiter +#{func(} + +--- call-args-unclosed-string --- +// Error: 6-7 unclosed delimiter +// Error: 1:7-2:1 unclosed string +#func("] diff --git a/tests/suite/scripting/closure.typ b/tests/suite/scripting/closure.typ new file mode 100644 index 00000000..e3677d33 --- /dev/null +++ b/tests/suite/scripting/closure.typ @@ -0,0 +1,223 @@ +// Test closures. + +--- closure-without-params-non-atomic --- +// Don't parse closure directly in content. + +#let x = "x" + +// Should output `x => y`. +#x => y + +--- closure-without-captures --- +// Basic closure without captures. +#{ + let adder = (x, y) => x + y + test(adder(2, 3), 5) +} + +--- closure-as-arg --- +// Pass closure as argument and return closure. +// Also uses shorthand syntax for a single argument. +#{ + let chain = (f, g) => (x) => f(g(x)) + let f = x => x + 1 + let g = x => 2 * x + let h = chain(f, g) + test(h(2), 5) +} + +--- closure-capture-from-popped-stack-frame --- +// Capture environment. +#{ + let mark = "!" + let greet = { + let hi = "Hi" + name => { + hi + ", " + name + mark + } + } + + test(greet("Typst"), "Hi, Typst!") + + // Changing the captured variable after the closure definition has no effect. + mark = "?" + test(greet("Typst"), "Hi, Typst!") +} + +--- closure-shadows-outer-var --- +// Redefined variable. +#{ + let x = 1 + let f() = { + let x = x + 2 + x + } + test(f(), 3) +} + +--- closure-shadows-outer-var-import --- +// Import bindings. +#{ + let b = "module.typ" + let f() = { + import b: b + b + } + test(f(), 1) +} + +--- closure-shadows-outer-var-for-loop --- +// For loop bindings. +#{ + let v = (1, 2, 3) + let f() = { + let s = 0 + for v in v { s += v } + s + } + test(f(), 6) +} + +--- closure-let-basic --- +// Let + closure bindings. +#{ + let g = "hi" + let f() = { + let g() = "bye" + g() + } + test(f(), "bye") +} + +--- closure-let-args --- +// Parameter bindings. +#{ + let x = 5 + let g() = { + let f(x, y: x) = x + y + f + } + + test(g()(8), 13) +} + +--- closure-bad-capture --- +// Don't leak environment. +#{ + // Error: 16-17 unknown variable: x + let func() = x + let x = "hi" + func() +} + +--- closure-missing-arg-positional --- +// Too few arguments. +#{ + let types(x, y) = "[" + str(type(x)) + ", " + str(type(y)) + "]" + test(types(14%, 12pt), "[ratio, length]") + + // Error: 8-21 missing argument: y + test(types("nope"), "[string, none]") +} + +--- closure-too-many-args-positional --- +// Too many arguments. +#{ + let f(x) = x + 1 + + // Error: 8-13 unexpected argument + f(1, "two", () => x) +} + +--- closure-capture-in-lvalue --- +// Mutable method with capture in argument. +#let x = "b" +#let f() = { + let a = (b: 5) + a.at(x) = 10 + a +} +#f() + +--- closure-capture-mutate --- +#let x = () +#let f() = { + // Error: 3-4 variables from outside the function are read-only and cannot be modified + x.at(1) = 2 +} +#f() + +--- closure-named-args-basic --- +// Named arguments. +#{ + let greet(name, birthday: false) = { + if birthday { "Happy Birthday, " } else { "Hey, " } + name + "!" + } + + test(greet("Typst"), "Hey, Typst!") + test(greet("Typst", birthday: true), "Happy Birthday, Typst!") + + // Error: 23-35 unexpected argument: whatever + test(greet("Typst", whatever: 10)) +} + +--- closure-args-sink --- +// Parameter unpacking. +#let f((a, b), ..c) = (a, b, c) +#test(f((1, 2), 3, 4), (1, 2, (3, 4))) + +#let f((k: a, b), c: 3, (d,)) = (a, b, c, d) +#test(f((k: 1, b: 2), (4,)), (1, 2, 3, 4)) + +// Error: 8-14 expected identifier, found destructuring pattern +#let f((a, b): 0) = none + +// Error: 10-19 expected pattern, found array +#let f(..(a, b: c)) = none + +// Error: 10-16 expected pattern, found array +#let f(..(a, b)) = none + +--- closure-param-duplicate-positional --- +// Error: 11-12 duplicate parameter: x +#let f(x, x) = none + +--- closure-body-multiple-expressions --- +// Error: 21 expected comma +// Error: 22-23 expected pattern, found integer +// Error: 24-25 unexpected plus +// Error: 26-27 expected pattern, found integer +#let f = (x: () => 1 2 + 3) => 4 + +--- closure-param-duplicate-mixed --- +// Error: 14-15 duplicate parameter: a +// Error: 23-24 duplicate parameter: b +// Error: 35-36 duplicate parameter: b +#let f(a, b, a: none, b: none, c, b) = none + +--- closure-param-duplicate-spread --- +// Error: 13-14 duplicate parameter: a +#let f(a, ..a) = none + +--- closure-pattern-bad-string --- +// Error: 7-14 expected pattern, found string +#((a, "named": b) => none) + +--- closure-let-pattern-bad-string --- +// Error: 10-15 expected pattern, found string +#let foo("key": b) = key + +--- closure-param-keyword --- +// Error: 10-14 expected pattern, found `none` +// Hint: 10-14 keyword `none` is not allowed as an identifier; try `none_` instead +#let foo(none: b) = key + +--- closure-param-named-underscore --- +// Error: 10-11 expected identifier, found underscore +#let foo(_: 3) = none + +--- issue-non-atomic-closure --- +// Ensure that we can't have non-atomic closures. +#let x = 1 +#let c = [#(x) => (1, 2)] +#test(c.children.last(), [(1, 2)])) diff --git a/tests/suite/scripting/destructuring.typ b/tests/suite/scripting/destructuring.typ new file mode 100644 index 00000000..0a3c1c54 --- /dev/null +++ b/tests/suite/scripting/destructuring.typ @@ -0,0 +1,357 @@ +--- destructuring-group-1 --- +// This wasn't allowed. +#let ((x)) = 1 +#test(x, 1) + +--- destructuring-group-2 --- +// This also wasn't allowed. +#let ((a, b)) = (1, 2) +#test(a, 1) +#test(b, 2) + +--- destructuring-dict-underscore --- +// Here, `best` was accessed as a variable, where it shouldn't have. +#{ + (best: _) = (best: "brr") +} + +--- destructuring-dict-array-at --- +// Same here. +#{ + let array = (1, 2, 3, 4) + (test: array.at(1), best: _) = (test: "baz", best: "brr") + test(array, (1, "baz", 3, 4)) +} + +--- destructuring-dict-bad --- +// Error: 7-10 expected identifier, found group +// Error: 12-14 expected pattern, found integer +#let ((a): 10) = "world" + +--- destructuring-bad-duplicate --- +// Here, `a` is not duplicate, where it was previously identified as one. +#let f((a: b), (c,), a) = (a, b, c) +#test(f((a: 1), (2,), 3), (3, 1, 2)) + +--- destructuring-non-atomic --- +// Ensure that we can't have non-atomic destructuring. +#let x = 1 +#let c = [#() = ()] +#test(c.children.last(), [()]) + +--- destructuring-let-array --- +// Simple destructuring. +#let (a, b) = (1, 2) +#test(a, 1) +#test(b, 2) + +--- destructuring-let-array-single-item --- +#let (a,) = (1,) +#test(a, 1) + +--- destructuring-let-array-placeholders --- +// Destructuring with multiple placeholders. +#let (a, _, c, _) = (1, 2, 3, 4) +#test(a, 1) +#test(c, 3) + +--- destructuring-let-array-with-sink-at-end --- +// Destructuring with a sink. +#let (a, b, ..c) = (1, 2, 3, 4, 5, 6) +#test(a, 1) +#test(b, 2) +#test(c, (3, 4, 5, 6)) + +--- destructuring-let-array-with-sink-in-middle --- +// Destructuring with a sink in the middle. +#let (a, ..b, c) = (1, 2, 3, 4, 5, 6) +#test(a, 1) +#test(b, (2, 3, 4, 5)) +#test(c, 6) + +--- destructuring-let-array-with-sink-at-start-empty --- +// Destructuring with an empty sink. +#let (..a, b, c) = (1, 2) +#test(a, ()) +#test(b, 1) +#test(c, 2) + +--- destructuring-let-array-with-sink-in-middle-empty --- +// Destructuring with an empty sink. +#let (a, ..b, c) = (1, 2) +#test(a, 1) +#test(b, ()) +#test(c, 2) + +--- destructuring-let-array-with-sink-at-end-empty --- +// Destructuring with an empty sink. +#let (a, b, ..c) = (1, 2) +#test(a, 1) +#test(b, 2) +#test(c, ()) + +--- destructuring-let-array-with-sink-empty --- +// Destructuring with an empty sink and empty array. +#let (..a) = () +#test(a, ()) + +--- destructuring-let-array-with-unnamed-sink --- +// Destructuring with unnamed sink. +#let (a, .., b) = (1, 2, 3, 4) +#test(a, 1) +#test(b, 4) + +// Error: 10-11 duplicate binding: a +#let (a, a) = (1, 2) + +// Error: 12-15 only one destructuring sink is allowed +#let (..a, ..a) = (1, 2) + +// Error: 12-13 duplicate binding: a +#let (a, ..a) = (1, 2) + +// Error: 13-14 duplicate binding: a +#let (a: a, a) = (a: 1, b: 2) + +// Error: 13-20 expected pattern, found function call +#let (a, b: b.at(0)) = (a: 1, b: 2) + +// Error: 7-14 expected pattern, found function call +#let (a.at(0),) = (1,) + +--- destructuring-let-array-too-few-elements --- +// Error: 13-14 not enough elements to destructure +#let (a, b, c) = (1, 2) + +--- destructuring-let-array-too-few-elements-with-sink --- +// Error: 7-10 not enough elements to destructure +#let (..a, b, c, d) = (1, 2) + +--- destructuring-let-array-bool-invalid --- +// Error: 6-12 cannot destructure boolean +#let (a, b) = true + +--- destructuring-let-dict --- +// Simple destructuring. +#let (a: a, b, x: c) = (a: 1, b: 2, x: 3) +#test(a, 1) +#test(b, 2) +#test(c, 3) + +--- destructuring-let-dict-with-sink-at-end --- +// Destructuring with a sink. +#let (a: _, ..b) = (a: 1, b: 2, c: 3) +#test(b, (b: 2, c: 3)) + +--- destructuring-let-dict-with-sink-in-middle --- +// Destructuring with a sink in the middle. +#let (a: _, ..b, c: _) = (a: 1, b: 2, c: 3) +#test(b, (b: 2)) + +--- destructuring-let-dict-with-sink-at-end-empty --- +// Destructuring with an empty sink. +#let (a: _, ..b) = (a: 1) +#test(b, (:)) + +--- destructuring-let-dict-with-sink-empty --- +// Destructuring with an empty sink and empty dict. +#let (..a) = (:) +#test(a, (:)) + +--- destructuring-let-dict-with-unnamed-sink --- +// Destructuring with unnamed sink. +#let (a, ..) = (a: 1, b: 2) +#test(a, 1) + +--- destructuring-let-nested --- +// Nested destructuring. +#let ((a, b), (key: c)) = ((1, 2), (key: 3)) +#test((a, b, c), (1, 2, 3)) + +--- destructuring-let-dict-key-string-invalid --- +// Keyed destructuring is not currently supported. +// Error: 7-18 expected pattern, found string +#let ("spacy key": val) = ("spacy key": 123) +#val + +--- destructuring-let-dict-key-expr-invalid --- +// Keyed destructuring is not currently supported. +#let x = "spacy key" +// Error: 7-10 expected identifier, found group +#let ((x): v) = ("spacy key": 123) + +--- destructuring-let-array-trailing-placeholders --- +// Trailing placeholders. +// Error: 10-11 not enough elements to destructure +#let (a, _, _, _, _) = (1,) +#test(a, 1) + +--- destructuring-let-dict-patterns-invalid --- +// Error: 10-13 expected pattern, found string +// Error: 18-19 expected pattern, found integer +#let (a: "a", b: 2) = (a: 1, b: 2) + +--- destructuring-let-dict-shorthand-missing-key --- +// Error: 10-11 dictionary does not contain key "b" +#let (a, b) = (a: 1) + +--- destructuring-let-dict-missing-key --- +// Error: 10-11 dictionary does not contain key "b" +#let (a, b: b) = (a: 1) + +--- destructuring-let-dict-from-array --- +// Error: 7-11 cannot destructure named pattern from an array +#let (a: a, b) = (1, 2, 3) + +--- destructuring-during-loop-continue --- +// Test continue while destructuring. +// Should output "one = I \ two = II \ one = I". +#for num in (1, 2, 3, 1) { + let (word, roman) = if num == 1 { + ("one", "I") + } else if num == 2 { + ("two", "II") + } else { + continue + } + [#word = #roman \ ] +} + +--- destructuring-assign --- +// Test destructuring assignments. + +#let a = none +#let b = none +#let c = none +#((a,) = (1,)) +#test(a, 1) + +#((_, a, b, _) = (1, 2, 3, 4)) +#test(a, 2) +#test(b, 3) + +#((a, b, ..c) = (1, 2, 3, 4, 5, 6)) +#test(a, 1) +#test(b, 2) +#test(c, (3, 4, 5, 6)) + +#((a: a, b, x: c) = (a: 1, b: 2, x: 3)) +#test(a, 1) +#test(b, 2) +#test(c, 3) + +#let a = (1, 2) +#((a: a.at(0), b) = (a: 3, b: 4)) +#test(a, (3, 2)) +#test(b, 4) + +#let a = (1, 2) +#((a.at(0), b) = (3, 4)) +#test(a, (3, 2)) +#test(b, 4) + +#((a, ..b) = (1, 2, 3, 4)) +#test(a, 1) +#test(b, (2, 3, 4)) + +#let a = (1, 2) +#((b, ..a.at(0)) = (1, 2, 3, 4)) +#test(a, ((2, 3, 4), 2)) +#test(b, 1) + +--- destructuring-assign-commas --- +// Test comma placement in destructuring assignment. +#let array = (1, 2, 3) +#((key: array.at(1)) = (key: "hi")) +#test(array, (1, "hi", 3)) + +#let array = (1, 2, 3) +#((array.at(1)) = ("hi")) +#test(array, (1, "hi", 3)) + +#let array = (1, 2, 3) +#((array.at(1),) = ("hi",)) +#test(array, (1, "hi", 3)) + +#let array = (1, 2, 3) +#((array.at(1)) = ("hi",)) +#test(array, (1, ("hi",), 3)) + +--- destructuring-assign-nested --- +// Test nested destructuring assignment. +#let a +#let b +#let c +#(((a, b), (key: c)) = ((1, 2), (key: 3))) +#test((a, b, c), (1, 2, 3)) + +--- destructuring-assign-nested-invalid --- +#let array = (1, 2, 3) +// Error: 3-17 cannot destructure string +#((array.at(1),) = ("hi")) +#test(array, (1, ("hi",), 3)) + +--- issue-3275-normal-variable --- +// Normal variable. +#for x in (1, 2) {} +#for x in (a: 1, b: 2) {} +#for x in "foo" {} +#for x in bytes("😊") {} + +--- issue-3275-placeholder --- +// Placeholder. +#for _ in (1, 2) {} +#for _ in (a: 1, b: 2) {} +#for _ in "foo" {} +#for _ in bytes("😊") {} + +--- issue-3275-destructuring --- +// Destructuring. +#for (a,b,c) in (("a", 1, bytes(())), ("b", 2, bytes(""))) {} +#for (a, ..) in (("a", 1, bytes(())), ("b", 2, bytes(""))) {} +#for (k, v) in (a: 1, b: 2, c: 3) {} +#for (.., v) in (a: 1, b: 2, c: 3) {} + +--- issue-3275-loop-over-content --- +// Error: 11-17 cannot loop over content +#for x in [1, 2] {} + +--- issue-3275-loop-over-arguments --- +// Error: 11-25 cannot loop over arguments +#for _ in arguments("a") {} + +--- issue-3275-loop-over-integer --- +// Error: 16-21 cannot loop over integer +#for (x, y) in 12306 {} + +--- issue-3275-destructuring-loop-over-content --- +// Error: 16-22 cannot loop over content +#for (x, y) in [1, 2] {} + +--- issue-3275-destructuring-loop-over-string --- +// Error: 6-12 cannot destructure values of string +#for (x, y) in "foo" {} + +--- issue-3275-destructuring-loop-over-string-array --- +// Error: 6-12 cannot destructure string +#for (x, y) in ("foo", "bar") {} + +--- issue-3275-destructuring-loop-over-bytes --- +// Error: 6-12 cannot destructure values of bytes +#for (x, y) in bytes("😊") {} + +--- issue-3275-destructuring-loop-over-bytes-array --- +// Error: 6-12 cannot destructure bytes +#for (x, y) in (bytes((1,2)), bytes((1,2))) {} + +--- issue-3275-destructuring-loop-over-int-array --- +// Error: 6-12 cannot destructure integer +#for (x, y) in (1, 2) {} + +--- issue-3275-destructuring-loop-over-2d-array-1 --- +// Error: 10-11 not enough elements to destructure +#for (x, y) in ((1,), (2,)) {} + +--- issue-3275-destructuring-loop-over-2d-array-2 --- +// Error: 6-12 too many elements to destructure +#for (x, y) in ((1,2,3), (4,5,6)) {} diff --git a/tests/suite/scripting/field.typ b/tests/suite/scripting/field.typ new file mode 100644 index 00000000..7b2427e3 --- /dev/null +++ b/tests/suite/scripting/field.typ @@ -0,0 +1,76 @@ +// Test field access. + +--- field-function --- +// Test fields on function scopes. +#enum.item +#assert.eq +#assert.ne + +--- field-normal-function-invalid --- +// Error: 9-16 function `assert` does not contain field `invalid` +#assert.invalid + +--- field-elem-function-invalid --- +// Error: 7-14 function `enum` does not contain field `invalid` +#enum.invalid + +--- field-elem-function-invalid-call --- +// Error: 7-14 function `enum` does not contain field `invalid` +#enum.invalid() + +--- field-closure-invalid --- +// Closures cannot have fields. +#let f(x) = x +// Error: 4-11 cannot access fields on user-defined functions +#f.invalid + +--- field-bool-invalid --- +// Error: 8-10 cannot access fields on type boolean +#false.ok + +--- field-bool-keyword-invalid --- +// Error: 9-13 cannot access fields on type boolean +#{false.true} + +--- field-invalid-none --- +#{ + let object = none + // Error: 3-9 none does not have accessible fields + object.property = "value" +} + +--- field-invalid-int --- +#{ + let object = 10 + // Error: 3-9 integer does not have accessible fields + object.property = "value" +} + +--- field-mutable-invalid-symbol --- +#{ + let object = sym.eq.not + // Error: 3-9 cannot mutate fields on symbol + object.property = "value" +} + +--- field-mutable-invalid-module --- +#{ + let object = calc + // Error: 3-9 cannot mutate fields on module + object.property = "value" +} + +--- field-mutable-invalid-function --- +#{ + let object = calc.sin + // Error: 3-9 cannot mutate fields on function + object.property = "value" +} + +--- field-mutable-invalid-stroke --- +#{ + let s = 1pt + red + // Error: 3-4 fields on stroke are not yet mutable + // Hint: 3-4 try creating a new stroke with the updated field value instead + s.thickness = 5pt +} diff --git a/tests/suite/scripting/for.typ b/tests/suite/scripting/for.typ new file mode 100644 index 00000000..e98b3c72 --- /dev/null +++ b/tests/suite/scripting/for.typ @@ -0,0 +1,135 @@ +// Test for loops. + +--- for-loop-basic --- + +// Empty array. +#for x in () [Nope] + +// Dictionary is traversed in insertion order. +// Should output `Name: Typst. Age: 2.`. +#for (k, v) in (Name: "Typst", Age: 2) [ + #k: #v. +] + +// Block body. +// Should output `[1st, 2nd, 3rd, 4th]`. +#{ + "[" + for v in (1, 2, 3, 4) { + if v > 1 [, ] + [#v] + if v == 1 [st] + if v == 2 [nd] + if v == 3 [rd] + if v >= 4 [th] + } + "]" +} + +// Content block body. +// Should output `2345`. +#for v in (1, 2, 3, 4, 5, 6, 7) [#if v >= 2 and v <= 5 { repr(v) }] + +// Map captured arguments. +#let f1(..args) = args.pos().map(repr) +#let f2(..args) = args.named().pairs().map(p => repr(p.first()) + ": " + repr(p.last())) +#let f(..args) = (f1(..args) + f2(..args)).join(", ") +#f(1, a: 2) + +--- for-loop-integrated --- +#let out = () + +// Values of array. +#for v in (1, 2, 3) { + out += (v,) +} + +// Indices and values of array. +#for (i, v) in ("1", "2", "3").enumerate() { + test(repr(i + 1), v) +} + +// Pairs 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, ("a", 4), ("b", 5), "a", 6, "b", 7)) + +// Grapheme clusters of string. +#let first = true +#let joined = for c in "abc👩👩👦👦" { + if not first { ", " } + first = false + c +} + +#test(joined, "a, b, c, 👩👩👦👦") + +// Return value. +#test(for v in "" [], none) +#test(type(for v in "1" []), content) + +--- for-loop-over-bool --- +// Uniterable expression. +// Error: 11-15 cannot loop over boolean +#for v in true {} + +--- for-loop-over-string --- +// Keys and values of strings. +// Error: 6-12 cannot destructure values of string +#for (k, v) in "hi" { + dont-care +} + +--- for-loop-destructuring-without-parentheses --- +// Destructuring without parentheses. +// Error: 7-8 unexpected comma +// Hint: 7-8 destructuring patterns must be wrapped in parentheses +#for k, v in (a: 4, b: 5) { + dont-care +} + +--- for-loop-destructuring-half --- +// Error: 7-8 unexpected comma +// Hint: 7-8 destructuring patterns must be wrapped in parentheses +#for k, in () {} + +--- for-loop-incomplete --- +// Error: 5 expected pattern +#for + +// Error: 5 expected pattern +#for// + +// Error: 6 expected pattern +#{for} + +// Error: 7 expected keyword `in` +#for v + +// Error: 10 expected expression +#for v in + +// Error: 15 expected block +#for v in iter + +// Error: 5 expected pattern +#for +v in iter {} + +// Error: 7-10 expected pattern, found string +// Error: 16 expected block +A#for "v" thing + +// Error: 6-9 expected pattern, found string +#for "v" in iter {} + +// Error: 7 expected keyword `in` +#for a + b in iter {} diff --git a/tests/suite/scripting/get-rule.typ b/tests/suite/scripting/get-rule.typ new file mode 100644 index 00000000..24d4e5db --- /dev/null +++ b/tests/suite/scripting/get-rule.typ @@ -0,0 +1,67 @@ +--- get-rule-basic --- +// Test basic get rule. +#context test(text.lang, "en") +#set text(lang: "de") +#context test(text.lang, "de") +#text(lang: "es", context test(text.lang, "es")) + +--- get-rule-in-function --- +// Test whether context is retained in nested function. +#let translate(..args) = args.named().at(text.lang) +#set text(lang: "de") +#context test(translate(de: "Inhalt", en: "Contents"), "Inhalt") + +--- get-rule-in-array-callback --- +// Test whether context is retained in built-in callback. +#set text(lang: "de") +#context test( + ("en", "de", "fr").sorted(key: v => v != text.lang), + ("de", "en", "fr"), +) + +--- get-rule-folding --- +// Test folding. +#set rect(stroke: red) +#context { + test(type(rect.stroke), stroke) + test(rect.stroke.paint, red) +} +#[ + #set rect(stroke: 4pt) + #context test(rect.stroke, 4pt + red) +] +#context test(rect.stroke, stroke(red)) + +--- get-rule-figure-caption-collision --- +// We have one collision: `figure.caption` could be both the element and a get +// rule for the `caption` field, which is settable. We always prefer the +// element. It's unfortunate, but probably nobody writes +// `set figure(caption: ..)` anyway. +#test(type(figure.caption), function) +#context test(type(figure.caption), function) + +--- get-rule-assertion-failure --- +// Error: 10-31 Assertion failed: "en" != "de" +#context test(text.lang, "de") + +--- get-rule-unknown-field --- +// Error: 15-20 function `text` does not contain field `langs` +#context text.langs + +--- get-rule-inherent-field --- +// Error: 18-22 function `heading` does not contain field `body` +#context heading.body + +--- get-rule-missing-context-no-context --- +// Error: 7-11 can only be used when context is known +// Hint: 7-11 try wrapping this in a `context` expression +// Hint: 7-11 the `context` expression should wrap everything that depends on this function +#text.lang + +--- get-rule-unknown-field-no-context --- +// Error: 7-12 function `text` does not contain field `langs` +#text.langs + +--- get-rule-inherent-field-no-context --- +// Error: 10-14 function `heading` does not contain field `body` +#heading.body diff --git a/tests/suite/scripting/if.typ b/tests/suite/scripting/if.typ new file mode 100644 index 00000000..cc88925f --- /dev/null +++ b/tests/suite/scripting/if.typ @@ -0,0 +1,134 @@ +// Test if-else expressions. + +--- if-markup --- +// Test condition evaluation. +#if 1 < 2 [ + One. +] + +#if true == false [ + {Bad}, but we {dont-care}! +] + +--- if-condition-complex --- +// Braced condition. +#if {true} [ + One. +] + +// Content block in condition. +#if [] != none [ + Two. +] + +// Multi-line condition with parens. +#if ( + 1 + 1 + == 1 +) [ + Nope. +] else { + "Three." +} + +// Multiline. +#if false [ + Bad. +] else { + let point = "." + "Four" + point +} + +// Content block can be argument or body depending on whitespace. +#{ + if content == type[b] [Fi] else [Nope] + if content == type [Nope] else [ve.] +} + +#let i = 3 +#if i < 2 [ + Five. +] else if i < 4 [ + Six. +] else [ + Seven. +] + +--- if-else-if-else --- +// Test else if. + +#let nth(n) = { + str(n) + if n == 1 { "st" } + else if n == 2 { "nd" } + else if n == 3 { "rd" } + else { "th" } +} + +#test(nth(1), "1st") +#test(nth(2), "2nd") +#test(nth(3), "3rd") +#test(nth(4), "4th") +#test(nth(5), "5th") + +--- if-expression --- +// Value of if expressions. + +#{ + 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) +} + +--- if-condition-string-invalid --- +// 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 } + +--- if-condition-invalid-and-wrong-type --- +// Make sure that we don't complain twice. +// Error: 5-12 cannot add integer and string +#if 1 + "2" {} + +--- if-incomplete --- +// Error: 4 expected expression +#if + +// Error: 5 expected expression +#{if} + +// Error: 6 expected block +#if x + +// Error: 2-6 unexpected keyword `else` +#else {} + +// Should output `x`. +// Error: 4 expected expression +#if +x {} + +// Should output `something`. +// Error: 6 expected block +#if x something + +// Should output `A thing.` +// Error: 19 expected block +A#if false {} else thing + +#if a []else [b] +#if a [] else [b] +#if a {} else [b] diff --git a/tests/suite/scripting/import.typ b/tests/suite/scripting/import.typ new file mode 100644 index 00000000..820f81d6 --- /dev/null +++ b/tests/suite/scripting/import.typ @@ -0,0 +1,334 @@ +// Test function and module imports. + +--- import-basic --- +// Test basic syntax and semantics. + +// Test that this will be overwritten. +#let value = [foo] + +// Import multiple things. +#import "module.typ": fn, value +#fn[Like and Subscribe!] +#value + +// Should output `bye`. +// Stop at semicolon. +#import "module.typ": a, c;bye + +--- import-item-markup --- +// An item import. +#import "module.typ": item +#test(item(1, 2), 3) + +--- import-item-in-code --- +// Code mode +#{ + import "module.typ": b + test(b, 1) +} + +--- import-wildcard-in-markup --- +// A wildcard import. +#import "module.typ": * + +// It exists now! +#test(d, 3) + +--- import-item-renamed --- +// A renamed item import. +#import "module.typ": item as something +#test(something(1, 2), 3) + +--- import-items-renamed-mixed --- +// Mixing renamed and not renamed items. +#import "module.typ": fn, b as val, item as other +#test(val, 1) +#test(other(1, 2), 3) + +--- import-from-function-scope --- +// Test importing from function scopes. + +#import enum: item +#import assert.with(true): * + +#enum( + item(1)[First], + item(5)[Fifth] +) +#eq(10, 10) +#ne(5, 6) + +--- import-from-function-scope-item-renamed --- +// Test renaming items imported from function scopes. +#import assert: eq as aseq +#aseq(10, 10) + +--- import-from-file-bare --- +// A module import without items. +#import "module.typ" +#test(module.b, 1) +#test(module.item(1, 2), 3) +#test(module.push(2), 3) + +--- import-from-file-renamed --- +// A renamed module import without items. +#import "module.typ" as other +#test(other.b, 1) +#test(other.item(1, 2), 3) +#test(other.push(2), 3) + +--- import-from-file-items-renamed-mixed --- +// Mixing renamed module and items. +#import "module.typ" as newname: b as newval, item +#test(newname.b, 1) +#test(newval, 1) +#test(item(1, 2), 3) +#test(newname.item(1, 2), 3) + +--- import-from-function-scope-renamed --- +// Renamed module import with function scopes. +#import enum as othernum +#test(enum, othernum) + +--- import-from-function-scope-renamed-twice --- +// Mixing renamed module import from function with renamed item import. +#import assert as asrt +#import asrt: ne as asne +#asne(1, 2) + +--- import-module-item-name-mutating --- +// Edge case for module access that isn't fixed. +#import "module.typ" + +// Works because the method name isn't categorized as mutating. +#test((module,).at(0).item(1, 2), 3) + +// Doesn't work because of mutating name. +// Error: 2-11 cannot mutate a temporary value +#(module,).at(0).push() + +--- import-no-whitespace --- +// Who needs whitespace anyways? +#import"module.typ":* + +--- import-trailing-comma --- +// Allow the trailing comma. +#import "module.typ": a, c, + +--- import-source-field-access --- +// Usual importing syntax also works for function scopes +#let d = (e: enum) +#import d.e +#import d.e as renamed +#import d.e: item +#item(2)[a] + +--- import-item-rename-unnecessary --- +// Warning: 23-27 unnecessary import rename to same name +#import enum: item as item + +--- import-rename-unnecessary --- +// Warning: 17-21 unnecessary import rename to same name +#import enum as enum + +--- import-rename-unnecessary-mixed --- +// Warning: 17-21 unnecessary import rename to same name +#import enum as enum: item + +// Warning: 17-21 unnecessary import rename to same name +// Warning: 31-35 unnecessary import rename to same name +#import enum as enum: item as item + +--- import-item-rename-unnecessary-but-ok --- +// No warning on a case that isn't obviously pathological +#import "module.typ" as module + +--- import-from-closure-invalid --- +// Can't import from closures. +#let f(x) = x +// Error: 9-10 cannot import from user-defined functions +#import f: x + +--- import-from-closure-renamed-invalid --- +// Can't import from closures, despite renaming. +#let f(x) = x +// Error: 9-10 cannot import from user-defined functions +#import f as g + +--- import-from-with-closure-invalid --- +// Can't import from closures, despite modifiers. +#let f(x) = x +// Error: 9-18 cannot import from user-defined functions +#import f.with(5): x + +--- import-from-with-closure-literal-invalid --- +// Error: 9-18 cannot import from user-defined functions +#import () => {5}: x + +--- import-from-int-invalid --- +// Error: 9-10 expected path, module, function, or type, found integer +#import 5: something + +--- import-from-int-renamed-invalid --- +// Error: 9-10 expected path, module, function, or type, found integer +#import 5 as x + +--- import-from-string-invalid --- +// Error: 9-11 failed to load file (is a directory) +#import "": name + +--- import-from-string-renamed-invalid --- +// Error: 9-11 failed to load file (is a directory) +#import "" as x + +--- import-file-not-found-invalid --- +// Error: 9-20 file not found (searched at tests/suite/scripting/lib/0.2.1) +#import "lib/0.2.1" + +--- import-file-not-found-renamed-invalid --- +// Error: 9-20 file not found (searched at tests/suite/scripting/lib/0.2.1) +#import "lib/0.2.1" as x + +--- import-file-not-valid-utf-8 --- +// Some non-text stuff. +// Error: 9-35 file is not valid utf-8 +#import "/assets/images/rhino.png" + +--- import-item-not-found --- +// Unresolved import. +// Error: 23-35 unresolved import +#import "module.typ": non_existing + +--- import-cyclic --- +// Cyclic import of this very file. +// Error: 9-23 cyclic import +#import "./import.typ" + +--- import-cyclic-in-other-file --- +// Cyclic import in other file. +#import "./modules/cycle1.typ": * + +This is never reached. + +--- import-renamed-old-name --- +// Renaming does not import the old name (without items). +#import "./modules/chap1.typ" as something +#test(something.name, "Klaus") +// Error: 7-12 unknown variable: chap1 +#test(chap1.name, "Klaus") + +--- import-items-renamed-old-name --- +// Renaming does not import the old name (with items). +#import "./modules/chap1.typ" as something: name as other +#test(other, "Klaus") +#test(something.name, "Klaus") +// Error: 7-12 unknown variable: chap1 +#test(chap1.b, "Klaus") + +--- import-incomplete --- +// Error: 8 expected expression +#import + +--- import-item-string-invalid --- +// Error: 26-29 unexpected string +#import "module.typ": a, "b", c + +--- import-bad-token --- +// Error: 23-24 unexpected equals sign +#import "module.typ": = + +--- import-duplicate-comma --- +// An additional trailing comma. +// Error: 31-32 unexpected comma +#import "module.typ": a, b, c,, + +--- import-no-colon --- +// Error: 2:2 expected semicolon or line break +#import "module.typ +"stuff + +--- import-bad-token-star --- +// A star in the list. +// Error: 26-27 unexpected star +#import "module.typ": a, *, b + +--- import-item-after-star --- +// An item after a star. +// Error: 24 expected semicolon or line break +#import "module.typ": *, a + +--- import-bad-colon-in-items --- +// Error: 14-15 unexpected colon +// Error: 16-17 unexpected integer +#import "": a: 1 + +--- import-missing-comma --- +// Error: 14 expected comma +#import "": a b + +--- import-from-package-bare --- +// Test import without items. +#import "@test/adder:0.1.0" +#test(adder.add(2, 8), 10) + +--- import-from-package-items --- +// Test import with items. +#import "@test/adder:0.1.0": add +#test(add(2, 8), 10) + +--- import-from-package-required-compiler-version --- +// Test too high required compiler version. +// Error: 9-29 package requires typst 1.0.0 or newer (current version is VERSION) +#import "@test/future:0.1.0": future + +--- import-from-package-namespace-invalid-1 --- +// Error: 9-13 `@` is not a valid package namespace +#import "@@": * + +--- import-from-package-name-missing-1 --- +// Error: 9-16 package specification is missing name +#import "@heya": * + +--- import-from-package-namespace-invalid-2 --- +// Error: 9-15 `123` is not a valid package namespace +#import "@123": * + +--- import-from-package-name-missing-2 --- +// Error: 9-17 package specification is missing name +#import "@test/": * + +--- import-from-package-version-missing-1 --- +// Error: 9-22 package specification is missing version +#import "@test/mypkg": * + +--- import-from-package-name-invalid --- +// Error: 9-20 `$$$` is not a valid package name +#import "@test/$$$": * + +--- import-from-package-version-missing-2 --- +// Error: 9-23 package specification is missing version +#import "@test/mypkg:": * + +--- import-from-package-version-missing-minor --- +// Error: 9-24 version number is missing minor version +#import "@test/mypkg:0": * + +--- import-from-package-version-major-invalid-1 --- +// Error: 9-29 `latest` is not a valid major version +#import "@test/mypkg:latest": * + +--- import-from-package-version-major-invalid-2 --- +// Error: 9-29 `-3` is not a valid major version +#import "@test/mypkg:-3.0.0": * + +--- import-from-package-version-missing-patch-1 --- +// Error: 9-26 version number is missing patch version +#import "@test/mypkg:0.3": * + +--- import-from-package-version-missing-patch-2 --- +// Error: 9-27 version number is missing patch version +#import "@test/mypkg:0.3.": * + +--- import-from-file-package-lookalike --- +// Error: 9-28 file not found (searched at tests/suite/scripting/#test/mypkg:1.0.0) +#import "#test/mypkg:1.0.0": * diff --git a/tests/suite/scripting/include.typ b/tests/suite/scripting/include.typ new file mode 100644 index 00000000..e4da6d19 --- /dev/null +++ b/tests/suite/scripting/include.typ @@ -0,0 +1,32 @@ +// Test module includes. + +--- include-file --- +#set page(width: 200pt) + += Document + +// Include a file +#include "modules/chap1.typ" + +// Expression as a file name. +#let chap2 = include "modu" + "les/chap" + "2.typ" + +-- _Intermission_ -- +#chap2 + +--- include-file-not-found --- +#{ + // Error: 19-38 file not found (searched at tests/suite/scripting/modules/chap3.typ) + let x = include "modules/chap3.typ" +} + +--- include-no-bindings --- +#include "modules/chap1.typ" + +// The variables of the file should not appear in this scope. +// Error: 2-6 unknown variable: name +#name + +--- include-semicolon-or-linebreak --- +// Error: 18 expected semicolon or line break +#include "hi.typ" Hi diff --git a/tests/suite/scripting/let.typ b/tests/suite/scripting/let.typ new file mode 100644 index 00000000..2604c4ea --- /dev/null +++ b/tests/suite/scripting/let.typ @@ -0,0 +1,143 @@ +// Test let bindings. + +--- let-basic --- +// Automatically initialized with none. +#let x +#test(x, none) + +// Manually initialized with one. +#let z = 1 +#test(z, 1) + +// Syntax sugar for function definitions. +#let fill = conifer +#let f(body) = rect(width: 2cm, fill: fill, inset: 5pt, body) +#f[Hi!] + +--- let-termination --- +// Termination. + +// 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 + +#test(v1, 1) +#test(v2, 2) +#test(v3, 3) + +--- let-valid-idents --- +// Test what constitutes a valid Typst identifier. +#let name = 1 +#test(name, 1) +#let name_ = 1 +#test(name_, 1) +#let name-2 = 1 +#test(name-2, 1) +#let name_2 = 1 +#test(name_2, 1) +#let __name = 1 +#test(__name, 1) +#let ůñıćóðė = 1 +#test(ůñıćóðė, 1) + +--- let-binding-keyword-in-markup --- +// Error: 6-8 expected pattern, found keyword `as` +// Hint: 6-8 keyword `as` is not allowed as an identifier; try `as_` instead +#let as = 1 + 2 + +--- let-binding-keyword-in-code --- +#{ + // Error: 7-9 expected pattern, found keyword `as` + // Hint: 7-9 keyword `as` is not allowed as an identifier; try `as_` instead + let as = 10 +} + +--- let-ident-parenthesized --- +// Test parenthesised assignments. +#let (a) = (1, 2) + +--- let-incomplete --- +// Error: 5 expected pattern +#let + +// Error: 6 expected pattern +#{let} + +// Error: 6-9 expected pattern, found string +#let "v" + +// Error: 7 expected semicolon or line break +#let v 1 + +// Error: 9 expected expression +#let v = + +// Error: 6-9 expected pattern, found string +#let "v" = 1 + +// Terminated because expression ends. +// Error: 12 expected semicolon or line break +#let v4 = 4 Four + +// Terminated by semicolon even though we are in a paren group. +// Error: 18 expected expression +// Error: 11-12 unclosed delimiter +#let v5 = (1, 2 + ; Five + +// Error: 9-13 expected pattern, found boolean +#let (..true) = false + +--- underscore-invalid --- +#let _ = 4 + +#for _ in range(2) [] + +// Error: 2-3 unexpected underscore +#_ + +// Error: 8-9 expected expression, found underscore +#lorem(_) + +// Error: 3-4 expected expression, found underscore +#(_,) + +// Error: 3-4 expected expression, found underscore +#{_} + +// Error: 8-9 expected expression, found underscore +#{ 1 + _ } + +--- let-function-incomplete --- +// Error: 13 expected equals sign +#let func(x) + +// Error: 15 expected expression +#let func(x) = + +--- let-function-parenthesized --- +// This is not yet parsed in the ideal way. +// Error: 12 expected equals sign +#let (func)(x) + +--- let-function-parenthesized-with-init --- +// These errors aren't great. +// Error: 12 expected equals sign +// Error: 15-15 expected semicolon or line break +#let (func)(x) = 3 + +--- let-with-no-init-group --- +// This was unintentionally allowed ... +// Error: 9 expected equals sign +#let (a) + +--- let-with-no-init-destructuring --- +// ... where this wasn't. +// Error: 12 expected equals sign +#let (a, b) diff --git a/tests/suite/scripting/loop.typ b/tests/suite/scripting/loop.typ new file mode 100644 index 00000000..689c1c93 --- /dev/null +++ b/tests/suite/scripting/loop.typ @@ -0,0 +1,142 @@ +// Test break and continue in loops. + +--- loop-break-basic --- +// Test break. + +#let var = 0 +#let error = false + +#for i in range(10) { + var += i + if i > 5 { + break + error = true + } +} + +#test(var, 21) +#test(error, false) + +--- loop-break-join-basic --- +// Test joining with break. + +#let i = 0 +#let x = while true { + i += 1 + str(i) + if i >= 5 { + "." + break + } +} + +#test(x, "12345.") + +--- loop-continue-basic --- +// Test continue. + +#let i = 0 +#let x = 0 + +#while x < 8 { + i += 1 + if calc.rem(i, 3) == 0 { + continue + } + x += i +} + +// If continue did not work, this would equal 10. +#test(x, 12) + +--- loop-continue-join --- +// Test joining with continue. + +#let x = for i in range(5) { + "a" + if calc.rem(i, 3) == 0 { + "_" + continue + } + str(i) +} + +#test(x, "a_a1a2a_a4") + +--- loop-break-outside-of-loop --- +// Test break outside of loop. +#let f() = { + // Error: 3-8 cannot break outside of loop + break +} + +#for i in range(1) { + f() +} + +--- loop-break-join-in-last-arg --- +// Test break in function call. +#let identity(x) = x +#let out = for i in range(5) { + "A" + identity({ + "B" + break + }) + "C" +} + +#test(out, "AB") + +--- loop-continue-outside-of-loop-in-block --- +// Test continue outside of loop. + +// Error: 12-20 cannot continue outside of loop +#let x = { continue } + +--- loop-continue-outside-of-loop-in-markup --- +// Error: 2-10 cannot continue outside of loop +#continue + +--- loop-break-join-in-nested-blocks --- +// Should output `Hello World 🌎`. +#for _ in range(10) { + [Hello ] + [World #{ + [🌎] + break + }] +} + +--- loop-break-join-set-and-show --- +// Should output `Some` in red, `Some` in blue and `Last` in green. +// Everything should be in smallcaps. +#for color in (red, blue, green, yellow) [ + #set text(font: "Roboto") + #show: it => text(fill: color, it) + #smallcaps(if color != green [ + Some + ] else [ + Last + #break + ]) +] + +--- loop-break-join-in-set-rule-args --- +// Test break in set rule. +// Should output `Hi` in blue. +#for i in range(10) { + [Hello] + set text(blue, ..break) + [Not happening] +} + +--- loop-break-join-in-first-arg --- +// Test second block during break flow. + +#for i in range(10) { + table( + { [A]; break }, + for _ in range(3) [B] + ) +} diff --git a/tests/suite/scripting/methods.typ b/tests/suite/scripting/methods.typ new file mode 100644 index 00000000..5deea2cf --- /dev/null +++ b/tests/suite/scripting/methods.typ @@ -0,0 +1,51 @@ +// Test method calls. + +--- method-whitespace --- +// Test whitespace around dot. +#test( "Hi there" . split() , ("Hi", "there")) + +--- method-mutating --- +// Test mutating indexed value. +#{ + let matrix = (((1,), (2,)), ((3,), (4,))) + matrix.at(1).at(0).push(5) + test(matrix, (((1,), (2,)), ((3, 5), (4,)))) +} + +--- method-multiline --- +// Test multiline chain in code block. +#{ + let rewritten = "Hello. This is a sentence. And one more." + .split(".") + .map(s => s.trim()) + .filter(s => s != "") + .map(s => s + "!") + .join("\n ") + + test(rewritten, "Hello!\n This is a sentence!\n And one more!") +} + +--- method-unknown --- +// Error: 2:10-2:13 type array has no method `fun` +#let numbers = () +#numbers.fun() + +--- method-unknown-but-field-exists --- +// Error: 2:4-2:10 type content has no method `stroke` +// Hint: 2:4-2:10 did you mean to access the field `stroke`? +#let l = line(stroke: red) +#l.stroke() + +--- method-mutate-on-temporary --- +// Error: 2:2-2:43 cannot mutate a temporary value +#let numbers = (1, 2, 3) +#numbers.map(v => v / 2).sorted().map(str).remove(4) + +--- assign-to-method-invalid --- +// Error: 2:3-2:19 cannot mutate a temporary value +#let numbers = (1, 2, 3) +#(numbers.sorted() = 1) + +--- method-mutate-on-std-constant --- +// Error: 2-5 cannot mutate a constant: box +#box.push(1) diff --git a/tests/suite/scripting/module.typ b/tests/suite/scripting/module.typ new file mode 100644 index 00000000..8a67d225 --- /dev/null +++ b/tests/suite/scripting/module.typ @@ -0,0 +1,13 @@ +// SKIP +// A file to import in import / include tests. + +#let a +#let b = 1 +#let c = 2 +#let d = 3 +#let value = [hi] +#let item(a, b) = a + b +#let push(a) = a + 1 +#let fn = rect.with(fill: conifer, inset: 5pt) + +Some _includable_ text. diff --git a/tests/suite/scripting/modules/chap1.typ b/tests/suite/scripting/modules/chap1.typ new file mode 100644 index 00000000..13d0acf8 --- /dev/null +++ b/tests/suite/scripting/modules/chap1.typ @@ -0,0 +1,8 @@ +// SKIP +#let name = "Klaus" + +== Chapter 1 +#name stood in a field of wheat. There was nothing of particular interest about +the field #name just casually surveyed for any paths on which the corn would not +totally ruin his semi-new outdorsy jacket but then again, most of us spend +considerable time in non-descript environments. diff --git a/tests/suite/scripting/modules/chap2.typ b/tests/suite/scripting/modules/chap2.typ new file mode 100644 index 00000000..9c9d12d7 --- /dev/null +++ b/tests/suite/scripting/modules/chap2.typ @@ -0,0 +1,10 @@ +// SKIP +#let name = "Klaus" + +== Chapter 2 +Their motivations, however, were pretty descript, so to speak. #name had not yet +conceptualized their consequences, but that should change pretty quickly. #name +approached the center of the field and picked up a 4-foot long disk made from +what could only be cow manure. The hair on the back of #name' neck bristled as +he stared at the unusual sight. After studying the object for a while, he +promptly popped the question, "How much?" diff --git a/tests/suite/scripting/modules/cycle1.typ b/tests/suite/scripting/modules/cycle1.typ new file mode 100644 index 00000000..0f924ac7 --- /dev/null +++ b/tests/suite/scripting/modules/cycle1.typ @@ -0,0 +1,5 @@ +// SKIP +#import "cycle2.typ": * +#let inaccessible = "wow" + +This is the first element of an import cycle. diff --git a/tests/suite/scripting/modules/cycle2.typ b/tests/suite/scripting/modules/cycle2.typ new file mode 100644 index 00000000..69eb2033 --- /dev/null +++ b/tests/suite/scripting/modules/cycle2.typ @@ -0,0 +1,5 @@ +// SKIP +#import "cycle1.typ": * +#let val = "much cycle" + +This is the second element of an import cycle. diff --git a/tests/suite/scripting/ops.typ b/tests/suite/scripting/ops.typ new file mode 100644 index 00000000..0f13d212 --- /dev/null +++ b/tests/suite/scripting/ops.typ @@ -0,0 +1,465 @@ +// Test binary expressions. + +--- ops-add-content --- +// Test adding content. +#([*Hello* ] + [world!]) + +--- ops-unary-basic --- +// Test math operators. + +// Test plus and minus. +#for v in (1, 3.14, 12pt, 45deg, 90%, 13% + 10pt, 6.3fr) { + // Test plus. + test(+v, v) + + // Test minus. + test(-v, -1 * v) + test(--v, v) + + // Test combination. + test(-++ --v, -v) +} + +#test(-(4 + 2), 6-12) + +// Addition. +#test(2 + 4, 6) +#test("a" + "b", "ab") +#test("a" + if false { "b" }, "a") +#test("a" + if true { "b" }, "ab") +#test(13 * "a" + "bbbbbb", "aaaaaaaaaaaaabbbbbb") +#test((1, 2) + (3, 4), (1, 2, 3, 4)) +#test((a: 1) + (b: 2, c: 3), (a: 1, b: 2, c: 3)) + +--- ops-add-too-large --- +// Error: 3-26 value is too large +#(9223372036854775807 + 1) + +--- ops-binary-basic --- +// 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) + +// With block. +#test(if true { + 1 +} + 2, 3) + +// Mathematical identities. +#let nums = ( + 1, 3.14, + 12pt, 3em, 12pt + 3em, + 45deg, + 90%, + 13% + 10pt, 5% + 1em + 3pt, + 2.3fr, +) + +#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) != int { + test(v + v, 2.0 * v) + } + + if type(v) != relative and ("pt" not in repr(v) or "em" not in repr(v)) { + test(v / v, 1.0) + } +} + +// Make sure length, ratio and relative length +// - can all be added to / subtracted from each other, +// - multiplied with integers and floats, +// - divided by integers and floats. +#let dims = (10pt, 1em, 10pt + 1em, 30%, 50% + 3cm, 40% + 2em + 1cm) +#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 division of different numeric types with zero components. +#for a in (0pt, 0em, 0%) { + for b in (10pt, 10em, 10%) { + test((2 * b) / b, 2) + test((a + b * 2) / b, 2) + test(b / (b * 2 + a), 0.5) + } +} + +--- ops-multiply-inf-with-length --- +// Test that multiplying infinite numbers by certain units does not crash. +#(float("inf") * 1pt) +#(float("inf") * 1em) +#(float("inf") * (1pt + 1em)) + +--- ops-attempt-nan-length --- +// Test that trying to produce a NaN scalar (such as in lengths) does not crash. +#let infpt = float("inf") * 1pt +#test(infpt - infpt, 0pt) +#test(infpt + (-infpt), 0pt) +// TODO: this result is surprising +#test(infpt / float("inf"), 0pt) + +--- ops-unary-bool --- +// Test boolean operators. + +// Test not. +#test(not true, false) +#test(not false, true) + +// 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) + +--- ops-equality --- +// Test equality operators. + +// Most things compare by value. +#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("a" != "a", false) + +// Functions compare by identity. +#test(test == test, true) +#test((() => {}) == (() => {}), false) + +// Content compares field by field. +#let t = [a] +#test(t == t, true) +#test([] == [], true) +#test([a] == [a], true) +#test(grid[a] == grid[a], true) +#test(grid[a] == grid[b], false) + +--- ops-compare --- +// 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(10% < 20%, true) +#test(50% < 40% + 0pt, false) +#test(40% + 0pt < 50% + 0pt, true) +#test(1em < 2em, true) +#test((0, 1, 2, 4) < (0, 1, 2, 5), true) +#test((0, 1, 2, 4) < (0, 1, 2, 3), false) +#test((0, 1, 2, 3.3) > (0, 1, 2, 4), false) +#test((0, 1, 2) < (0, 1, 2, 3), true) +#test((0, 1, "b") > (0, 1, "a", 3), true) +#test((0, 1.1, 3) >= (0, 1.1, 3), true) +#test((0, 1, datetime(day: 1, month: 12, year: 2023)) <= (0, 1, datetime(day: 1, month: 12, year: 2023), 3), true) +#test(("a", 23, 40, "b") > ("a", 23, 40), true) +#test(() <= (), true) +#test(() >= (), true) +#test(() <= (1,), true) +#test((1,) <= (), false) + +--- ops-in --- +// Test `in` operator. +#test("hi" in "worship", true) +#test("hi" in ("we", "hi", "bye"), true) +#test("Hey" in "abHeyCd", true) +#test("Hey" in "abheyCd", false) +#test(5 in range(10), true) +#test(12 in range(10), false) +#test("" in (), false) +#test("key" in (key: "value"), true) +#test("value" in (key: "value"), false) +#test("Hey" not in "abheyCd", true) +#test("a" not +/* fun comment? */ in "abc", false) + +--- ops-not-trailing --- +// Error: 10 expected keyword `in` +#("a" not) + +--- func-with --- +// Test `with` method. + +// Apply positional arguments. +#let add(x, y) = x + y +#test(add.with(2)(3), 5) +#test(add.with(2, 3)(), 5) +#test(add.with(2).with(3)(), 5) +#test((add.with(2))(4), 6) +#test((add.with(2).with(3))(), 5) + +// Make sure that named arguments are overridable. +#let inc(x, y: 1) = x + y +#test(inc(1), 2) + +#let inc2 = inc.with(y: 2) +#test(inc2(2), 4) +#test(inc2(2, y: 4), 6) + +// Apply arguments to an argument sink. +#let times(..sink) = { + let res = sink.pos().product() + if sink.named().at("negate", default: false) { res *= -1 } + res +} +#test((times.with(2, negate: true).with(5))(), -10) +#test((times.with(2).with(5).with(negate: true))(), -10) +#test((times.with(2).with(5, negate: true))(), -10) +#test((times.with(2).with(negate: true))(5), -10) + +--- ops-precedence-basic --- +// 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) + +--- ops-precedence-boolean-ops --- +// Assignment binds stronger than boolean operations. +// Error: 2:3-2:8 cannot mutate a temporary value +#let x = false +#(not x = "a") + +--- ops-precedence-unary --- +// Precedence doesn't matter for chained unary operators. +// Error: 3-12 cannot apply '-' to boolean +#(-not true) + +--- ops-precedence-not-in --- +// Not in handles precedence. +#test(-1 not in (1, 2, 3), true) + +--- ops-precedence-parentheses --- +// Parentheses override precedence. +#test((1), 1) +#test((1+2)*-3, -9) + +// Error: 8-9 unclosed delimiter +#test({(1 + 1}, 2) + +--- ops-associativity-left --- +// 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) + +--- ops-associativity-right --- +// Assignment is right-associative. +#{ + let x = 1 + let y = 2 + x = y = "ok" + test(x, none) + test(y, "ok") +} + +--- ops-unary-minus-missing-expr --- +// Error: 4 expected expression +#(-) + +--- ops-add-missing-rhs --- +// Error: 10 expected expression +#test({1+}, 1) + +--- ops-mul-missing-rhs --- +// Error: 10 expected expression +#test({2*}, 2) + +--- ops-unary-plus-on-content --- +// Error: 3-13 cannot apply unary '+' to content +#(+([] + [])) + +--- ops-unary-plus-on-string --- +// Error: 3-6 cannot apply '-' to string +#(-"") + +--- ops-not-on-array --- +// Error: 3-9 cannot apply 'not' to array +#(not ()) + +--- ops-compare-relative-length-and-ratio --- +// Error: 3-19 cannot compare relative length and ratio +#(30% + 1pt <= 40%) + +--- ops-compare-em-with-abs --- +// Error: 3-14 cannot compare 1em with 10pt +#(1em <= 10pt) + +--- ops-compare-normal-float-with-nan --- +// Error: 3-22 cannot compare 2.2 with NaN +#(2.2 <= float("nan")) + +--- ops-compare-int-and-str --- +// Error: 3-26 cannot compare integer and string +#((0, 1, 3) > (0, 1, "a")) + +--- ops-compare-array-nested-failure --- +// Error: 3-42 cannot compare 3.5 with NaN +#((0, "a", 3.5) <= (0, "a", float("nan"))) + +--- ops-divide-by-zero-float --- +// Error: 3-12 cannot divide by zero +#(1.2 / 0.0) + +--- ops-divide-by-zero-int --- +// Error: 3-8 cannot divide by zero +#(1 / 0) + +--- ops-divide-by-zero-angle --- +// Error: 3-15 cannot divide by zero +#(15deg / 0deg) + +--- ops-binary-arithmetic-error-message --- +// Special messages for +, -, * and /. +// Error: 3-10 cannot add integer and string +#(1 + "2", 40% - 1) + +--- add-assign-int-and-str --- +// Error: 15-23 cannot add integer and string +#{ let x = 1; x += "2" } + +--- ops-divide-ratio-by-length --- +// Error: 4-13 cannot divide ratio by length +#( 10% / 5pt ) + +--- ops-divide-em-by-abs --- +// Error: 3-12 cannot divide these two lengths +#(1em / 5pt) + +--- ops-divide-relative-length-by-ratio --- +// Error: 3-19 cannot divide relative length by ratio +#((10% + 1pt) / 5%) + +--- ops-divide-relative-lengths --- +// Error: 3-28 cannot divide these two relative lengths +#((10% + 1pt) / (20% + 1pt)) + +--- ops-subtract-int-from-ratio --- +// Error: 13-20 cannot subtract integer from ratio +#((1234567, 40% - 1)) + +--- ops-multiply-int-with-bool --- +// Error: 3-11 cannot multiply integer with boolean +#(2 * true) + +--- ops-divide-int-by-length --- +// Error: 3-11 cannot divide integer by length +#(3 / 12pt) + +--- multiply-negative-int-with-str --- +// Error: 3-10 number must be at least zero +#(-1 * "") + +--- ops-assign --- +// 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") + +--- ops-assign-unknown-var-lhs --- +#{ + // Error: 3-6 unknown variable: a-1 + // Hint: 3-6 if you meant to use subtraction, try adding spaces around the minus sign + a-1 = 2 +} + +--- ops-assign-unknown-var-rhs --- +#{ + let a = 2 + a = 1-a + a = a -1 + + // Error: 7-10 unknown variable: a-1 + // Hint: 7-10 if you meant to use subtraction, try adding spaces around the minus sign + a = a-1 +} + +--- ops-assign-unknown-parenthesized-variable --- +// Error: 4-5 unknown variable: x +#((x) = "") + +--- ops-assign-destructuring-unknown-variable --- +// Error: 4-5 unknown variable: x +#((x,) = (1,)) + +--- ops-assign-to-temporary --- +// Error: 3-8 cannot mutate a temporary value +#(1 + 2 += 3) + +--- ops-assign-to-invalid-unary-op --- +// Error: 2:3-2:8 cannot apply 'not' to string +#let x = "Hey" +#(not x = "a") + +--- ops-assign-to-invalid-binary-op --- +// Error: 7-8 unknown variable: x +#(1 + x += 3) + +--- ops-assign-unknown-variable --- +// Error: 3-4 unknown variable: z +#(z = 1) + +--- ops-assign-to-std-constant --- +// Error: 3-7 cannot mutate a constant: rect +#(rect = "hi") + +--- ops-assign-to-shadowed-std-constant --- +// Works if we define rect beforehand +// (since then it doesn't resolve to the standard library version anymore). +#let rect = "" +#(rect = "hi") diff --git a/tests/suite/scripting/params.typ b/tests/suite/scripting/params.typ new file mode 100644 index 00000000..688124f2 --- /dev/null +++ b/tests/suite/scripting/params.typ @@ -0,0 +1,69 @@ +--- param-underscore-missing-argument --- +// Error: 17-20 missing argument: pattern parameter +#let f(a: 10) = a() + 1 +#f(a: _ => 5) + +--- params-sink-named --- +// ... but this was. +#let f(..x) = {} +#f(arg: 1) + +--- params-sink-unnamed --- +// unnamed spread +#let f(.., a) = a +#test(f(1, 2, 3), 3) + +// This wasn't allowed before the bug fix ... +#let f(..) = 2 +#test(f(arg: 1), 2) + +--- params-sink-bool-invalid --- +// Error: 10-14 expected pattern, found boolean +#let f(..true) = none + +--- params-sink-multiple-invalid --- +// Error: 13-16 only one argument sink is allowed +#let f(..a, ..b) = none + +--- params-sink-at-start --- +// Spread at beginning. +#{ + let f(..a, b) = (a, b) + test(repr(f(1)), "((), 1)") + test(repr(f(1, 2, 3)), "((1, 2), 3)") + test(repr(f(1, 2, 3, 4, 5)), "((1, 2, 3, 4), 5)") +} + +--- params-sink-in-middle --- +// Spread in the middle. +#{ + let f(a, ..b, c) = (a, b, c) + test(repr(f(1, 2)), "(1, (), 2)") + test(repr(f(1, 2, 3, 4, 5)), "(1, (2, 3, 4), 5)") +} + +--- params-sink-unnamed-empty --- +// Unnamed sink should just ignore any extra arguments. +#{ + let f(a, b: 5, ..) = (a, b) + test(f(4), (4, 5)) + test(f(10, b: 11), (10, 11)) + test(f(13, 20, b: 12), (13, 12)) + test(f(15, b: 16, c: 13), (15, 16)) +} + +--- params-sink-missing-arguments --- +#{ + let f(..a, b, c, d) = none + + // Error: 3-10 missing argument: d + f(1, 2) +} + +--- issue-1029-parameter-destructuring --- +// Test that underscore works in parameter patterns. +#test((1, 2, 3).zip((1, 2, 3)).map(((_, x)) => x), (1, 2, 3)) + +--- issue-1351-parameter-dictionary --- +// Error: 17-22 expected pattern, found string +#let foo((test: "bar")) = {} diff --git a/tests/suite/scripting/recursion.typ b/tests/suite/scripting/recursion.typ new file mode 100644 index 00000000..43fe848e --- /dev/null +++ b/tests/suite/scripting/recursion.typ @@ -0,0 +1,55 @@ +// Test recursive function calls. + +--- recursion-named --- +// Test with named function. +#let fib(n) = { + if n <= 2 { + 1 + } else { + fib(n - 1) + fib(n - 2) + } +} + +#test(fib(10), 55) + +--- recursion-unnamed-invalid --- +// Test with unnamed function. +// Error: 17-18 unknown variable: f +#let f = (n) => f(n - 1) +#f(10) + +--- recursion-named-returns-itself --- +// Test capturing with named function. +#let f = 10 +#let f() = f +#test(type(f()), function) + +--- recursion-unnamed-does-not-return-itself --- +// Test capturing with unnamed function. +#let f = 10 +#let f = () => f +#test(type(f()), int) + +--- recursion-shadowing --- +// Test redefinition. +#let f(x) = "hello" +#let f(x) = if x != none { f(none) } else { "world" } +#test(f(1), "world") + +--- recursion-maximum-depth --- +// Error: 15-21 maximum function call depth exceeded +#let rec(n) = rec(n) + 1 +#rec(1) + +--- recursion-via-include-in-layout --- +// Test cyclic imports during layout. +// Error: 2-38 maximum show rule depth exceeded +// Hint: 2-38 check whether the show rule matches its own output +#layout(_ => include "recursion.typ") + +--- recursion-show-math --- +// Test recursive show rules. +// Error: 22-25 maximum show rule depth exceeded +// Hint: 22-25 check whether the show rule matches its own output +#show math.equation: $x$ +$ x $ diff --git a/tests/suite/scripting/return.typ b/tests/suite/scripting/return.typ new file mode 100644 index 00000000..63e1c0b9 --- /dev/null +++ b/tests/suite/scripting/return.typ @@ -0,0 +1,87 @@ +// Test return out of functions. + +--- return-with-value --- +// Test return with value. +#let f(x) = { + return x + 1 +} + +#test(f(1), 2) + +--- return-join --- +// Test return with joining. + +#let f(x) = { + "a" + if x == 0 { + return "b" + } else if x == 1 { + "c" + } else { + "d" + return + "e" + } +} + +#test(f(0), "b") +#test(f(1), "ac") +#test(f(2), "ad") + +--- return-in-nested-content-block --- +// Test return with joining and content. + +#let f(text, caption: none) = { + text + if caption == none [\.#return] + [, ] + emph(caption) + [\.] +} + +#f(caption: [with caption])[My figure] + +#f[My other figure] + +--- return-outside-of-function --- +// Test return outside of function. + +#for x in range(5) { + // Error: 3-9 cannot return outside of function + return +} + +--- return-in-first-arg --- +// Test that the expression is evaluated to the end. +#let sum(..args) = { + let s = 0 + for v in args.pos() { + s += v + } + s +} + +#let f() = { + sum(..return, 1, 2, 3) + "nope" +} + +#test(f(), 6) + +--- return-in-content-block --- +// Test value return from content. +#let x = 3 +#let f() = [ + Hello 😀 + #return "nope" + World +] + +#test(f(), "nope") + +--- return-semicolon-or-linebreak --- +// Test rejection of extra value +#let f() = [ + // Error: 16-16 expected semicolon or line break + #return a + b Hello World +] diff --git a/tests/suite/scripting/while.typ b/tests/suite/scripting/while.typ new file mode 100644 index 00000000..5e452a89 --- /dev/null +++ b/tests/suite/scripting/while.typ @@ -0,0 +1,59 @@ +// Test while expressions. + +--- while-loop-basic --- +// Should output `2 4 6 8 10`. +#let i = 0 +#while i < 10 [ + #(i += 2) + #i +] + +// Should output `Hi`. +#let iter = true +#while iter { + iter = false + "Hi." +} + +#while false { + dont-care +} + +--- while-loop-expr --- +// Value of while loops. + +#test(while false {}, none) + +#let i = 0 +#test(type(while i < 1 [#(i += 1)]), content) + +--- while-loop-condition-content-invalid --- +// Condition must be boolean. +// Error: 8-14 expected boolean, found content +#while [nope] [nope] + +--- while-loop-condition-always-true --- +// Error: 8-25 condition is always true +#while 2 < "hello".len() {} + +--- while-loop-limit --- +// Error: 2:2-2:24 loop seems to be infinite +#let i = 1 +#while i > 0 { i += 1 } + +--- while-loop-incomplete --- +// Error: 7 expected expression +#while + +// Error: 8 expected expression +#{while} + +// Error: 9 expected block +#while x + +// Error: 7 expected expression +#while +x {} + +// Error: 9 expected block +#while x something |
