summaryrefslogtreecommitdiff
path: root/tests/typ/code
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-05-18 00:36:11 +0200
committerLaurenz <laurmaedje@gmail.com>2021-05-18 00:36:11 +0200
commit8b58171d7ca036d71b32749286c251cc91bdd10e (patch)
tree4594ab5088edf8eec44f3bafe3fb8fecb13ac61b /tests/typ/code
parent8d67c0ca5eb3486dde97fd281bd4a51d535c600c (diff)
Reorganize test cases
Diffstat (limited to 'tests/typ/code')
-rw-r--r--tests/typ/code/array.typ43
-rw-r--r--tests/typ/code/assoc.typ18
-rw-r--r--tests/typ/code/block-invalid.typ37
-rw-r--r--tests/typ/code/block-scoping.typ35
-rw-r--r--tests/typ/code/block.typ38
-rw-r--r--tests/typ/code/call-invalid.typ44
-rw-r--r--tests/typ/code/call.typ70
-rw-r--r--tests/typ/code/closure.typ64
-rw-r--r--tests/typ/code/comment.typ23
-rw-r--r--tests/typ/code/dict.typ20
-rw-r--r--tests/typ/code/for-pattern.typ35
-rw-r--r--tests/typ/code/for.typ71
-rw-r--r--tests/typ/code/if.typ71
-rw-r--r--tests/typ/code/invalid.typ100
-rw-r--r--tests/typ/code/let.typ63
-rw-r--r--tests/typ/code/ops-invalid.typ59
-rw-r--r--tests/typ/code/ops.typ147
-rw-r--r--tests/typ/code/prec.typ30
-rw-r--r--tests/typ/code/repr.typ57
-rw-r--r--tests/typ/code/while.typ43
20 files changed, 1068 insertions, 0 deletions
diff --git a/tests/typ/code/array.typ b/tests/typ/code/array.typ
new file mode 100644
index 00000000..c9383501
--- /dev/null
+++ b/tests/typ/code/array.typ
@@ -0,0 +1,43 @@
+// Test arrays.
+
+---
+// 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"
+ , #002
+ ,)}
+
+// Error: 3 expected closing paren
+{(}
+
+// Error: 2-3 expected expression, found closing paren
+{)}
+
+// Error: 2:4 expected comma
+// Error: 1:4-1:6 expected expression, found end of block comment
+{(1*/2)}
+
+// Error: 6-8 expected expression, found invalid token
+{(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)}
diff --git a/tests/typ/code/assoc.typ b/tests/typ/code/assoc.typ
new file mode 100644
index 00000000..19c56951
--- /dev/null
+++ b/tests/typ/code/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/code/block-invalid.typ b/tests/typ/code/block-invalid.typ
new file mode 100644
index 00000000..d98bf06b
--- /dev/null
+++ b/tests/typ/code/block-invalid.typ
@@ -0,0 +1,37 @@
+// Test invalid code block syntax.
+
+---
+// Multiple unseparated expressions in one line.
+
+// Error: 2-4 expected expression, found invalid token
+{1u}
+
+// Should output `1`.
+// Error: 3 expected semicolon or line break
+{0 1}
+
+// Should output `2`.
+// Error: 2:12 expected semicolon or line break
+// Error: 1:22 expected semicolon or line break
+{let x = -1 let y = 3 x + y}
+
+// Should output `3`.
+{
+ // Error: 9-12 expected identifier, found string
+ for "v"
+
+ // Error: 10 expected keyword `in`
+ for v let z = 1 + 2
+
+ z
+}
+
+---
+// Ref: false
+// Error: 3:1 expected closing brace
+{
+
+---
+// Ref: false
+// Error: 1-2 unexpected closing brace
+}
diff --git a/tests/typ/code/block-scoping.typ b/tests/typ/code/block-scoping.typ
new file mode 100644
index 00000000..7bb98969
--- /dev/null
+++ b/tests/typ/code/block-scoping.typ
@@ -0,0 +1,35 @@
+// Test scoping with blocks.
+// Ref: false
+
+---
+// Block in template does not create a scope.
+{ let x = 1 }
+#test(x, 1)
+
+---
+// Block in expression does create a scope.
+#let a = {
+ let b = 1
+ b
+}
+
+#test(a, 1)
+
+// Error: 2-3 unknown variable
+{b}
+
+---
+// Multiple nested scopes.
+{
+ let a = "a1"
+ {
+ let a = "a2"
+ {
+ test(a, "a2")
+ let a = "a3"
+ test(a, "a3")
+ }
+ test(a, "a2")
+ }
+ test(a, "a1")
+}
diff --git a/tests/typ/code/block.typ b/tests/typ/code/block.typ
new file mode 100644
index 00000000..196e6c14
--- /dev/null
+++ b/tests/typ/code/block.typ
@@ -0,0 +1,38 @@
+// Test code blocks.
+
+---
+All none
+
+// Nothing evaluates to none.
+{}
+
+// Let evaluates to none.
+{ let v = 0 }
+
+// Trailing none evaluates to none.
+{
+ type("")
+ none
+}
+
+---
+// Evaluates to single expression.
+{ "Hello" }
+
+// Evaluates to trailing expression.
+{ let x = "Hel"; x + "lo" }
+
+// Evaluates to concatenation of for loop bodies.
+{
+ let parts = ("Hel", "lo")
+ for s in parts [{s}]
+}
+
+---
+// Works the same way in code environment.
+// Ref: false
+#test(3, {
+ let x = 1
+ let y = 2
+ x + y
+})
diff --git a/tests/typ/code/call-invalid.typ b/tests/typ/code/call-invalid.typ
new file mode 100644
index 00000000..0ed5246f
--- /dev/null
+++ b/tests/typ/code/call-invalid.typ
@@ -0,0 +1,44 @@
+// Test invalid function calls.
+
+---
+// Error: 1-2 unexpected invalid token
+#
+
+---
+// Error: 7-8 expected expression, found colon
+#args(:)
+
+// Error: 10-12 expected expression, found end of block comment
+#args(a:1*/)
+
+// Error: 8 expected comma
+#args(1 2)
+
+// Error: 2:7-2:8 expected identifier
+// Error: 1:9 expected expression
+#args(1:)
+
+// Error: 7-8 expected identifier
+#args(1:2)
+
+// Error: 7-10 expected identifier
+{args((x):1)}
+
+---
+#let x = "string"
+
+// Error: 1-3 expected function, found string
+#x()
+
+---
+// Error: 3:1 expected closing bracket
+#args[`a]`
+
+---
+// Error: 7 expected closing paren
+{args(}
+
+---
+// Error: 3:1 expected quote
+// Error: 2:1 expected closing paren
+#args("]
diff --git a/tests/typ/code/call.typ b/tests/typ/code/call.typ
new file mode 100644
index 00000000..dcf11806
--- /dev/null
+++ b/tests/typ/code/call.typ
@@ -0,0 +1,70 @@
+// Test function calls.
+
+---
+// One argument.
+#args(bold)
+
+// One argument and trailing comma.
+#args(1,)
+
+// One named argument.
+#args(a:2)
+
+// Mixed arguments.
+{args(1, b: "2", 3)}
+
+// Should output `() + 2`.
+#args() + 2
+
+---
+// Ref: false
+
+// Call function assigned to variable.
+#let alias = type
+#test(alias(alias), "function")
+
+// Library function `font` returns template.
+#test(type(font(12pt)), "template")
+
+---
+// Callee expressions.
+{
+ // Error: 5-9 expected function, found boolean
+ true()
+
+ // 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)
+}
+
+#let f(x, body) = (y) => {
+ [{x}] + body + [{y}]
+}
+
+// Call return value of function with body.
+#f(1)[2](3)
+
+// Don't allow this to be a closure.
+// Should output `x => "hi"`.
+#let x = "x"
+#x => "hi"
+
+---
+// Different forms of template arguments.
+
+#let a = "a"
+
+#args[a] \
+#args(a) \
+#args(a, [b]) \
+#args(a)[b]
+
+// Template can be argument or body depending on whitespace.
+#if "template" == type[b] [Sure ]
+#if "template" == type [Nope.] #else [thing.]
+
+// Should output `<function args> (Okay.)`.
+#args (Okay.)
diff --git a/tests/typ/code/closure.typ b/tests/typ/code/closure.typ
new file mode 100644
index 00000000..86a6f632
--- /dev/null
+++ b/tests/typ/code/closure.typ
@@ -0,0 +1,64 @@
+// Test closures.
+// Ref: false
+
+---
+
+// 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?")
+
+ mark = "!"
+ test(greet("Typst"), "Hi, Typst!")
+}
+
+// Don't leak environment.
+{
+ // Error: 18-19 unknown variable
+ let func() = x
+ let x = "hi"
+
+ test(func(), error)
+}
+
+---
+// Too few arguments.
+{
+ let types(x, y) = "[" + type(x) + ", " + type(y) + "]"
+ test(types(14%, 12pt), "[relative, length]")
+
+ // Error: 16-22 missing argument: y
+ test(types("nope"), "[string, none]")
+}
+
+// Too many arguments.
+{
+ let f(x) = x + 1
+
+ // Error: 2:10-2:15 unexpected argument
+ // Error: 1:17-1:24 unexpected argument
+ f(1, "two", () => x)
+}
diff --git a/tests/typ/code/comment.typ b/tests/typ/code/comment.typ
new file mode 100644
index 00000000..25180211
--- /dev/null
+++ b/tests/typ/code/comment.typ
@@ -0,0 +1,23 @@
+// Test line and block comments.
+
+---
+// Line comment acts as spacing.
+A// you
+B
+
+// Block comment does not act as spacing.
+C/*
+ /* */
+*/D
+
+// Works in code.
+#test(type(/*1*/ 1) //
+, "integer")
+
+---
+// End should not appear without start.
+// Error: 1:7-1:9 unexpected end of block comment
+/* */ */
+
+// Unterminated is okay.
+/*
diff --git a/tests/typ/code/dict.typ b/tests/typ/code/dict.typ
new file mode 100644
index 00000000..655a3299
--- /dev/null
+++ b/tests/typ/code/dict.typ
@@ -0,0 +1,20 @@
+// Test dictionaries.
+
+---
+// Empty
+{(:)}
+
+// Two pairs.
+{(a1: 1, a2: 2)}
+
+---
+// Simple expression after already being identified as a dictionary.
+// Error: 9-10 expected named pair, found expression
+{(a: 1, b)}
+
+// Identified as dictionary due to initial colon.
+// Error: 4:4-4:5 expected named pair, found expression
+// Error: 3:5 expected comma
+// Error: 2:12-2:16 expected identifier
+// Error: 1:17-1:18 expected expression, found colon
+{(:1 b:[], true::)}
diff --git a/tests/typ/code/for-pattern.typ b/tests/typ/code/for-pattern.typ
new file mode 100644
index 00000000..a6a7c16a
--- /dev/null
+++ b/tests/typ/code/for-pattern.typ
@@ -0,0 +1,35 @@
+// Test for loop patterns.
+// Ref: false
+
+---
+#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))
+
+---
+// Keys and values of strings.
+// Error: 6-10 mismatched pattern
+#for k, v in "hi" {
+ dont-care
+}
diff --git a/tests/typ/code/for.typ b/tests/typ/code/for.typ
new file mode 100644
index 00000000..bca1af46
--- /dev/null
+++ b/tests/typ/code/for.typ
@@ -0,0 +1,71 @@
+// Test for loops.
+
+---
+// Empty array.
+#for x in () [Nope]
+
+// Array.
+#let sum = 0
+#for x in (1, 2, 3, 4, 5) {
+ sum += x
+}
+
+#test(sum, 15)
+
+// Dictionary is not traversed in insertion order.
+// Should output `age: 1, name: Typst,`.
+#for k, v in (Name: "Typst", Age: 2) [
+ {k}: {v}.
+]
+
+// String.
+{
+ let out = ""
+ let first = true
+ for c in "abc" {
+ if not first {
+ out += ", "
+ }
+ first = false
+ out += c
+ }
+ test(out, "a, b, c")
+}
+
+---
+// Block body.
+// Should output `[1st, 2nd, 3rd, 4th, 5th, 6th]`.
+{
+ "[" + for v in (1, 2, 3, 4, 5, 6) {
+ (if v > 1 [, ]
+ + [{v}]
+ + if v == 1 [st]
+ + if v == 2 [nd]
+ + if v == 3 [rd]
+ + if v >= 4 [th])
+ } + "]"
+}
+
+// Template body.
+// Should output `234`.
+#for v in (1, 2, 3, 4, 5, 6, 7) [#if v >= 2 and v <= 5 { repr(v) }]
+
+---
+// Value of for loops.
+// Ref: false
+#test(type(for v in () {}), "template")
+#test(type(for v in () []), "template")
+
+---
+// Uniterable expression.
+// Error: 11-15 cannot loop over boolean
+#for v in true {}
+
+// Make sure that we don't complain twice.
+// Error: 11-18 cannot add integer and string
+#for v in 1 + "2" {}
+
+// A single error stops iteration.
+#test(error, for v in (1, 2, 3) {
+ if v < 2 [Ok] else {error}
+})
diff --git a/tests/typ/code/if.typ b/tests/typ/code/if.typ
new file mode 100644
index 00000000..8d07e9b8
--- /dev/null
+++ b/tests/typ/code/if.typ
@@ -0,0 +1,71 @@
+// Test if-else expressions.
+
+---
+// Test condition evaluation.
+#if 1 < 2 [
+ One.
+]
+
+#if true == false [
+ {Bad}, but we {dont-care}!
+]
+
+---
+// Braced condition.
+#if {true} [
+ One.
+]
+
+// Template 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
+}
+
+---
+// 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" {}
diff --git a/tests/typ/code/invalid.typ b/tests/typ/code/invalid.typ
new file mode 100644
index 00000000..49158a68
--- /dev/null
+++ b/tests/typ/code/invalid.typ
@@ -0,0 +1,100 @@
+// Test invalid control syntax.
+
+---
+// Error: 5 expected identifier
+#let
+
+// Error: 5 expected identifier
+{let}
+
+// Error: 6-9 expected identifier, found string
+#let "v"
+
+// Should output `1`.
+// Error: 7 expected semicolon or line break
+#let v 1
+
+// Error: 9 expected expression
+#let v =
+
+// Should output `= 1`.
+// Error: 6-9 expected identifier, found string
+#let "v" = 1
+
+---
+// 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: 20 expected body
+A#if false {} #else thing
+
+---
+// Error: 7 expected expression
+#while
+
+// Error: 7 expected expression
+{while}
+
+// Error: 9 expected body
+#while x
+
+// Should output `x`.
+// Error: 7 expected expression
+#while
+x {}
+
+// Should output `something`.
+// Error: 9 expected body
+#while x something
+
+---
+// Error: 5 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
+
+// Should output `v in iter`.
+// Error: 5 expected identifier
+#for
+v in iter {}
+
+// Should output `A thing`.
+// Error: 7-10 expected identifier, found string
+A#for "v" thing
+
+// Should output `in iter`.
+// Error: 6-9 expected identifier, found string
+#for "v" in iter {}
+
+// Should output `+ b in iter`.
+// Error: 7 expected keyword `in`
+#for a + b in iter {}
diff --git a/tests/typ/code/let.typ b/tests/typ/code/let.typ
new file mode 100644
index 00000000..4f84aa67
--- /dev/null
+++ b/tests/typ/code/let.typ
@@ -0,0 +1,63 @@
+// Test let bindings.
+
+---
+// Ref: false
+
+// Automatically initialized with none.
+#let x
+#test(x, none)
+
+// Error: 9 expected expression
+#let y =
+#test(y, none)
+
+// Manually initialized with one.
+#let z = 1
+#test(z, 1)
+
+---
+// Syntax sugar for function definitions.
+#let background = #9feb52
+#let rect(body) = rect(width: 2cm, fill: background, pad(5pt, body))
+#rect[Hi!]
+
+// Error: 13 expected body
+#let func(x)
+
+// Error: 2-6 unknown variable
+{func}
+
+// Error: 15 expected expression
+#let func(x) =
+
+// Error: 2-6 unknown variable
+{func}
+
+---
+// 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
+
+// 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: 2:19 expected expression
+// Error: 1:19 expected closing paren
+#let v5 = (1, 2 + ; Five
+
+#test(v1, 1)
+#test(v2, 2)
+#test(v3, 3)
+#test(v4, 4)
+#test(v5, (1, 2))
diff --git a/tests/typ/code/ops-invalid.typ b/tests/typ/code/ops-invalid.typ
new file mode 100644
index 00000000..12d2a2c3
--- /dev/null
+++ b/tests/typ/code/ops-invalid.typ
@@ -0,0 +1,59 @@
+// Test invalid expressions.
+// Ref: false
+
+---
+// Missing expressions.
+
+// Error: 3 expected expression
+{-}
+
+// Error: 10 expected expression
+#test({1+}, 1)
+
+// Error: 10 expected expression
+#test({2*}, 2)
+
+---
+// Mismatched types.
+
+// Error: 2-12 cannot apply '+' to template
+{+([] + [])}
+
+// Error: 2-5 cannot apply '-' to string
+{-""}
+
+// Error: 2-8 cannot apply 'not' to array
+{not ()}
+
+// Error: 1:2-1:12 cannot apply '<=' to relative and relative
+{30% <= 40%}
+
+// Special messages for +, -, * and /.
+// Error: 4:03-4:10 cannot add integer and string
+// Error: 3:12-3:19 cannot subtract integer from relative
+// Error: 2:21-2:29 cannot multiply integer with boolean
+// Error: 1:31-1:39 cannot divide integer by length
+{(1 + "2", 40% - 1, 2 * true, 3 / 12pt)}
+
+// Error: 14-22 cannot apply '+=' to integer and string
+{ let x = 1; x += "2" }
+
+---
+// Bad left-hand sides of assignment.
+
+// Error: 3-6 cannot assign to this expression
+{ (x) = "" }
+
+// Error: 3-8 cannot assign to this expression
+{ 1 + 2 += 3 }
+
+// Error: 3-4 unknown variable
+{ z = 1 }
+
+// Error: 3-7 cannot assign to 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/code/ops.typ b/tests/typ/code/ops.typ
new file mode 100644
index 00000000..ef249c43
--- /dev/null
+++ b/tests/typ/code/ops.typ
@@ -0,0 +1,147 @@
+// Test binary expressions.
+// Ref: false
+
+---
+// Test template addition.
+// Ref: true
+{[*Hello ] + [world!*]}
+
+---
+// Test math operators.
+
+// Test plus and minus.
+#for v in (1, 3.14, 12pt, 45deg, 90%, 13% + 10pt) {
+ // Test plus.
+ test(+v, v)
+
+ // Test minus.
+ test(-v, -1 * v)
+ test(--v, v)
+
+ // Test combination.
+ test(-++ --v, -v)
+}
+
+#test(-(4 + 2), 6-12)
+
+// Addition.
+#test(2 + 4, 6)
+#test("a" + "b", "ab")
+#test((1, 2) + (3, 4), (1, 2, 3, 4))
+#test((a: 1) + (b: 2, c: 3), (a: 1, b: 2, c: 3))
+
+// Subtraction.
+#test(1-4, 3*-1)
+#test(4cm - 2cm, 2cm)
+#test(1e+2-1e-2, 99.99)
+
+// Multiplication.
+#test(2 * 4, 8)
+
+// Division.
+#test(12pt/.4, 30pt)
+#test(7 / 2, 3.5)
+
+// Combination.
+#test(3-4 * 5 < -10, true)
+#test({ let x; x = 1 + 4*5 >= 21 and { x = "a"; x + "b" == "ab" }; x }, true)
+
+// Mathematical identities.
+#let nums = (1, 3.14, 12pt, 45deg, 90%, 13% + 10pt)
+#for v in nums {
+ // Test plus and minus.
+ test(v + v - v, v)
+ test(v - v - v, -v)
+
+ // Test plus/minus and multiplication.
+ test(v - v, 0 * v)
+ test(v + v, 2 * v)
+
+ // Integer addition does not give a float.
+ if type(v) != "integer" {
+ test(v + v, 2.0 * v)
+ }
+
+ // Linears cannot be divided by themselves.
+ if type(v) != "linear" {
+ test(v / v, 1.0)
+ test(v / v == 1, true)
+ }
+}
+
+// Make sure length, relative and linear
+// - can all be added to / subtracted from each other,
+// - multiplied with integers and floats,
+// - divided by floats.
+#let dims = (10pt, 30%, 50% + 3cm)
+#for a in dims {
+ for b in dims {
+ test(type(a + b), type(a - b))
+ }
+
+ for b in (7, 3.14) {
+ test(type(a * b), type(a))
+ test(type(b * a), type(a))
+ test(type(a / b), type(a))
+ }
+}
+
+---
+// Test boolean operators.
+
+// 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.
+
+#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)
+
+---
+// Test comparison operators.
+
+#test(13 * 3 < 14 * 4, true)
+#test(5 < 10, true)
+#test(5 > 5, false)
+#test(5 <= 5, true)
+#test(5 <= 4, false)
+#test(45deg < 1rad, true)
+
+---
+// Test assignment operators.
+
+#let x = 0
+{ x = 10 } #test(x, 10)
+{ x -= 5 } #test(x, 5)
+{ x += 1 } #test(x, 6)
+{ x *= x } #test(x, 36)
+{ x /= 2.0 } #test(x, 18.0)
+{ x = "some" } #test(x, "some")
+{ x += "thing" } #test(x, "something")
diff --git a/tests/typ/code/prec.typ b/tests/typ/code/prec.typ
new file mode 100644
index 00000000..e64e583c
--- /dev/null
+++ b/tests/typ/code/prec.typ
@@ -0,0 +1,30 @@
+// Test operator precedence.
+// Ref: false
+
+---
+// Multiplication binds stronger than addition.
+#test(1+2*-3, -5)
+
+// Subtraction binds stronger than comparison.
+#test(3 == 5 - 2, true)
+
+// Boolean operations bind stronger than '=='.
+#test("a" == "a" and 2 < 3, true)
+#test(not "b" == "b", false)
+
+// Assignment binds stronger than boolean operations.
+// Error: 2-7 cannot assign to this expression
+{not x = "a"}
+
+---
+// Parentheses override precedence.
+#test((1), 1)
+#test((1+2)*-3, -9)
+
+// Error: 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/code/repr.typ b/tests/typ/code/repr.typ
new file mode 100644
index 00000000..f2404510
--- /dev/null
+++ b/tests/typ/code/repr.typ
@@ -0,0 +1,57 @@
+// Test representation of values in the document.
+
+---
+// Variables.
+
+#let name = "Typst"
+#let ke-bab = "Kebab!"
+#let α = "Alpha"
+
+{name} \
+{ke-bab} \
+{α}
+
+// Error: 2-3 unknown variable
+{_}
+
+---
+// Literal values.
+{none} (empty) \
+{true} \
+{false}
+
+---
+// Numerical values.
+{1} \
+{1.0e-4} \
+{3.15} \
+{1e-10} \
+{50.368%} \
+{0.0000012345pt} \
+{4.5cm} \
+{12e1pt} \
+{2.5rad} \
+{45deg} \
+// Not in monospace via repr.
+#repr(45deg)
+
+---
+// Colors.
+{#f7a20500}
+
+---
+// Strings and escaping.
+{"hi"} \
+{"a\n[]\"\u{1F680}string"}
+
+---
+// Templates.
+{[*{"H" + "i"} there*]}
+
+---
+// Functions
+#let f(x) = x
+
+{rect} \
+{f} \
+{() => none}
diff --git a/tests/typ/code/while.typ b/tests/typ/code/while.typ
new file mode 100644
index 00000000..acf7951e
--- /dev/null
+++ b/tests/typ/code/while.typ
@@ -0,0 +1,43 @@
+// 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(type(while false {}), "template")
+#test(type(while false []), "template")
+
+---
+// Condition must be boolean.
+// Error: 8-14 expected boolean, found template
+#while [nope] [nope]
+
+// Make sure that we don't complain twice.
+// Error: 8-15 unknown variable
+#while nothing {}
+
+// A single error stops iteration.
+#let i = 0
+#test(error, while i < 10 {
+ i += 1
+ if i < 5 [nope] else { error }
+})
+#test(i, 5)