diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-29 13:37:25 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-29 14:18:13 +0100 |
| commit | 0efe669278a5e1c3f2985eba2f3360e91159c54a (patch) | |
| tree | 502712857c48f0decb5e698257c0a96d358a436e /tests/typ/compiler | |
| parent | 836692e73cff0356e409a9ba5b4887b86809d4ca (diff) | |
Reorganize library and tests
Diffstat (limited to 'tests/typ/compiler')
40 files changed, 2789 insertions, 0 deletions
diff --git a/tests/typ/compiler/array.typ b/tests/typ/compiler/array.typ new file mode 100644 index 00000000..cb8433cb --- /dev/null +++ b/tests/typ/compiler/array.typ @@ -0,0 +1,98 @@ +// Test arrays. +// Ref: false + +--- +// Ref: true + +#set page(width: 150pt) + +// Empty. +{()} + +// Not an array, just a parenthesized expression. +{(1)} + +// One item and trailing comma. +{(-1,)} + +// No trailing comma. +{(true, false)} + +// Multiple lines and items and trailing comma. +{("1" + , rgb("002") + ,)} + +--- +// Test lvalue and rvalue access. +{ + let array = (1, 2) + array(1) += 5 + array(0) + test(array, (1, 8)) +} + +--- +// Test rvalue out of bounds. +// Error: 2-14 array index out of bounds (index: 5, len: 3) +{(1, 2, 3)(5)} + +--- +// Test lvalue out of bounds. +{ + let array = (1, 2, 3) + // Error: 3-11 array index out of bounds (index: 3, len: 3) + array(3) = 5 +} + +--- +// Test negative indices. +{ + let array = (1, 2, 3, 4) + test(array(0), 1) + test(array(-1), 4) + test(array(-2), 3) + test(array(-3), 2) + test(array(-4), 1) +} + +--- +// Error: 2-15 array index out of bounds (index: -4, len: 3) +{(1, 2, 3)(-4)} + +--- +// Test non-collection indexing. + +{ + let x = 10pt + // Error: 3-4 expected collection, found length + x() = 1 +} + +--- +// Error: 3 expected closing paren +{(} + +// Error: 2-3 expected expression, found closing paren +{)} + +// Error: 4 expected comma +// Error: 4-6 unexpected end of block comment +{(1*/2)} + +// Error: 6-8 invalid number suffix +{(1, 1u 2)} + +// Error: 3-4 expected expression, found comma +{(,1)} + +// Missing expression makes named pair incomplete, making this an empty array. +// Error: 5 expected expression +{(a:)} + +// Named pair after this is already identified as an array. +// Error: 6-10 expected expression, found named pair +{(1, b: 2)} + +// Keyed pair after this is already identified as an array. +// Error: 6-14 expected expression, found keyed pair +{(1, "key": 2)} diff --git a/tests/typ/compiler/bench.typ b/tests/typ/compiler/bench.typ new file mode 100644 index 00000000..6aff1ac2 --- /dev/null +++ b/tests/typ/compiler/bench.typ @@ -0,0 +1,45 @@ +// Configuration with `page` and `font` functions. +#set page(width: 450pt, margin: 1cm) + +// There are variables and they can take normal values like strings, ... +#let city = "Berlin" + +// ... but also "content" values. While these contain markup, +// they are also values and can be summed, stored in arrays etc. +// There are also more standard control flow structures, like #if and #for. +#let university = [*Technische Universität {city}*] +#let faculty = [*Fakultät II, Institut for Mathematik*] + +// The `box` function just places content into a rectangular container. When +// the only argument to a function is a content block, the parentheses can be +// omitted (i.e. `f[a]` is the same as `f([a])`). +#box[ + // Backslash adds a forced line break. + #university \ + #faculty \ + Sekretariat MA \ + Dr. Max Mustermann \ + Ola Nordmann, John Doe +] +#align(right, box[*WiSe 2019/2020* \ Woche 3]) + +// Adds vertical spacing. +#v(6mm) + +// If the last argument to a function is a content block, we can also place it +// behind the parentheses. +#align(center)[ + // Markdown-like syntax for headings. + ==== 3. Übungsblatt Computerorientierte Mathematik II #v(4mm) + *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(4mm) + *Alle Antworten sind zu beweisen.* +] + +*1. Aufgabe* #align(right)[(1 + 1 + 2 Punkte)] + +Ein _Binärbaum_ ist ein Wurzelbaum, in dem jeder Knoten ≤ 2 Kinder hat. +Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel +zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Weges +von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel. + +#v(6mm) diff --git a/tests/typ/compiler/block.typ b/tests/typ/compiler/block.typ new file mode 100644 index 00000000..45f63f8e --- /dev/null +++ b/tests/typ/compiler/block.typ @@ -0,0 +1,144 @@ +// Test code blocks. +// Ref: false + +--- +// Ref: true + +// 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] + "?" +} + +--- +// Nothing evaluates to none. +#test({}, none) + +// Let evaluates to none. +#test({ let v = 0 }, none) + +// Evaluates to single expression. +#test({ "hello" }, "hello") + +// Evaluates to string. +#test({ let x = "m"; x + "y" }, "my") + +// Evaluated to int. +#test({ + let x = 1 + let y = 2 + x + y +}, 3) + +// String is joined with trailing none, evaluates to string. +#test({ + type("") + none +}, "string") + +--- +// Some things can't be joined. +{ + [A] + // Error: 3-4 cannot join content with integer + 1 + [B] +} + +--- +// Block directly in markup also creates a scope. +{ let x = 1 } + +// Error: 7-8 unknown variable +#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} + +--- +// Double block creates a scope. +{{ + import b from "module.typ" + test(b, 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") +} + +--- +// Content blocks also create a scope. +[#let x = 1] + +// Error: 2-3 unknown variable +{x} + +--- +// Multiple unseparated expressions in one line. + +// Error: 2-4 invalid number suffix +{1u} + +// Should output `1`. +// Error: 3 expected semicolon or line break +{1 2} + +// Should output `2`. +// Error: 12 expected semicolon or line break +// Error: 22 expected semicolon or line break +{let x = -1 let y = 3 x + y} + +// Should output `3`. +{ + // Error: 7-10 expected identifier, found string + for "v" + + // Error: 8 expected keyword `in` + for v let z = 1 + 2 + + z +} + +--- +// Error: 2 expected closing brace +{ + +--- +// Error: 1-2 unexpected closing brace +} diff --git a/tests/typ/compiler/break-continue.typ b/tests/typ/compiler/break-continue.typ new file mode 100644 index 00000000..fb3222fa --- /dev/null +++ b/tests/typ/compiler/break-continue.typ @@ -0,0 +1,147 @@ +// Test break and continue in loops. +// Ref: false + +--- +// 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) + +--- +// Test joining with break. + +#let i = 0 +#let x = while true { + i += 1 + str(i) + if i >= 5 { + "." + break + } +} + +#test(x, "12345.") + +--- +// Test continue. + +#let i = 0 +#let x = 0 + +#while x < 8 { + i += 1 + if mod(i, 3) == 0 { + continue + } + x += i +} + +// If continue did not work, this would equal 10. +#test(x, 12) + +--- +// Test joining with continue. + +#let x = for i in range(5) { + "a" + if mod(i, 3) == 0 { + "_" + continue + } + str(i) +} + +#test(x, "a_a1a2a_a4") + +--- +// Test break outside of loop. +#let f() = { + // Error: 3-8 cannot break outside of loop + break +} + +#for i in range(1) { + f() +} + +--- +// Test break in function call. +#let identity(x) = x +#let out = for i in range(5) { + "A" + identity({ + "B" + break + }) + "C" +} + +#test(out, "AB") + +--- +// Test continue outside of loop. + +// Error: 12-20 cannot continue outside of loop +#let x = { continue } + +--- +// Error: 1-10 cannot continue outside of loop +#continue + +--- +// Ref: true +// Should output `Hello World 🌎`. +#for _ in range(10) { + [Hello ] + [World { + [🌎] + break + }] +} + +--- +// Ref: true +// 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("Roboto") + #show it => text(fill: color, it) + #smallcaps(if color != green [ + Some + ] else [ + Last + #break + ]) +] + +--- +// Ref: true +// Test break in set rule. +// Should output `Hi` in blue. +#for i in range(10) { + [Hello] + set text(blue, ..break) + [Not happening] +} + +--- +// Test second block during break flow. +// Ref: true + +#for i in range(10) { + table( + { [A]; break }, + for _ in range(3) [B] + ) +} diff --git a/tests/typ/compiler/call.typ b/tests/typ/compiler/call.typ new file mode 100644 index 00000000..dc582c9c --- /dev/null +++ b/tests/typ/compiler/call.typ @@ -0,0 +1,106 @@ +// Test function calls. +// Ref: false + +--- +// Ref: true + +// Ommitted 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] + +--- +// Trailing comma. +#test(1 + 1, 2,) + +// Call function assigned to variable. +#let alias = type +#test(alias(alias), "function") + +// Callee expressions. +{ + // Wrapped in parens. + test((type)("hi"), "string") + + // Call the return value of a function. + let adder(dx) = x => x + dx + test(adder(2)(5), 7) +} + +--- +// Error: 28-47 duplicate argument +#set text(family: "Arial", family: "Helvetica") + +--- +// Error: 2-6 expected callable or collection, found boolean +{true()} + +--- +#let x = "x" + +// Error: 1-3 expected callable or collection, found string +#x() + +--- +#let f(x) = x + +// Error: 1-6 expected callable or collection, found integer +#f(1)(2) + +--- +#let f(x) = x + +// Error: 1-6 expected callable or collection, found content +#f[1](2) + +--- +// Error: 7-8 expected expression, found colon +#func(:) + +// Error: 10-12 unexpected end of block comment +#func(a:1*/) + +// Error: 8 expected comma +#func(1 2) + +// Error: 7-8 expected identifier, found integer +// Error: 9 expected expression +#func(1:) + +// Error: 7-8 expected identifier, found integer +#func(1:2) + +// Error: 7-12 expected identifier, found string +#func("abc": 2) + +// Error: 7-10 expected identifier, found group +{func((x):1)} + +--- +// Error: 2:1 expected closing bracket +#func[`a]` + +--- +// Error: 7 expected closing paren +{func(} + +--- +// Error: 2:1 expected quote +// Error: 2:1 expected closing paren +#func("] diff --git a/tests/typ/compiler/closure.typ b/tests/typ/compiler/closure.typ new file mode 100644 index 00000000..2c6c1ea0 --- /dev/null +++ b/tests/typ/compiler/closure.typ @@ -0,0 +1,158 @@ +// Test closures. +// Ref: false + +--- +// Don't parse closure directly in content. +// Ref: true + +#let x = "x" + +// Should output `x => y`. +#x => y + +--- +// Basic closure without captures. +{ + let adder = (x, y) => x + y + test(adder(2, 3), 5) +} + +--- +// 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) +} + +--- +// 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!") +} + +--- +// Redefined variable. +{ + let x = 1 + let f() = { + let x = x + 2 + x + } + test(f(), 3) +} + +--- +// Import bindings. +{ + let b = "module.typ" + let f() = { + import b from b + b + } + test(f(), 1) +} + +--- +// For loop bindings. +{ + let v = (1, 2, 3) + let f() = { + let s = 0 + for v in v { s += v } + s + } + test(f(), 6) +} + +--- +// Let + closure bindings. +{ + let g = "hi" + let f() = { + let g() = "bye" + g() + } + test(f(), "bye") +} + +--- +// Parameter bindings. +{ + let x = 5 + let g() = { + let f(x, y: x) = x + y + f + } + + test(g()(8), 13) +} + +--- +// Don't leak environment. +{ + // Error: 16-17 unknown variable + let func() = x + let x = "hi" + func() +} + +--- +// Too few arguments. +{ + let types(x, y) = "[" + type(x) + ", " + type(y) + "]" + test(types(14%, 12pt), "[ratio, length]") + + // Error: 13-21 missing argument: y + test(types("nope"), "[string, none]") +} + +--- +// Too many arguments. +{ + let f(x) = x + 1 + + // Error: 8-13 unexpected argument + f(1, "two", () => x) +} + +--- +// 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 + test(greet("Typst", whatever: 10)) +} + +--- +// Error: 6-16 expected identifier, named pair or argument sink, found keyed pair +{(a, "named": b) => none} + +--- +// Error: 10-15 expected identifier, found string +#let foo("key": b) = key + +--- +// Error: 10-14 expected identifier, found `none` +#let foo(none: b) = key diff --git a/tests/typ/compiler/comment.typ b/tests/typ/compiler/comment.typ new file mode 100644 index 00000000..4a4dc7ab --- /dev/null +++ b/tests/typ/compiler/comment.typ @@ -0,0 +1,33 @@ +// Test line and block comments. + +--- +// Line comment acts as spacing. +A// you +B + +// Block comment does not act as spacing, nested block comments. +C/* + /* */ +*/D + +// Works in code. +#test(type(/*1*/ 1) // +, "integer") + +// End of block comment in line comment. +// Hello */ + +// Nested line comment. +/*//*/ +Still comment. +*/ + +E + +--- +// End should not appear without start. +// Error: 7-9 unexpected end of block comment +/* */ */ + +// Unterminated is okay. +/* diff --git a/tests/typ/compiler/construct.typ b/tests/typ/compiler/construct.typ new file mode 100644 index 00000000..f535184c --- /dev/null +++ b/tests/typ/compiler/construct.typ @@ -0,0 +1,31 @@ +// Test node functions. + +--- +// Ensure that constructor styles aren't passed down the tree. +// The inner list should have no extra indent. +#set par(leading: 2pt) +#list(body-indent: 20pt, [First], list[A][B]) + +--- +// Ensure that constructor styles win, but not over outer styles. +// The outer paragraph should be right-aligned, +// but the B should be center-aligned. +#set par(align: center) +#par(align: right)[ + A #rect(width: 2cm, fill: conifer, inset: 4pt)[B] +] + +--- +// The inner rectangle should also be yellow here. +// (and therefore invisible) +[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))] + +--- +// The inner rectangle should not be yellow here. +A #rect(fill: yellow, inset: 5pt, rect()) B + +--- +// The constructor property should still work +// when there are recursive show rules. +#show list: set text(blue) +#list(label: "(a)", [A], list[B]) diff --git a/tests/typ/compiler/dict.typ b/tests/typ/compiler/dict.typ new file mode 100644 index 00000000..d791f77b --- /dev/null +++ b/tests/typ/compiler/dict.typ @@ -0,0 +1,75 @@ +// Test dictionaries. +// Ref: false + +--- +// Ref: true + +// Empty +{(:)} + +// Two pairs and string key. +#let dict = (normal: 1, "spacy key": 2) +#dict + +#test(dict.normal, 1) +#test(dict("spacy key"), 2) + +--- +// Test lvalue and rvalue access. +{ + let dict = (a: 1, "b b": 1) + dict("b b") += 1 + dict.state = (ok: true, err: false) + test(dict, (a: 1, "b b": 2, state: (ok: true, err: false))) + test(dict.state.ok, true) + dict("state").ok = false + test(dict.state.ok, false) + test(dict.state.err, false) +} + +--- +// Test rvalue missing key. +{ + let dict = (a: 1, b: 2) + // Error: 11-20 dictionary does not contain key "c" + let x = dict("c") +} + +--- +// Missing lvalue is automatically none-initialized. +{ + let dict = (:) + dict("b") += 1 + test(dict, (b: 1)) +} + +--- +// Error: 24-32 pair has duplicate key +{(first: 1, second: 2, first: 3)} + +--- +// Error: 17-23 pair has duplicate key +{(a: 1, "b": 2, "a": 3)} + +--- +// Simple expression after already being identified as a dictionary. +// Error: 9-10 expected named or keyed pair, found identifier +{(a: 1, b)} + +// Identified as dictionary due to initial colon. +// Error: 4-5 expected named or keyed pair, found integer +// Error: 5 expected comma +// Error: 12-16 expected identifier or string, found boolean +// Error: 17-18 expected expression, found colon +{(:1 b:"", true::)} + +--- +// Error: 3-15 cannot mutate a temporary value +{ (key: value).other = "some" } + +--- +{ + let object = none + // Error: 3-9 expected dictionary, found none + object.property = "value" +} diff --git a/tests/typ/compiler/field.typ b/tests/typ/compiler/field.typ new file mode 100644 index 00000000..abea87fb --- /dev/null +++ b/tests/typ/compiler/field.typ @@ -0,0 +1,40 @@ +// Test field access. +// Ref: false + +--- +// Test field on dictionary. +#let dict = (nothing: "ness", hello: "world") +#test(dict.nothing, "ness") +{ + let world = dict + .hello + + test(world, "world") +} + +--- +// Test field on node. +#show list: node => { + test(node.items.len(), 3) +} + +- A +- B +- C + +--- +// Error: 6-13 dictionary does not contain key "invalid" +{(:).invalid} + +--- +// Error: 2-7 cannot access field on boolean +{false.ok} + +--- +// Error: 29-32 unknown field "fun" +#show heading: node => node.fun += A + +--- +// Error: 8-12 expected identifier, found boolean +{false.true} diff --git a/tests/typ/compiler/for.typ b/tests/typ/compiler/for.typ new file mode 100644 index 00000000..822f7423 --- /dev/null +++ b/tests/typ/compiler/for.typ @@ -0,0 +1,123 @@ +// Test for loops. +// Ref: false + +--- +// Ref: true + +// Empty array. +#for x in () [Nope] + +// Dictionary is not traversed in insertion order. +// Should output `Age: 2. Name: Typst.`. +#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.positional().map(repr) +#let f2(..args) = args.named().pairs((k, v) => repr(k) + ": " + repr(v)) +#let f(..args) = (f1(..args) + f2(..args)).join(", ") +#f(1, a: 2) + +--- +#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") { + test(repr(i + 1), 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)) + +// 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") + +--- +// Uniterable expression. +// Error: 11-15 cannot loop over boolean +#for v in true {} + +--- +// Keys and values of strings. +// Error: 6-10 mismatched pattern +#for k, v in "hi" { + dont-care +} + +--- +// Error: 5 expected identifier +#for + +// Error: 7 expected identifier +#for// + +// Error: 5 expected identifier +{for} + +// Error: 7 expected keyword `in` +#for v + +// Error: 10 expected expression +#for v in + +// Error: 15 expected body +#for v in iter + +// Error: 5 expected identifier +#for +v in iter {} + +// Error: 7-10 expected identifier, found string +A#for "v" thing + +// Error: 6-9 expected identifier, found string +#for "v" in iter {} + +// Error: 7 expected keyword `in` +#for a + b in iter {} diff --git a/tests/typ/compiler/if.typ b/tests/typ/compiler/if.typ new file mode 100644 index 00000000..0d87c689 --- /dev/null +++ b/tests/typ/compiler/if.typ @@ -0,0 +1,136 @@ +// Test if-else expressions. + +--- +// Test condition evaluation. +#if 1 < 2 [ + One. +] + +#if true == false [ + {Bad}, but we {dont-care}! +] + +--- +// 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. +] + +--- +// Test else if. +// Ref: false + +#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") + +--- +// 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) +} + +--- +// 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" {} + +--- +// Error: 4 expected expression +#if + +// Error: 4 expected expression +{if} + +// Error: 6 expected body +#if x + +// Error: 1-6 unexpected keyword `else` +#else {} + +// Should output `x`. +// Error: 4 expected expression +#if +x {} + +// Should output `something`. +// Error: 6 expected body +#if x something + +// Should output `A thing.` +// Error: 19 expected body +A#if false {} else thing + +#if a []else [b] +#if a [] else [b] +#if a {} else [b] diff --git a/tests/typ/compiler/import.typ b/tests/typ/compiler/import.typ new file mode 100644 index 00000000..0620403d --- /dev/null +++ b/tests/typ/compiler/import.typ @@ -0,0 +1,116 @@ +// Test module imports. + +--- +// Test importing semantics. + +// A named import. +#import item from "module.typ" +#test(item(1, 2), 3) + +// Test that this will be overwritten. +#let value = [foo] + +// Import multiple things. +#import fn, value from "module.typ" +#fn[Like and Subscribe!] +#value + +// Code mode +{ + import b from "module.typ" + test(b, 1) +} + +// A wildcard import. +#import * from "module.typ" + +// It exists now! +#d + +// Who needs whitespace anyways? +#import*from"module.typ" + +// Should output `bye`. +// Stop at semicolon. +#import a, c from "module.typ";bye + +// Allow the trailing comma. +#import a, c, from "module.typ" + +--- +// Error: 19-21 failed to load file (is a directory) +#import name from "" + +--- +// Error: 16-27 file not found (searched at typ/compiler/lib/0.2.1) +#import * from "lib/0.2.1" + +--- +// Some non-text stuff. +// Error: 16-37 file is not valid utf-8 +#import * from "../../res/rhino.png" + +--- +// Unresolved import. +// Error: 9-21 unresolved import +#import non_existing from "module.typ" + +--- +// Cyclic import of this very file. +// Error: 16-30 cyclic import +#import * from "./import.typ" + +--- +// Cyclic import in other file. +#import * from "./modules/cycle1.typ" + +This is never reached. + +--- +// Error: 8 expected import items +// Error: 8 expected keyword `from` +#import + +// Error: 9-19 expected identifier, found string +// Error: 19 expected keyword `from` +#import "file.typ" + +// Error: 16-19 expected identifier, found string +// Error: 22 expected keyword `from` +#import afrom, "b", c + +// Error: 9 expected import items +#import from "module.typ" + +// Error: 9-10 expected expression, found assignment operator +// Error: 10 expected import items +#import = from "module.typ" + +// Error: 15 expected expression +#import * from + +// An additional trailing comma. +// Error: 17-18 expected expression, found comma +#import a, b, c,, from "module.typ" + +// Error: 1-6 unexpected keyword `from` +#from "module.typ" + +// Error: 2:2 expected semicolon or line break +#import * from "module.typ +"target + +// Error: 28 expected semicolon or line break +#import * from "module.typ" § 0.2.1 + +// A star in the list. +// Error: 12-13 expected expression, found star +#import a, *, b from "module.typ" + +// An item after a star. +// Error: 10 expected keyword `from` +#import *, a from "module.typ" + +--- +// Error: 9-13 expected identifier, found named pair +#import a: 1 from "" diff --git a/tests/typ/compiler/include.typ b/tests/typ/compiler/include.typ new file mode 100644 index 00000000..289fea21 --- /dev/null +++ b/tests/typ/compiler/include.typ @@ -0,0 +1,32 @@ +// Test module includes. + +--- +#set page(width: 200pt) + += Document + +// Include a file +#include "/typ/compiler/modules/chap1.typ" + +// Expression as a file name. +#let chap2 = include "modu" + "les/chap" + "2.typ" + +-- _Intermission_ -- +#chap2 + +--- +{ + // Error: 19-38 file not found (searched at typ/compiler/modules/chap3.typ) + let x = include "modules/chap3.typ" +} + +--- +#include "modules/chap1.typ" + +// The variables of the file should not appear in this scope. +// Error: 1-6 unknown variable +#name + +--- +// Error: 18 expected semicolon or line break +#include "hi.typ" Hi diff --git a/tests/typ/compiler/label.typ b/tests/typ/compiler/label.typ new file mode 100644 index 00000000..795c0435 --- /dev/null +++ b/tests/typ/compiler/label.typ @@ -0,0 +1,58 @@ +// Test labels. + +--- +// Test labelled headings. +#show heading: set text(10pt) +#show heading.where(label: <intro>): underline + += Introduction <intro> +The beginning. + += Conclusion +The end. + +--- +// Test label after expression. +#show strong.where(label: <v>): set text(red) + +#let a = [*A*] +#let b = [*B*] +#a <v> #b + +--- +// Test labelled text. +#show "t": it => { + set text(blue) if it.label == <last> + it +} + +This is a thing [that <last>] happened. + +--- +// Test abusing dynamic labels for styling. +#show <red>: set text(red) +#show <blue>: set text(blue) + +*A* *B* <red> *C* #label("bl" + "ue") *D* + +--- +// Test that label ignores parbreak. +#show <hide>: none + +_Hidden_ +<hide> + +_Hidden_ + +<hide> +_Visible_ + +--- +// Test that label only works within one content block. +#show <strike>: strike +*This is* [<strike>] *protected.* +*This is not.* <strike> + +--- +// Test that incomplete label is text. +1 < 2 is #if 1 < 2 [not] a label. diff --git a/tests/typ/compiler/let.typ b/tests/typ/compiler/let.typ new file mode 100644 index 00000000..c3be64a5 --- /dev/null +++ b/tests/typ/compiler/let.typ @@ -0,0 +1,68 @@ +// Test let bindings. + +--- +// 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 rect(body) = rect(width: 2cm, fill: fill, inset: 5pt, body) +#rect[Hi!] + +--- +// 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) + +--- +// Error: 5 expected identifier +#let + +// Error: 5 expected identifier +{let} + +// Error: 6-9 expected identifier, found string +#let "v" + +// Error: 7 expected semicolon or line break +#let v 1 + +// Error: 9 expected expression +#let v = + +// Error: 6-9 expected identifier, 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: 18 expected closing paren +#let v5 = (1, 2 + ; Five + +--- +// Error: 13 expected body +#let func(x) + +// Error: 15 expected expression +#let func(x) = diff --git a/tests/typ/compiler/methods-collection.typ b/tests/typ/compiler/methods-collection.typ new file mode 100644 index 00000000..fcebf640 --- /dev/null +++ b/tests/typ/compiler/methods-collection.typ @@ -0,0 +1,115 @@ +// Test the collection methods. +// Ref: false + +--- +// Test the `len` method. +#test(().len(), 0) +#test(("A", "B", "C").len(), 3) +#test("Hello World!".len(), 12) +#test((a: 1, b: 2).len(), 2) + +--- +// The the `first` and `last` methods. +#test(().first(), none) +#test(().last(), none) +#test((1,).first(), 1) +#test((2,).last(), 2) +#test((1, 2, 3).first(), 1) +#test((1, 2, 3).last(), 3) + +--- +// Test the `push` and `pop` methods. +{ + let tasks = (a: (1, 2, 3), b: (4, 5, 6)) + tasks("a").pop() + tasks("b").push(7) + test(tasks("a"), (1, 2)) + test(tasks("b"), (4, 5, 6, 7)) +} + +--- +// Test the `insert` and `remove` methods. +{ + let array = (0, 1, 2, 4, 5) + array.insert(3, 3) + test(array, range(6)) + array.remove(1) + test(array, (0, 2, 3, 4, 5)) +} + +--- +// Error: 2:17-2:19 missing argument: index +#let numbers = () +{ numbers.insert() } + +--- +// Test the `slice` method. +#test((1, 2, 3, 4).slice(2), (3, 4)) +#test(range(10).slice(2, 6), (2, 3, 4, 5)) +#test(range(10).slice(4, count: 3), (4, 5, 6)) +#test(range(10).slice(-5, count: 2), (5, 6)) +#test((1, 2, 3).slice(2, -2), ()) +#test((1, 2, 3).slice(-2, 2), (2,)) +#test((1, 2, 3).slice(-3, 2), (1, 2)) +#test("ABCD".split("").slice(1, -1).join("-"), "A-B-C-D") + +--- +// Error: 3-31 array index out of bounds (index: 12, len: 10) +{ range(10).slice(9, count: 3) } + +--- +// Error: 3-25 array index out of bounds (index: -4, len: 3) +{ (1, 2, 3).slice(0, -4) } + +--- +// Test the `position` method. +#test(("Hi", "❤️", "Love").position(s => s == "❤️"), 1) +#test(("Bye", "💘", "Apart").position(s => s == "❤️"), none) +#test(("A", "B", "CDEF", "G").position(v => v.len() > 2), 2) + +--- +// Test the `rev` method. +#test(range(3).rev(), (2, 1, 0)) + +--- +// Test the `join` method. +#test(().join(), none) +#test((1,).join(), 1) +#test(("a", "b", "c").join(), "abc") +#test("(" + ("a", "b", "c").join(", ") + ")", "(a, b, c)") + +--- +// Error: 2-22 cannot join boolean with boolean +{(true, false).join()} + +--- +// Error: 2-20 cannot join string with integer +{("a", "b").join(1)} + +--- +// Test joining content. +// Ref: true +{([One], [Two], [Three]).join([, ], last: [ and ])}. + +--- +// Test the `sorted` method. +#test(().sorted(), ()) +#test(((true, false) * 10).sorted(), (false,) * 10 + (true,) * 10) +#test(("it", "the", "hi", "text").sorted(), ("hi", "it", "text", "the")) +#test((2, 1, 3, 10, 5, 8, 6, -7, 2).sorted(), (-7, 1, 2, 2, 3, 5, 6, 8, 10)) + +--- +// Error: 2-26 cannot order content and content +{([Hi], [There]).sorted()} + +--- +// Test dictionary methods. +#let dict = (a: 3, c: 2, b: 1) +#test("c" in dict, true) +#test(dict.len(), 3) +#test(dict.values(), (3, 1, 2)) +#test(dict.pairs((k, v) => k + str(v)).join(), "a3b1c2") + +{ dict.remove("c") } +#test("c" in dict, false) +#test(dict, (a: 3, b: 1)) diff --git a/tests/typ/compiler/methods-color.typ b/tests/typ/compiler/methods-color.typ new file mode 100644 index 00000000..1188030a --- /dev/null +++ b/tests/typ/compiler/methods-color.typ @@ -0,0 +1,22 @@ +// Test color modification methods. + +--- +// Test gray color modification. +#test(luma(20%).lighten(50%), luma(60%)) +#test(luma(80%).darken(20%), luma(63.9%)) +#test(luma(80%).negate(), luma(20%)) + +--- +// Test CMYK color conversion. +// Ref: true +#let c = cmyk(50%, 64%, 16%, 17%) +#rect(width: 1cm, fill: cmyk(69%, 11%, 69%, 41%)) +#rect(width: 1cm, fill: c) +#rect(width: 1cm, fill: c.negate()) + +#for x in range(0, 11) { + square(width: 9pt, fill: c.lighten(x * 10%)) +} +#for x in range(0, 11) { + square(width: 9pt, fill: c.darken(x * 10%)) +} diff --git a/tests/typ/compiler/methods-str.typ b/tests/typ/compiler/methods-str.typ new file mode 100644 index 00000000..aead4aa4 --- /dev/null +++ b/tests/typ/compiler/methods-str.typ @@ -0,0 +1,117 @@ +// Test the string methods. +// Ref: false + +--- +// Test the `slice` method. +#test("abc".slice(1, 2), "b") +#test("abc🏡def".slice(2, 7), "c🏡") +#test("abc🏡def".slice(2, -2), "c🏡d") +#test("abc🏡def".slice(-3, -1), "de") + +--- +// Test the `contains` method. +#test("abc".contains("b"), true) +#test("b" in "abc", true) +#test("1234f".contains(regex("\d")), true) +#test(regex("\d") in "1234f", true) +#test("abc".contains("d"), false) +#test("1234g" in "1234f", false) +#test("abc".contains(regex("^[abc]$")), false) +#test("abc".contains(regex("^[abc]+$")), true) + +--- +// Test the `starts-with` and `ends-with` methods. +#test("Typst".starts-with("Ty"), true) +#test("Typst".starts-with(regex("[Tt]ys")), false) +#test("Typst".starts-with("st"), false) +#test("Typst".ends-with("st"), true) +#test("Typst".ends-with(regex("\d*")), true) +#test("Typst".ends-with(regex("\d+")), false) +#test("Typ12".ends-with(regex("\d+")), true) + +--- +// Test the `find` and `position` methods. +#let date = regex("\d{2}:\d{2}") +#test("Hello World".find("World"), "World") +#test("Hello World".position("World"), 6) +#test("It's 12:13 now".find(date), "12:13") +#test("It's 12:13 now".position(date), 5) + +--- +// Test the `match` method. +#test("Is there a".match("for this?"), none) +#test( + "The time of my life.".match(regex("[mit]+e")), + (start: 4, end: 8, text: "time", captures: ()), +) + +// Test the `matches` method. +#test("Hello there".matches("\d"), ()) +#test("Day by Day.".matches("Day"), ( + (start: 0, end: 3, text: "Day", captures: ()), + (start: 7, end: 10, text: "Day", captures: ()), +)) + +// Compute the sum of all timestamps in the text. +#let timesum(text) = { + let time = 0 + for match in text.matches(regex("(\d+):(\d+)")) { + let caps = match.captures + time += 60 * int(caps(0)) + int(caps(1)) + } + str(int(time / 60)) + ":" + str(mod(time, 60)) +} + +#test(timesum(""), "0:0") +#test(timesum("2:70"), "3:10") +#test(timesum("1:20, 2:10, 0:40"), "4:10") + +--- +// Test the `replace` method. +#test("ABC".replace("", "-"), "-A-B-C-") +#test("Ok".replace("Ok", "Nope", count: 0), "Ok") +#test("to add?".replace("", "How ", count: 1), "How to add?") +#test("AB C DEF GH J".replace(" ", ",", count: 2), "AB,C,DEF GH J") +#test("Walcemo" + .replace("o", "k") + .replace("e", "o") + .replace("k", "e") + .replace("a", "e"), + "Welcome" +) +#test("123".replace(regex("\d$"), "_"), "12_") +#test("123".replace(regex("\d{1,2}$"), "__"), "1__") + +--- +// Test the `trim` method. +#let str = "Typst, LaTeX, Word, InDesign" +#let array = ("Typst", "LaTeX", "Word", "InDesign") +#test(str.split(",").map(s => s.trim()), array) +#test("".trim(), "") +#test(" abc ".trim(at: start), "abc ") +#test(" abc ".trim(at: end, repeat: true), " abc") +#test(" abc".trim(at: start, repeat: false), "abc") +#test("aabcaa".trim("a", repeat: false), "abca") +#test("aabca".trim("a", at: start), "bca") +#test("aabcaa".trim("a", at: end, repeat: false), "aabca") +#test("".trim(regex(".")), "") +#test("123abc456".trim(regex("\d")), "abc") +#test("123abc456".trim(regex("\d"), repeat: false), "23abc45") +#test("123a4b5c678".trim(regex("\d"), repeat: true), "a4b5c") +#test("123a4b5c678".trim(regex("\d"), repeat: false), "23a4b5c67") +#test("123abc456".trim(regex("\d"), at: start), "abc456") +#test("123abc456".trim(regex("\d"), at: end), "123abc") +#test("123abc456".trim(regex("\d+"), at: end, repeat: false), "123abc") +#test("123abc456".trim(regex("\d{1,2}$"), repeat: false), "123abc4") +#test("hello world".trim(regex(".")), "") + +--- +// Error: 17-21 expected either `start` or `end` +{"abc".trim(at: left)} + +--- +// Test the `split` method. +#test("abc".split(""), ("", "a", "b", "c", "")) +#test("abc".split("b"), ("a", "c")) +#test("a123c".split(regex("\d")), ("a", "", "", "c")) +#test("a123c".split(regex("\d+")), ("a", "c")) diff --git a/tests/typ/compiler/methods.typ b/tests/typ/compiler/methods.typ new file mode 100644 index 00000000..07f6e410 --- /dev/null +++ b/tests/typ/compiler/methods.typ @@ -0,0 +1,46 @@ +// Test method calls. +// Ref: false + +--- +// Test whitespace around dot. +#test( "Hi there" . split() , ("Hi", "there")) + +--- +// Test mutating indexed value. +{ + let matrix = (((1,), (2,)), ((3,), (4,))) + matrix(1)(0).push(5) + test(matrix, (((1,), (2,)), ((3, 5), (4,)))) +} + +--- +// 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!") +} + +--- +// Error: 2:3-2:16 type array has no method `fun` +#let numbers = () +{ numbers.fun() } + +--- +// Error: 2:3-2:44 cannot mutate a temporary value +#let numbers = (1, 2, 3) +{ numbers.map(v => v / 2).sorted().map(str).remove(4) } + +--- +// Error: 2:3-2:19 cannot mutate a temporary value +#let numbers = (1, 2, 3) +{ numbers.sorted() = 1 } + +--- +// Error: 3-6 cannot mutate a constant +{ box.push(1) } diff --git a/tests/typ/compiler/module.typ b/tests/typ/compiler/module.typ new file mode 100644 index 00000000..b0a3fbf3 --- /dev/null +++ b/tests/typ/compiler/module.typ @@ -0,0 +1,12 @@ +// A file to import in import / include tests. +// Ref: false + +#let a +#let b = 1 +#let c = 2 +#let d = 3 +#let value = [hi] +#let item(a, b) = a + b +#let fn = rect.with(fill: conifer, inset: 5pt) + +Some _includable_ text. diff --git a/tests/typ/compiler/modules/chap1.typ b/tests/typ/compiler/modules/chap1.typ new file mode 100644 index 00000000..06a4c1a1 --- /dev/null +++ b/tests/typ/compiler/modules/chap1.typ @@ -0,0 +1,9 @@ +// Ref: false + +#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/typ/compiler/modules/chap2.typ b/tests/typ/compiler/modules/chap2.typ new file mode 100644 index 00000000..d4aedc60 --- /dev/null +++ b/tests/typ/compiler/modules/chap2.typ @@ -0,0 +1,11 @@ +// Ref: false + +#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/typ/compiler/modules/cycle1.typ b/tests/typ/compiler/modules/cycle1.typ new file mode 100644 index 00000000..a9c00f5e --- /dev/null +++ b/tests/typ/compiler/modules/cycle1.typ @@ -0,0 +1,6 @@ +// Ref: false + +#import * from "cycle2.typ" +#let inaccessible = "wow" + +This is the first element of an import cycle. diff --git a/tests/typ/compiler/modules/cycle2.typ b/tests/typ/compiler/modules/cycle2.typ new file mode 100644 index 00000000..204da519 --- /dev/null +++ b/tests/typ/compiler/modules/cycle2.typ @@ -0,0 +1,6 @@ +// Ref: false + +#import * from "cycle1.typ" +#let val = "much cycle" + +This is the second element of an import cycle. diff --git a/tests/typ/compiler/ops-assoc.typ b/tests/typ/compiler/ops-assoc.typ new file mode 100644 index 00000000..ec128c61 --- /dev/null +++ b/tests/typ/compiler/ops-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/typ/compiler/ops-invalid.typ b/tests/typ/compiler/ops-invalid.typ new file mode 100644 index 00000000..3e9e5478 --- /dev/null +++ b/tests/typ/compiler/ops-invalid.typ @@ -0,0 +1,107 @@ +// Test invalid operations. +// Ref: false + +--- +// Error: 3 expected expression +{-} + +--- +// Error: 10 expected expression +#test({1+}, 1) + +--- +// Error: 10 expected expression +#test({2*}, 2) + +--- +// Error: 2-12 cannot apply '+' to content +{+([] + [])} + +--- +// Error: 2-5 cannot apply '-' to string +{-""} + +--- +// Error: 2-8 cannot apply 'not' to array +{not ()} + +--- +// Error: 2-18 cannot apply '<=' to relative length and ratio +{30% + 1pt <= 40%} + +--- +// Error: 2-13 cannot apply '<=' to length and length +{1em <= 10pt} + +--- +// Special messages for +, -, * and /. +// Error: 03-10 cannot add integer and string +{(1 + "2", 40% - 1)} + +--- +// Error: 14-22 cannot add integer and string +{ let x = 1; x += "2" } + +--- +// Error: 3-12 cannot divide ratio by length +{ 10% / 5pt } + +--- +// Error: 3-12 cannot divide these two lengths +{ 1em / 5pt } + +--- +// Error: 3-19 cannot divide relative length by ratio +{ (10% + 1pt) / 5% } + +--- +// Error: 3-28 cannot divide these two relative lengths +{ (10% + 1pt) / (20% + 1pt) } + +--- +// Error: 12-19 cannot subtract integer from ratio +{(1234567, 40% - 1)} + +--- +// Error: 2-10 cannot multiply integer with boolean +{2 * true} + +--- +// Error: 2-10 cannot divide integer by length +{3 / 12pt} + +--- +// Error: 2-9 cannot repeat this string -1 times +{-1 * ""} + +--- +{ + let x = 2 + for _ in range(61) { + x *= 2 + } + // Error: 4-18 cannot repeat this string 4611686018427387904 times + {x * "abcdefgh"} +} + +--- +// Error: 3-6 cannot mutate a temporary value +{ (x) = "" } + +--- +// Error: 3-8 cannot mutate a temporary value +{ 1 + 2 += 3 } + +--- +// Error: 3-4 unknown variable +{ z = 1 } + +--- +// Error: 3-7 cannot mutate a constant +{ rect = "hi" } + +--- +// 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/typ/compiler/ops-prec.typ b/tests/typ/compiler/ops-prec.typ new file mode 100644 index 00000000..23afcc5f --- /dev/null +++ b/tests/typ/compiler/ops-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 mutate a temporary value +{not x = "a"} + +--- +// Parentheses override precedence. +#test((1), 1) +#test((1+2)*-3, -9) + +// Error: 14 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/typ/compiler/ops.typ b/tests/typ/compiler/ops.typ new file mode 100644 index 00000000..a38a527c --- /dev/null +++ b/tests/typ/compiler/ops.typ @@ -0,0 +1,230 @@ +// Test binary expressions. +// Ref: false + +--- +// Test adding content. +// Ref: true +{[*Hello* ] + [world!]} + +--- +// 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)) + +// 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) != "integer" { + test(v + v, 2.0 * v) + } + + if "relative" not in type(v) 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) + } +} + +--- +// 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) + +--- +// 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 by hash equality. +#let t = [a] +#test(t == t, true) +#test([] == [], true) +#test([a] == [a], true) +#test([[a]] == [a], true) +#test([] == [a], false) +#test(box[] == box[], true) +#test(box[a] == box[], false) + +--- +// 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 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") + +--- +// Error: 3-6 cannot mutate a constant +{ box = 1 } + +--- +// 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) + +--- +// Error: 9 expected keyword `in` +{"a" not} + +--- +// Test `with` method. + +// Apply positional arguments. +#let add(x, y) = x + y +#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) diff --git a/tests/typ/compiler/repr.typ b/tests/typ/compiler/repr.typ new file mode 100644 index 00000000..bdea9c82 --- /dev/null +++ b/tests/typ/compiler/repr.typ @@ -0,0 +1,48 @@ +// Test representation of values in the document. + +--- +// Literal values. +{auto} \ +{none} (empty) \ +{true} \ +{false} + +--- +// Numerical values. +{1} \ +{1.0e-4} \ +{3.15} \ +{1e-10} \ +{50.368%} \ +{0.0000012345pt} \ +{4.5cm} \ +{12e1pt} \ +{2.5rad} \ +{45deg} \ +{1.7em} \ +{1cm + 0em} \ +{2em + 10pt} \ +{2.3fr} + +--- +// Colors and strokes. +#set text(0.8em) +#rgb("f7a205") \ +{2pt + rgb("f7a205")} + +--- +// Strings and escaping. +#raw(repr("hi"), lang: "typc") \ +#repr("a\n[]\"\u{1F680}string") + +--- +// Content. +#raw(repr[*{"H" + "i"} there*]) + +--- +// Functions are invisible. +Nothing +#let f(x) = x +{f} +{rect} +{() => none} diff --git a/tests/typ/compiler/return.typ b/tests/typ/compiler/return.typ new file mode 100644 index 00000000..0eea394e --- /dev/null +++ b/tests/typ/compiler/return.typ @@ -0,0 +1,82 @@ +// Test return out of functions. +// Ref: false + +--- +// Test return with value. +#let f(x) = { + return x + 1 +} + +#test(f(1), 2) + +--- +// 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") + +--- +// Test return with joining and content. +// Ref: true + +#let f(text, caption: none) = { + text + if caption == none [\.#return] + [, ] + emph(caption) + [\.] +} + +#f(caption: [with caption])[My figure] + +#f[My other figure] + +--- +// Test return outside of function. + +#for x in range(5) { + // Error: 3-9 cannot return outside of function + return +} + +--- +// Test that the expression is evaluated to the end. +#let sum(..args) = { + let s = 0 + for v in args.positional() { + s += v + } + s +} + +#let f() = { + sum(..return, 1, 2, 3) + "nope" +} + +#test(f(), 6) + +--- +// Test value return from content. +#let x = 3 +#let f() = [ + Hello 😀 + #return "nope" + World +] + +#test(f(), "nope") diff --git a/tests/typ/compiler/set.typ b/tests/typ/compiler/set.typ new file mode 100644 index 00000000..fc5053b1 --- /dev/null +++ b/tests/typ/compiler/set.typ @@ -0,0 +1,66 @@ +// General tests for set. + +--- +// Test that text is affected by instantiation-site bold. +#let x = [World] +Hello *{x}* + +--- +// Test that lists are affected by correct indents. +#let fruit = [ + - Apple + - Orange + #list(body-indent: 20pt, [Pear]) +] + +- Fruit +[#set list(indent: 10pt) + #fruit] +- No more fruit + +--- +// Test that that block spacing and text style are respected from +// the outside, but the more specific fill is respected. +#set block(spacing: 4pt) +#set text(style: "italic", fill: eastern) +#let x = [And the forest #parbreak() lay silent!] +#text(fill: forest, x) + +--- +// Test that scoping works as expected. +{ + if true { + set text(blue) + [Blue ] + } + [Not blue] +} + +--- +// Test relative path resolving in layout phase. +#let choice = ("monkey.svg", "rhino.png", "tiger.jpg") +#set enum(label: n => { + let path = "../../res/" + choice(n - 1) + move(dy: -0.15em, image(path, width: 1em, height: 1em)) +}) + ++ Monkey ++ Rhino ++ Tiger + +--- +// Test conditional set. +#show ref: it => { + set text(red) if it.target == "unknown" + it +} + +@hello from the @unknown + +--- +// Error: 19-24 expected boolean, found integer +#set text(red) if 1 + 2 + +--- +// Error: 11-25 set is only allowed directly in code and content blocks +{ let x = set text(blue) } diff --git a/tests/typ/compiler/shorthand.typ b/tests/typ/compiler/shorthand.typ new file mode 100644 index 00000000..5c94dab0 --- /dev/null +++ b/tests/typ/compiler/shorthand.typ @@ -0,0 +1,20 @@ +// Test shorthands for unicode codepoints. + +--- +The non-breaking~space does work. + +--- +// Make sure non-breaking and normal space always +// have the same width. Even if the font decided +// differently. +#set text("Latin Modern Roman") +a b \ +a~b + +--- +- En dash: -- +- Em dash: --- + +--- +#set text("Roboto") +A... vs {"A..."} diff --git a/tests/typ/compiler/show-bare.typ b/tests/typ/compiler/show-bare.typ new file mode 100644 index 00000000..8b8d0852 --- /dev/null +++ b/tests/typ/compiler/show-bare.typ @@ -0,0 +1,33 @@ +// Test bare show without selector. + +--- +#set page(height: 130pt) +#set text(0.7em) + +#align(center)[ + #text(1.3em)[*Essay on typography*] \ + T. Ypst +] + +#show columns.with(2) +Great typography is at the essence of great storytelling. It is the medium that +transports meaning from parchment to reader, the wave that sparks a flame +in booklovers and the great fulfiller of human need. + +--- +// Test bare show in content block. +A [_B #show c => [*#c*]; C_] D + +--- +// Test style precedence. +#set text(fill: eastern, size: 1.5em) +#show text.with(fill: forest) +Forest + +--- +#show [Shown] +Ignored + +--- +// Error: 4-18 show is only allowed directly in code and content blocks +{ (show body => 2) * body } diff --git a/tests/typ/compiler/show-node.typ b/tests/typ/compiler/show-node.typ new file mode 100644 index 00000000..98f36f13 --- /dev/null +++ b/tests/typ/compiler/show-node.typ @@ -0,0 +1,104 @@ +// Test node show rules. + +--- +// Override lists. +#show list: it => "(" + it.items.join(", ") + ")" + +- A + - B + - C +- D +- E + +--- +// Test full reset. +#show heading: [B] +#show heading: set text(size: 10pt, weight: 400) +A [= Heading] C + +--- +// Test full removal. +#show heading: none + +Where is += There are no headings around here! +my heading? + +--- +// Test integrated example. +#show heading: it => block({ + set text(10pt) + move(dy: -1pt)[📖] + h(5pt) + if it.level == 1 { + underline(text(1.25em, blue, it.body)) + } else { + text(red, it.body) + } +}) + += Task 1 +Some text. + +== Subtask +Some more text. + += Task 2 +Another text. + +--- +// Test set and show in code blocks. +#show heading: it => { + set text(red) + show "ding": [🛎] + it.body +} + += Heading + +--- +// Test that scoping works as expected. +{ + let world = [ World ] + show "W": strong + world + { + set text(blue) + show it => { + show "o": "Ø" + it + } + world + } + world +} + +--- +#show heading: [1234] += Heading + +--- +// Error: 25-29 unknown field "page" +#show heading: it => it.page += Heading + +--- +// Error: 7-12 this function is not selectable +#show upper: it => {} + +--- +// Error: 7-11 to select text, please use a string or regex instead +#show text: it => {} + +--- +// Error: 16-20 expected content or function, found integer +#show heading: 1234 += Heading + +--- +// Error: 7-10 expected selector, found color +#show red: [] + +--- +// Error: 7-25 show is only allowed directly in code and content blocks +{ 1 + show heading: none } diff --git a/tests/typ/compiler/show-recursive.typ b/tests/typ/compiler/show-recursive.typ new file mode 100644 index 00000000..91a295f2 --- /dev/null +++ b/tests/typ/compiler/show-recursive.typ @@ -0,0 +1,51 @@ +// Test recursive show rules. + +--- +// Test basic identity. +#show heading: it => it += Heading + +--- +// Test more recipes down the chain. +#show list: scale.with(origin: left, x: 80%) +#show heading: [] +#show enum: [] +- Actual +- Tight +- List += Nope + +--- +// Test show rule in function. +#let starwars(body) = { + show list: it => block({ + stack(dir: ltr, + text(red, it), + 1fr, + scale(x: -100%, text(blue, it)), + ) + }) + body +} + +- Normal list + +#starwars[ + - Star + - Wars + - List +] + +- Normal list + +--- +// Test multi-recursion with nested lists. +#set rect(inset: 3pt) +#show list: rect.with(stroke: blue) +#show list: rect.with(stroke: red) +#show list: block + +- List + - Nested + - List +- Recursive! diff --git a/tests/typ/compiler/show-selector.typ b/tests/typ/compiler/show-selector.typ new file mode 100644 index 00000000..0e9823a5 --- /dev/null +++ b/tests/typ/compiler/show-selector.typ @@ -0,0 +1,36 @@ +// Test show rule patterns. + +--- +// Inline code. +#show raw.where(block: false): rect.with( + radius: 2pt, + outset: (y: 3pt), + inset: (x: 3pt), + fill: luma(230), +) + +// Code blocks. +#show raw.where(block: true): rect.with( + outset: -3pt, + inset: 11pt, + fill: luma(230), + stroke: (left: 1.5pt + luma(180)), +) + +#set page(margin: (top: 12pt)) +#set par(justify: true) + +This code tests `code` +with selectors and justification. + +```rs +code!("it"); +``` + +--- +#show heading.where(level: 1): set text(red) +#show heading.where(level: 2): set text(blue) +#show heading: set text(green) += Red +== Blue +=== Green diff --git a/tests/typ/compiler/show-text.typ b/tests/typ/compiler/show-text.typ new file mode 100644 index 00000000..124d2ed2 --- /dev/null +++ b/tests/typ/compiler/show-text.typ @@ -0,0 +1,59 @@ +// Test text replacement show rules. + +--- +// Test classic example. +#set text("Roboto") +#show "Der Spiegel": smallcaps +Die Zeitung Der Spiegel existiert. + +--- +// Another classic example. +#show "TeX": [T#h(-0.145em)#move(dy: 0.233em)[E]#h(-0.135em)X] +#show regex("(Lua)?(La)?TeX"): name => box(text("Latin Modern Roman")[#name]) + +TeX, LaTeX, LuaTeX and LuaLaTeX! + +--- +// Test that replacements happen exactly once. +#show "A": [BB] +#show "B": [CC] +AA (8) + +--- +// Test caseless match and word boundaries. +#show regex("(?i)\bworld\b"): [🌍] + +Treeworld, the World of worlds, is a world. + +--- +// This is a fun one. +#set par(justify: true) +#show regex("\S"): letter => rect(inset: 2pt)[#upper(letter)] +#lorem(5) + +--- +// See also: https://github.com/mTvare6/hello-world.rs +#show regex("(?i)rust"): it => [#it (🚀)] +Rust is memory-safe and blazingly fast. Let's rewrite everything in rust. + +--- +// Test accessing the string itself. +#show "hello": it => it.text.split("").map(upper).join("|") +Oh, hello there! + +--- +// Replace worlds but only in lists. +#show list: it => [ + #show "World": [🌎] + #it +] + +World +- World + +--- +// Test absolute path in layout phase. + +#show "GRAPH": image("/res/graph.png") + +The GRAPH has nodes. diff --git a/tests/typ/compiler/spread.typ b/tests/typ/compiler/spread.typ new file mode 100644 index 00000000..ff661ead --- /dev/null +++ b/tests/typ/compiler/spread.typ @@ -0,0 +1,93 @@ +// Test argument sinks and spreading. +// Ref: false + +--- +// 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)") +} + +--- +// Test multiple calls. +{ + let f(b, c: "!") = b + c + let g(a, ..sink) = a + f(..sink) + test(g("a", "b", c: "c"), "abc") +} + +--- +// Test doing things with arguments. +{ + let save(..args) = { + test(type(args), "arguments") + test(repr(args), "(1, 2, three: true)") + } + + save(1, 2, three: true) +} + +--- +// Test spreading array and dictionary. +{ + let more = (3, -3, 6, 10) + test(min(1, 2, ..more), -3) + test(max(..more, 9), 10) + test(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)") +} + +--- +// None is spreadable. +#let f() = none +#f(..none) +#f(..if false {}) +#f(..for x in () []) + +--- +// Error: 8-14 cannot spread string +#min(.."nope") + +--- +// Error: 8-14 expected identifier, named pair or argument sink, found spread +#let f(..true) = none + +--- +// Error: 15-16 only one argument sink is allowed +#let f(..a, ..b) = none + +--- +// Test spreading into array and dictionary. +{ + let l = (1, 2, 3) + let r = (5, 6, 7) + test((..l, 4, ..r), range(1, 8)) + test((..none), ()) +} + +{ + let x = (a: 1) + let y = (b: 2) + let z = (a: 3) + test((:..x, ..y, ..z), (a: 3, b: 2)) + test((..(a: 1), b: 2), (a: 1, b: 2)) +} + +--- +// Error: 11-17 cannot spread dictionary into array +{(1, 2, ..(a: 1))} + +--- +// Error: 5-11 cannot spread array into dictionary +{(..(1, 2), a: 1)} diff --git a/tests/typ/compiler/while.typ b/tests/typ/compiler/while.typ new file mode 100644 index 00000000..5dc5ae41 --- /dev/null +++ b/tests/typ/compiler/while.typ @@ -0,0 +1,58 @@ +// Test while expressions. + +--- +// 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 +} + +--- +// Value of while loops. +// Ref: false + +#test(while false {}, none) + +#let i = 0 +#test(type(while i < 1 [{ i += 1 }]), "content") + +--- +// Condition must be boolean. +// Error: 8-14 expected boolean, found content +#while [nope] [nope] + +--- +// Make sure that we terminate and don't complain multiple times. +#while true { + // Error: 3-7 unknown variable + nope +} + +--- +// Error: 7 expected expression +#while + +// Error: 7 expected expression +{while} + +// Error: 9 expected body +#while x + +// Error: 7 expected expression +#while +x {} + +// Error: 9 expected body +#while x something |
