@effekt-lang/effekt 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +6 -18
  2. package/bin/effekt +0 -0
  3. package/libraries/common/args.effekt +0 -27
  4. package/libraries/common/array.effekt +34 -63
  5. package/libraries/common/bench.effekt +40 -22
  6. package/libraries/common/buffer.effekt +8 -20
  7. package/libraries/common/bytes.effekt +5 -15
  8. package/libraries/common/dequeue.effekt +5 -5
  9. package/libraries/common/effekt.effekt +51 -92
  10. package/libraries/common/exception.effekt +0 -1
  11. package/libraries/common/io/console.effekt +30 -36
  12. package/libraries/common/io/error.effekt +131 -30
  13. package/libraries/common/io/filesystem.effekt +262 -0
  14. package/libraries/common/io/network.effekt +30 -54
  15. package/libraries/common/io/time.effekt +9 -12
  16. package/libraries/common/io.effekt +54 -124
  17. package/libraries/common/list.effekt +345 -327
  18. package/libraries/common/process.effekt +0 -1
  19. package/libraries/common/queue.effekt +1 -3
  20. package/libraries/common/ref.effekt +11 -24
  21. package/libraries/common/result.effekt +6 -14
  22. package/libraries/common/seq.effekt +28 -49
  23. package/libraries/common/string/tty.effekt +66 -0
  24. package/libraries/common/string.effekt +7 -49
  25. package/libraries/common/test.effekt +96 -34
  26. package/libraries/js/unsafe/cont.effekt +1 -1
  27. package/libraries/llvm/io.c +258 -378
  28. package/libraries/llvm/main.c +3 -0
  29. package/libraries/llvm/rts.ll +57 -38
  30. package/libraries/llvm/types.c +10 -1
  31. package/licenses/eclipse public license, version 2.0 - epl-2.0.html +4 -4
  32. package/licenses/mit license - mit-license.html +21 -20
  33. package/licenses/the bsd license - bsd-license.html +20 -21
  34. package/licenses/the mit license - mit.html +22 -21
  35. package/package.json +1 -1
  36. package/libraries/chez/lift/effekt.ss +0 -118
  37. package/libraries/common/io/files.effekt +0 -282
  38. package/libraries/js/io.js +0 -143
  39. package/libraries/llvm/io.ll +0 -19
  40. package/libraries/ml/effekt.sml +0 -65
  41. package/libraries/ml/internal/mllist.effekt +0 -9
  42. package/libraries/ml/internal/mloption.effekt +0 -18
  43. package/libraries/ml/random.effekt +0 -85
  44. package/libraries/ml/text/regex.effekt +0 -102
@@ -7,4 +7,3 @@ extern io def exit(errorCode: Int): Nothing =
7
7
  ret %Pos zeroinitializer
8
8
  """
9
9
  chez "(exit ${errorCode})"
10
- ml "(Posix.Process.exit (Word8.fromInt ${errorCode}))"
@@ -2,9 +2,7 @@ module queue
2
2
 
3
3
  import array
4
4
 
5
- /**
6
- * Mutable, automatically resizing queue.
7
- */
5
+ /// Mutable, automatically resizing queue.
8
6
  interface Queue[T] {
9
7
  def empty?(): Bool
10
8
 
@@ -3,21 +3,17 @@ module ref
3
3
  import effekt
4
4
 
5
5
  extern js """
6
- function set$impl(ref, value) {
7
- ref.value = value;
8
- return $effekt.unit;
9
- }
6
+ function set$impl(ref, value) {
7
+ ref.value = value;
8
+ return $effekt.unit;
9
+ }
10
10
  """
11
11
 
12
- /**
13
- * Global, mutable references
14
- */
12
+ /// Global, mutable references
15
13
  extern type Ref[T]
16
14
 
17
- /**
18
- * Allocates a new reference of size `size`, keeping its value _undefined_.
19
- * Prefer using `ref` constructor instead to ensure that the value is defined.
20
- */
15
+ /// Allocates a new reference of size `size`, keeping its value _undefined_.
16
+ /// Prefer using `ref` constructor instead to ensure that the value is defined.
21
17
  extern global def allocate[T](): Ref[T] =
22
18
  js "{ value: undefined }"
23
19
  chez "(box #f)"
@@ -26,38 +22,29 @@ extern global def allocate[T](): Ref[T] =
26
22
  ret %Pos %z
27
23
  """
28
24
 
29
- /**
30
- * Creates a new reference with the initial value `init`.
31
- */
25
+ /// Creates a new reference with the initial value `init`.
32
26
  extern global def ref[T](init: T): Ref[T] =
33
27
  js "{ value: ${init} }"
34
28
  chez "(box ${init})"
35
- ml "ref ${init}"
36
29
  llvm """
37
30
  %z = call %Pos @c_ref_fresh(%Pos ${init})
38
31
  ret %Pos %z
39
32
  """
40
33
 
41
- /**
42
- * Gets the referenced element of the `ref` in constant time.
43
- */
34
+ /// Gets the referenced element of the `ref` in constant time.
44
35
  extern global def get[T](ref: Ref[T]): T =
45
36
  js "${ref}.value"
46
37
  chez "(unbox ${ref})"
47
- ml "!${ref}"
48
38
  llvm """
49
39
  %z = call %Pos @c_ref_get(%Pos ${ref})
50
40
  ret %Pos %z
51
41
  """
52
42
 
53
- /**
54
- * Sets the referenced element of the `ref` to `value` in constant time.
55
- */
43
+ /// Sets the referenced element of the `ref` to `value` in constant time.
56
44
  extern global def set[T](ref: Ref[T], value: T): Unit =
57
45
  js "set$impl(${ref}, ${value})"
58
46
  chez "(set-box! ${ref} ${value})"
59
- ml "${ref} := ${value}"
60
47
  llvm """
61
48
  %z = call %Pos @c_ref_set(%Pos ${ref}, %Pos ${value})
62
49
  ret %Pos %z
63
- """
50
+ """
@@ -4,18 +4,14 @@ import effekt
4
4
  import option
5
5
  import exception
6
6
 
7
- /**
8
- * Represents the result of a potentially failing computation.
9
- * It is the "reification" of the Exception[E] effect.
10
- */
7
+ /// Represents the result of a potentially failing computation.
8
+ /// It is the "reification" of the Exception[E] effect.
11
9
  type Result[A, E] {
12
10
  Error(exception: E, msg: String);
13
11
  Success(a: A)
14
12
  }
15
13
 
16
- /**
17
- * Represent ("reify") the result of a potentially failing computation.
18
- */
14
+ /// Represent ("reify") the result of a potentially failing computation.
19
15
  def result[A, E] { f: => A / Exception[E] }: Result[A, E] = try {
20
16
  Success(f())
21
17
  } with Exception[E] {
@@ -27,18 +23,14 @@ def result[A, E](proxy: on[E]) { f: => A / Exception[E] }: Result[A, E] = try {
27
23
  def raise(exc, msg) = Error(exc, msg)
28
24
  }
29
25
 
30
- /**
31
- * Extracts the value of a result, if available
32
- * Monadic "reflection"
33
- */
26
+ /// Extracts the value of a result, if available
27
+ /// Monadic "reflection"
34
28
  def value[A, E](r: Result[A, E]): A / Exception[E] = r match {
35
29
  case Success(a) => a
36
30
  case Error(exc, msg) => do raise(exc, msg)
37
31
  }
38
32
 
39
- /**
40
- * Converts a result to an option forgetting the error details
41
- */
33
+ /// Converts a result to an option forgetting the error details
42
34
  def toOption[A, E](r: Result[A, E]): Option[A] = r match {
43
35
  case Success(a) => Some(a)
44
36
  case Error(exc, msg) => None()
@@ -1,41 +1,32 @@
1
+ /// This file implements the [[Seq]] type as a general purpose functional sequence.
2
+ ///
3
+ /// Implemented as a 2-3 finger tree, it supports
4
+ /// - prepend and append in amortized O(1),
5
+ /// - concat(m, n) in O(log(min(m, n)))
6
+ /// - first and last in amortized O(1)
7
+ /// - size in amortized O(1)
8
+ ///
9
+ /// More information on finger trees:
10
+ /// https://www.staff.city.ac.uk/~ross/papers/FingerTree.pdf
1
11
  module seq
2
12
 
3
- /**
4
- * This file implements the [[Seq]] type as a general purpose functional sequence.
5
- *
6
- * Implemented as a 2-3 finger tree, it supports
7
- * - prepend and append in amortized O(1),
8
- * - concat(m, n) in O(log(min(m, n)))
9
- * - first and last in amortized O(1)
10
- * - size in amortized O(1)
11
- *
12
- * More information on finger trees:
13
- * https://www.staff.city.ac.uk/~ross/papers/FingerTree.pdf
14
- */
15
-
16
- /**
17
- * Sequences of elements
18
- *
19
- * They are represented as 2-3 finger trees.
20
- */
13
+ /// Sequences of elements
14
+ ///
15
+ /// They are represented as 2-3 finger trees.
21
16
  type Seq[A] {
22
17
  Empty()
23
18
  Single(value: A)
24
19
  Deep(size: Int, prefix: Digit[A], middle: Tree[A], suffix: Digit[A])
25
20
  }
26
21
 
27
- /**
28
- * Result of splitting a sequence at the front or back.
29
- */
22
+ /// Result of splitting a sequence at the front or back.
30
23
  type View[A] {
31
24
  IsEmpty()
32
25
  View(element: A, remainder: Seq[A])
33
26
  }
34
27
 
35
- /**
36
- * Exception[NoSuchElement] is raised when accessing non-existing elements
37
- * or splitting empty collections.
38
- */
28
+ /// Exception[NoSuchElement] is raised when accessing non-existing elements
29
+ /// or splitting empty collections.
39
30
  record NoSuchElement()
40
31
 
41
32
 
@@ -61,11 +52,9 @@ record IndexOutOfBounds()
61
52
  // Internal Types
62
53
  // --------------
63
54
 
64
- /**
65
- * Internal grouping of 1-4 elements
66
- *
67
- * It is called digit since Okasaki (1998) calls finger trees "numerical operations".
68
- */
55
+ /// Internal grouping of 1-4 elements
56
+ ///
57
+ /// It is called digit since Okasaki (1998) calls finger trees "numerical operations".
69
58
  type Digit[A] {
70
59
  One(value: A)
71
60
  Two(first: A, second: A)
@@ -73,9 +62,7 @@ type Digit[A] {
73
62
  Four(first: A, second: A, third: A, fourth: A)
74
63
  }
75
64
 
76
- /**
77
- * Internal representation of a 2-3 tree
78
- */
65
+ /// Internal representation of a 2-3 tree
79
66
  type Node[A] {
80
67
  // These are necessary to avoid polymorphic recursion
81
68
  Leaf2(first: A, second: A)
@@ -84,12 +71,10 @@ type Node[A] {
84
71
  Node3(size: Int, first: Node[A], second: Node[A], third: Node[A])
85
72
  }
86
73
 
87
- /**
88
- * Internal duplicate of Seq
89
- *
90
- * splitting into two type parameters is necessary since our MLton backend does
91
- * not support mutually recursive data type declarations.
92
- */
74
+ /// Internal duplicate of Seq
75
+ ///
76
+ /// splitting into two type parameters is necessary since our MLton backend does
77
+ /// not support mutually recursive data type declarations.
93
78
  type Finger[A, N] {
94
79
  NoFinger()
95
80
  SingleFinger(value: Node[A])
@@ -141,9 +126,7 @@ def size[A](tree: Tree[A]): Int = tree match {
141
126
  case DeepFinger(size, _, _, _) => size
142
127
  }
143
128
 
144
- /**
145
- * The size of the given sequence
146
- */
129
+ /// The size of the given sequence
147
130
  def size[A](seq: Seq[A]): Int = seq match {
148
131
  case Empty() => 0
149
132
  case Single(value) => 1
@@ -408,9 +391,7 @@ def reverse[A](seq: Seq[A]): Seq[A] = {
408
391
  }
409
392
 
410
393
 
411
- /**
412
- * Random access
413
- */
394
+ /// Random access
414
395
  def index[A](seq: Seq[A], index: Int): A / Exception[NoSuchElement] = {
415
396
 
416
397
  // invariant for all internal functions: element is in the sequence
@@ -485,9 +466,7 @@ def index[A](seq: Seq[A], index: Int): A / Exception[NoSuchElement] = {
485
466
  }
486
467
  }
487
468
 
488
- /**
489
- * Random access
490
- */
469
+ /// Random access
491
470
  def update[A](seq: Seq[A], index: Int, value: A): Seq[A] / Exception[NoSuchElement] = {
492
471
 
493
472
  // invariant for all internal functions: element is in the sequence
@@ -911,4 +890,4 @@ def map[A, B](seq: Seq[A]) { f: (A) {Control} => B }: Seq[B] = {
911
890
 
912
891
 
913
892
  // TODO equality comparison
914
- //
893
+ //
@@ -0,0 +1,66 @@
1
+ module string/tty
2
+
3
+ // ANSI escape codes
4
+ namespace ANSI {
5
+ val CSI = "\u001b["
6
+
7
+ def escape(s: String) = CSI ++ s ++ "m"
8
+
9
+ val BLACK = escape("30")
10
+ val RED = escape("31")
11
+ val GREEN = escape("32")
12
+ val YELLOW = escape("33")
13
+ val BLUE = escape("34")
14
+ val MAGENTA = escape("35")
15
+ val CYAN = escape("36")
16
+ val WHITE = escape("37")
17
+
18
+ val BG_BLACK = escape("40")
19
+ val BG_RED = escape("41")
20
+ val BG_GREEN = escape("42")
21
+ val BG_YELLOW = escape("43")
22
+ val BG_BLUE = escape("44")
23
+ val BG_MAGENTA = escape("45")
24
+ val BG_CYAN = escape("46")
25
+ val BG_WHITE = escape("47")
26
+
27
+ val RESET = escape("0")
28
+
29
+ val BOLD = escape("1")
30
+ val FAINT = escape("2")
31
+ val ITALIC = escape("3")
32
+ val UNDERLINE = escape("4")
33
+ val BLINK = escape("5")
34
+ val REVERSE = escape("7")
35
+ val CROSSOUT = escape("9")
36
+ val OVERLINE = escape("53")
37
+ }
38
+
39
+ def red(text: String) = Formatted::colored(text, ANSI::RED)
40
+ def green(text: String) = Formatted::colored(text, ANSI::GREEN)
41
+ def dim(text: String) = Formatted::colored(text, ANSI::FAINT)
42
+ def bold(text: String) = Formatted::colored(text, ANSI::BOLD)
43
+
44
+ interface Formatted {
45
+ def supportsEscape(escape: String): Bool
46
+ }
47
+
48
+ namespace Formatted {
49
+ /// Run given block of code, allowing all formatting
50
+ def formatting[R] { prog : => R / Formatted }: R =
51
+ try { prog() } with Formatted {
52
+ def supportsEscape(escape: String) = resume(true)
53
+ }
54
+
55
+ /// Run given block of code, ignoring all formatting
56
+ def noFormatting[R] { prog : => R / Formatted }: R =
57
+ try { prog() } with Formatted {
58
+ def supportsEscape(escape: String) = resume(false)
59
+ }
60
+
61
+ def tryEmit(escape: String): String / Formatted =
62
+ if (do supportsEscape(escape)) escape else ""
63
+
64
+ def colored(text: String, colorEscape: String): String / Formatted =
65
+ tryEmit(colorEscape) ++ text ++ tryEmit(ANSI::RESET)
66
+ }
@@ -34,9 +34,7 @@ def substring(str: String, from: Int): String =
34
34
  str.substring(from, str.length)
35
35
 
36
36
 
37
- /**
38
- * Checks whether str starts with the given prefix at `from`
39
- */
37
+ /// Checks whether str starts with the given prefix at `from`
40
38
  def isSubstringAt(str: String, prefix: String, from: Int): Bool = {
41
39
  with default[OutOfBounds, Bool] { false };
42
40
 
@@ -59,11 +57,9 @@ def startsWith(str: String, prefix: String): Bool =
59
57
  def endsWith(str: String, suffix: String): Bool =
60
58
  isSubstringAt(str, suffix, str.length - suffix.length)
61
59
 
62
- /**
63
- * Repeats the given string n-times.
64
- *
65
- * TODO use a more efficient way of appending strings like a buffer
66
- */
60
+ /// Repeats the given string n-times.
61
+ ///
62
+ /// TODO use a more efficient way of appending strings like a buffer
67
63
  def repeat(str: String, n: Int): String = {
68
64
  def go(n: Int, result: String): String = {
69
65
  if (n == 0) result
@@ -180,17 +176,13 @@ def toInt(str: String, base: Int): Int / Exception[WrongFormat] = {
180
176
  }
181
177
  }
182
178
 
183
- // Native versions of toInt (unused right now)
184
- // ML: also accepts sml style `~2` for unary minus along with `+2` and `-2`
179
+ /// Native versions of toInt (unused right now)
185
180
  extern pure def unsafeToInt(str: String): Int =
186
181
  js "(Number.isNaN(parseInt(${str})) ? undefined : parseInt(${str}))"
187
182
  chez "(string->number ${str})"
188
- ml "Int.fromString ${str}"
189
183
 
190
184
 
191
- /**
192
- * Returns the index of the first occurrence of `sub` in `str`
193
- */
185
+ /// Returns the index of the first occurrence of `sub` in `str`
194
186
  def indexOf(str: String, sub: String): Option[Int] =
195
187
  indexOf(str, sub, 0)
196
188
 
@@ -204,9 +196,7 @@ def indexOf(str: String, sub: String, from: Int): Option[Int] = {
204
196
  go(from)
205
197
  }
206
198
 
207
- /**
208
- * Returns the index of the last occurence of `sub` in `str`
209
- */
199
+ /// Returns the index of the last occurence of `sub` in `str`
210
200
  def lastIndexOf(str: String, sub: String): Option[Int] =
211
201
  lastIndexOf(str, sub, str.length)
212
202
 
@@ -243,36 +233,11 @@ def string[T] { prog: => T / Stream }: (T, String) = <>
243
233
  def printing[T] { prog: => T / Stream }: T = <>
244
234
 
245
235
 
246
- // ANSI escape codes
247
- namespace ANSI {
248
- val BLACK = "\u001b[30m"
249
- val RED = "\u001b[31m"
250
- val GREEN = "\u001b[32m"
251
- val YELLOW = "\u001b[33m"
252
- val BLUE = "\u001b[34m"
253
- val MAGENTA = "\u001b[35m"
254
- val CYAN = "\u001b[36m"
255
- val WHITE = "\u001b[37m"
256
-
257
- val BG_BLACK = "\u001b[40m"
258
- val BG_RED = "\u001b[41m"
259
- val BG_GREEN = "\u001b[42m"
260
- val BG_YELLOW = "\u001b[43m"
261
- val BG_BLUE = "\u001b[44m"
262
- val BG_MAGENTA = "\u001b[45m"
263
- val BG_CYAN = "\u001b[46m"
264
- val BG_WHITE = "\u001b[47m"
265
-
266
- val RESET = "\u001b[0m"
267
- }
268
-
269
-
270
236
  // Characters
271
237
  // ----------
272
238
  //
273
239
  // JS: Int (Unicode codepoints)
274
240
  // CS: ?
275
- // ML: 8-bit https://smlfamily.github.io/Basis/char.html
276
241
  // LLVM: i64 representing utf-8 (varying length 1-4 bytes)
277
242
 
278
243
  extern pure def toString(ch: Char): String =
@@ -287,20 +252,17 @@ extern pure def toString(ch: Char): String =
287
252
  extern pure def toInt(ch: Char): Int =
288
253
  js "${ch}"
289
254
  chez "${ch}"
290
- ml "${ch}"
291
255
  llvm "ret %Int ${ch}"
292
256
 
293
257
  extern pure def toChar(codepoint: Int): Char =
294
258
  js "${codepoint}"
295
259
  chez "${codepoint}"
296
- ml "${codepoint}"
297
260
  llvm "ret %Int ${codepoint}"
298
261
 
299
262
 
300
263
  extern pure def infixLt(x: Char, y: Char): Bool =
301
264
  js "(${x} < ${y})"
302
265
  chez "(< ${x} ${y})"
303
- ml "(${x}: int) < ${y}"
304
266
  llvm """
305
267
  %z = icmp slt %Int ${x}, ${y}
306
268
  %fat_z = zext i1 %z to i64
@@ -311,7 +273,6 @@ extern pure def infixLt(x: Char, y: Char): Bool =
311
273
  extern pure def infixLte(x: Char, y: Char): Bool =
312
274
  js "(${x} <= ${y})"
313
275
  chez "(<= ${x} ${y})"
314
- ml "(${x}: int) <= ${y}"
315
276
  llvm """
316
277
  %z = icmp sle %Int ${x}, ${y}
317
278
  %fat_z = zext i1 %z to i64
@@ -322,7 +283,6 @@ extern pure def infixLte(x: Char, y: Char): Bool =
322
283
  extern pure def infixGt(x: Char, y: Char): Bool =
323
284
  js "(${x} > ${y})"
324
285
  chez "(> ${x} ${y})"
325
- ml "(${x}: int) > ${y}"
326
286
  llvm """
327
287
  %z = icmp sgt %Int ${x}, ${y}
328
288
  %fat_z = zext i1 %z to i64
@@ -333,7 +293,6 @@ extern pure def infixGt(x: Char, y: Char): Bool =
333
293
  extern pure def infixGte(x: Char, y: Char): Bool =
334
294
  js "(${x} >= ${y})"
335
295
  chez "(>= ${x} ${y})"
336
- ml "(${x}: int) >= ${y}"
337
296
  llvm """
338
297
  %z = icmp sge %Int ${x}, ${y}
339
298
  %fat_z = zext i1 %z to i64
@@ -388,7 +347,6 @@ def charAt(str: String, index: Int): Char / Exception[OutOfBounds] =
388
347
  extern pure def unsafeCharAt(str: String, n: Int): Char =
389
348
  js "${str}.codePointAt(${n})"
390
349
  chez "(char->integer (string-ref ${str} ${n}))"
391
- ml "(Char.ord (String.sub (${str}, ${n})))"
392
350
  llvm """
393
351
  %x = call %Int @c_buffer_character_at(%Pos ${str}, i64 ${n})
394
352
  ret %Int %x
@@ -1,5 +1,6 @@
1
- import string
1
+ import string/tty
2
2
  import process
3
+ import bench
3
4
 
4
5
  interface Assertion {
5
6
  def assert(condition: Bool, msg: String): Unit
@@ -29,70 +30,131 @@ def assert(obtained: Char, expected: Char, msg: String): Unit / Assertion =
29
30
  def assert(obtained: String, expected: String, msg: String): Unit / Assertion =
30
31
  assertEqual(obtained, expected, msg) { (x, y) => x == y }
31
32
 
32
- def assert(obtained: Int, expected: Int): Unit / Assertion =
33
+ def assert(obtained: Int, expected: Int): Unit / { Assertion, Formatted } =
33
34
  assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
34
35
 
35
- def assert(obtained: Bool, expected: Bool): Unit / Assertion =
36
+ def assert(obtained: Bool, expected: Bool): Unit / { Assertion, Formatted } =
36
37
  assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
37
38
 
38
- def assert(obtained: Char, expected: Char): Unit / Assertion =
39
+ def assert(obtained: Char, expected: Char): Unit / { Assertion, Formatted } =
39
40
  assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
40
41
 
41
- def assert(obtained: String, expected: String): Unit / Assertion =
42
+ def assert(obtained: String, expected: String): Unit / { Assertion, Formatted } =
42
43
  assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
43
44
 
44
45
  def assertEqual[A](obtained: A, expected: A, msg: String): Unit / Assertion =
45
46
  assertEqual(obtained, expected, msg) { (x, y) => x.equals(y) }
46
47
 
47
- def assertEqual[A](obtained: A, expected: A): Unit / Assertion =
48
+ def assertEqual[A](obtained: A, expected: A): Unit / { Assertion, Formatted } =
48
49
  assertEqual(obtained, expected) { (x, y) => x.equals(y) } { x => x.genericShow }
49
50
 
50
51
  def assertEqual[A](obtained: A, expected: A, msg: String) { equals: (A, A) => Bool }: Unit / Assertion =
51
52
  do assert(equals(obtained, expected), msg)
52
53
 
53
- def assertEqual[A](obtained: A, expected: A) { equals: (A, A) => Bool } { show: A => String }: Unit / Assertion =
54
- do assert(equals(obtained, expected), "Obtained: " ++ show(obtained) ++ "\n but expected:" ++ show(expected))
54
+ def assertEqual[A](obtained: A, expected: A) { equals: (A, A) => Bool } { show: A => String }: Unit / { Assertion, Formatted } =
55
+ do assert(equals(obtained, expected), Formatted::tryEmit(ANSI::RESET) ++ "Expected: ".dim ++ show(expected).green ++ "\n Obtained: ".dim ++ show(obtained).red)
56
+ // NOTE: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
57
+ // Here's an accidental capture! Can we prevent this somehow nicely?
55
58
 
56
59
 
57
60
  interface Test {
58
- def success(name: String): Unit
59
- def failure(name: String, msg: String): Unit
61
+ def success(name: String, duration: Int): Unit
62
+ def failure(name: String, msg: String, duration: Int): Unit
60
63
  }
61
64
 
62
- def test(name: String) { body: => Unit / Assertion } =
63
- try { body(); do success(name) } with Assertion {
64
- def assert(condition, msg) = if (condition) resume(()) else do failure(name, msg)
65
+ /// Runs the `body` as a test under the given `name`
66
+ /// See `suite` for examples.
67
+ def test(name: String) { body: => Unit / Assertion } = {
68
+ val startTime = bench::relativeTimestamp()
69
+ try {
70
+ body()
71
+ val duration = Duration::diff(startTime, bench::relativeTimestamp())
72
+ do success(name, duration)
73
+ } with Assertion {
74
+ def assert(condition, msg) =
75
+ if (condition) resume(())
76
+ else {
77
+ val duration = Duration::diff(startTime, bench::relativeTimestamp())
78
+ do failure(name, msg, duration)
79
+ }
65
80
  }
81
+ }
82
+
83
+ /// Run a test suite with a given `name`.
84
+ /// - If `printTimes` is `true` (or missing), prints out time in milliseconds.
85
+ /// - Formats automatically using ANSI escapes.
86
+ /// - Returns `true` if all tests succeed, otherwise returns `false`. If you want to exit the program on failure, see `mainSuite`.
87
+ ///
88
+ /// Example:
89
+ /// ```effekt
90
+ /// suite("My Tests") {
91
+ /// test("1 + 1 == 2") {
92
+ /// assertEqual(1 + 1, 2)
93
+ /// }
94
+ /// }
95
+ /// ```
96
+ def suite(name: String, printTimes: Bool) { body: => Unit / { Test, Formatted } }: Bool / {} = {
97
+ with Formatted::formatting;
98
+
99
+ def ms(duration: Int): String / Formatted =
100
+ if (printTimes) {
101
+ " " ++ ("[" ++ bench::formatMs(duration) ++ "]").dim
102
+ } else {
103
+ ""
104
+ }
105
+
106
+ def dimWhenZeroElse(s: String, n: Int) { colorIfNonZero: String => String / Formatted } =
107
+ if (n == 0) { dim(s) }
108
+ else { colorIfNonZero(s) }
66
109
 
67
- def suite(name: String) { body: => Unit / Test }: Bool = {
68
- println("Running suite: " ++ name);
69
110
  var failed = 0
70
111
  var passed = 0
71
112
 
72
- try { body() } with Test {
73
- def success(name) = {
74
- passed = passed + 1
75
- println(ANSI::GREEN ++ "+ " ++ name ++ "" ++ ANSI::RESET);
76
- resume(())
77
- }
78
- def failure(name, msg) = {
79
- failed = failed + 1
80
- println(ANSI::RED ++ "- (FAIL) " ++ name ++ "\n " ++ msg ++ ANSI::RESET);
81
- resume(())
113
+ // 1) Print the name of the test
114
+ println(name.bold)
115
+
116
+ // 2) Run the tests, timing them
117
+ val totalDuration = timed {
118
+ try { body() } with Test {
119
+ // 2a) Handle a passing test on success
120
+ def success(name, duration) = {
121
+ passed = passed + 1
122
+ println("✓".green ++ " " ++ name ++ duration.ms)
123
+ resume(())
124
+ }
125
+
126
+ // 2b) Handle a failing test on failure, additionally printing its message
127
+ def failure(name, msg, duration) = {
128
+ failed = failed + 1
129
+ println("✕".red ++ " " ++ name ++ duration.ms)
130
+ println(" " ++ msg.red)
131
+ resume(())
132
+ }
82
133
  }
83
134
  }
84
- if (failed > 0) {
85
- println(ANSI::RED ++ "Some tests failed (" ++ passed.show ++ " passed, " ++ failed.show ++ " failed)" ++ ANSI::RESET)
86
- false
87
- } else {
88
- println(ANSI::GREEN ++ "All tests passed (" ++ passed.show ++ " passed)" ++ ANSI::RESET)
89
- true
90
- }
135
+
136
+ // 3) Format the test results
137
+ println("")
138
+ println(" " ++ (passed.show ++ " pass").dimWhenZeroElse(passed) { green })
139
+ println(" " ++ (failed.show ++ " fail").dimWhenZeroElse(failed) { red })
140
+ println(" " ++ (passed + failed).show ++ " tests total" ++ totalDuration.ms)
141
+
142
+ // 4) Return true if all tests succeeded, otherwise false
143
+ return failed == 0
91
144
  }
92
145
 
146
+ /// See `suite` above.
147
+ def suite(name: String) { body: => Unit / { Test, Formatted } }: Bool / {} =
148
+ suite(name, true) { body }
149
+
93
150
  /// Use as `def main() = mainSuite("...") { ... }`
94
- def mainSuite(name: String) { body: => Unit / Test }: Unit = {
95
- val result = suite(name) { body() }
151
+ /// Recommended for standalone test files ran by CI.
152
+ ///
153
+ /// Exits after running all tests:
154
+ /// - if all tests succeed, exits the program with success (exit code 0)
155
+ /// - otherwise exits the program with failure (exit code 1)
156
+ def mainSuite(name: String) { body: => Unit / { Test, Formatted } }: Unit = {
157
+ val result = suite(name, true) { body }
96
158
  val exitCode = if (result) 0 else 1
97
159
  exit(exitCode)
98
160
  }
@@ -8,4 +8,4 @@ extern io def cont[A, B] { r: A => B / {} }: Cont[A, B] = js "${box r}"
8
8
 
9
9
  // This is unsafe if the continuation left the scope of the
10
10
  // capabilities it closed over
11
- extern control def apply[A, B](k: Cont[A, B], a: A): B / {} = js "${k}(${a})"
11
+ extern async def apply[A, B](k: Cont[A, B], a: A): B / {} = js "${k}(${a})"