@effekt-lang/effekt 0.2.1 → 0.3.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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/bin/effekt +0 -0
  4. package/bin/effekt.sh +2 -0
  5. package/libraries/chez/THIRD-PARTY.txt +11 -0
  6. package/libraries/chez/callcc/datatype.ss +188 -0
  7. package/libraries/chez/callcc/effekt.ss +31 -0
  8. package/libraries/chez/callcc/seq.ss +44 -0
  9. package/libraries/chez/callcc/seq0.ss +69 -0
  10. package/libraries/chez/callcc/tail.ss +76 -0
  11. package/libraries/chez/callcc/tail0.ss +89 -0
  12. package/libraries/chez/common/effekt_primitives.ss +105 -0
  13. package/libraries/chez/common/immutable/cslist.effekt +37 -0
  14. package/libraries/chez/common/mutable/dict.effekt +28 -0
  15. package/libraries/chez/common/text/pregexp.scm +772 -0
  16. package/libraries/chez/common/text/regex.effekt +31 -0
  17. package/libraries/chez/lift/effekt.ss +118 -0
  18. package/libraries/chez/monadic/effekt.ss +84 -0
  19. package/libraries/chez/monadic/seq0.ss +76 -0
  20. package/libraries/common/args.effekt +95 -0
  21. package/libraries/common/array.effekt +225 -0
  22. package/libraries/common/bench.effekt +60 -0
  23. package/libraries/common/buffer.effekt +123 -0
  24. package/libraries/common/bytes.effekt +125 -0
  25. package/libraries/common/dequeue.effekt +92 -0
  26. package/libraries/common/effekt.effekt +764 -0
  27. package/libraries/common/exception.effekt +122 -0
  28. package/libraries/common/io/console.effekt +66 -0
  29. package/libraries/common/io/error.effekt +365 -0
  30. package/libraries/common/io/files.effekt +282 -0
  31. package/libraries/common/io/network.effekt +105 -0
  32. package/libraries/common/io/time.effekt +15 -0
  33. package/libraries/common/io.effekt +158 -0
  34. package/libraries/common/list.effekt +687 -0
  35. package/libraries/common/option.effekt +72 -0
  36. package/libraries/common/process.effekt +10 -0
  37. package/libraries/common/queue.effekt +140 -0
  38. package/libraries/common/ref.effekt +63 -0
  39. package/libraries/common/result.effekt +45 -0
  40. package/libraries/common/seq.effekt +914 -0
  41. package/libraries/common/string.effekt +395 -0
  42. package/libraries/common/test.effekt +98 -0
  43. package/libraries/js/effekt_builtins.js +57 -0
  44. package/libraries/js/effekt_runtime.js +385 -0
  45. package/libraries/js/io.js +143 -0
  46. package/libraries/js/mutable/map.effekt +31 -0
  47. package/libraries/js/text/regex.effekt +33 -0
  48. package/libraries/js/unsafe/cont.effekt +11 -0
  49. package/libraries/js/web/dom.effekt +45 -0
  50. package/libraries/llvm/array.c +53 -0
  51. package/libraries/llvm/buffer.c +286 -0
  52. package/libraries/llvm/forward-declare-c.ll +43 -0
  53. package/libraries/llvm/hole.c +11 -0
  54. package/libraries/llvm/io.c +566 -0
  55. package/libraries/llvm/io.ll +19 -0
  56. package/libraries/llvm/main.c +41 -0
  57. package/libraries/llvm/ref.c +45 -0
  58. package/libraries/llvm/rts.ll +761 -0
  59. package/libraries/llvm/sanity.c +16 -0
  60. package/libraries/llvm/types.c +43 -0
  61. package/libraries/ml/effekt.sml +65 -0
  62. package/libraries/ml/internal/mllist.effekt +9 -0
  63. package/libraries/ml/internal/mloption.effekt +18 -0
  64. package/libraries/ml/random.effekt +85 -0
  65. package/libraries/ml/text/regex.effekt +102 -0
  66. package/licenses/THIRD-PARTY.txt +21 -0
  67. package/licenses/apache-2.0 - license-2.0.txt +202 -0
  68. package/licenses/eclipse public license, version 2.0 - epl-2.0.html +61 -0
  69. package/licenses/kiama-license.txt +373 -0
  70. package/licenses/kiama-readme.txt +186 -0
  71. package/licenses/mit license - mit-license.html +1239 -0
  72. package/licenses/the apache software license, version 2.0 - license-2.0.txt +202 -0
  73. package/licenses/the bsd license - bsd-license.html +1237 -0
  74. package/licenses/the mit license - mit.html +1239 -0
  75. package/package.json +26 -18
@@ -0,0 +1,395 @@
1
+ module string
2
+
3
+ import effekt
4
+ import option
5
+ import list
6
+ import exception
7
+ import result
8
+
9
+ // TODO
10
+ // - [ ] handle unicode codepoints (that can span two indices) correctly
11
+ // - [ ] use string buffers or other buffers to implement repeated concatenation more efficiently (and `join`)
12
+ // - [ ] maybe use native implementations for toInt, repeat, split, indexOf, etc.
13
+
14
+ /**
15
+ * Strings
16
+ * - JS: Strings are represented as UTF-16 code units where some characters take 2 slots (surrogate pairs)
17
+ * - ML: Strings are sequences of 8-bit characters (https://smlfamily.github.io/Basis/string.html)
18
+ * - Chez: Strings are sequences of unicode characters (?)
19
+ * - LLVM: UTF-8 (characters can take from 1-4 bytes).
20
+ */
21
+
22
+
23
+ // Operations
24
+ // ----------
25
+
26
+ def substring(str: String, from: Int, to: Int): String = {
27
+ def clamp(lower: Int, x: Int, upper: Int) = max(lower, min(x, upper))
28
+ val lower = clamp(0, from, to)
29
+ val upper = clamp(0, to, str.length)
30
+ str.unsafeSubstring(lower, upper)
31
+ }
32
+
33
+ def substring(str: String, from: Int): String =
34
+ str.substring(from, str.length)
35
+
36
+
37
+ /**
38
+ * Checks whether str starts with the given prefix at `from`
39
+ */
40
+ def isSubstringAt(str: String, prefix: String, from: Int): Bool = {
41
+ with default[OutOfBounds, Bool] { false };
42
+
43
+ val stringLength = str.length
44
+ val prefixLength = prefix.length
45
+
46
+ def done?(index: Int): Bool = index >= prefixLength
47
+ def matches?(index: Int): Bool = prefix.charAt(index) == str.charAt(index + from)
48
+
49
+ def go(index: Int): Bool =
50
+ if (index.done?) true
51
+ else if (index.matches?) go(index + 1) else false
52
+
53
+ go(0)
54
+ }
55
+
56
+ def startsWith(str: String, prefix: String): Bool =
57
+ isSubstringAt(str, prefix, 0)
58
+
59
+ def endsWith(str: String, suffix: String): Bool =
60
+ isSubstringAt(str, suffix, str.length - suffix.length)
61
+
62
+ /**
63
+ * Repeats the given string n-times.
64
+ *
65
+ * TODO use a more efficient way of appending strings like a buffer
66
+ */
67
+ def repeat(str: String, n: Int): String = {
68
+ def go(n: Int, result: String): String = {
69
+ if (n == 0) result
70
+ else go(n - 1, result ++ str)
71
+ }
72
+ go(n, "")
73
+ }
74
+
75
+ // TODO use .split() in JS
76
+ def split(str: String, sep: String): List[String] = {
77
+ val strLength = str.length
78
+ val sepLength = sep.length
79
+
80
+ // special case: seperator is empty, just emit individual chars
81
+ def empty(index: Int, components: List[String]): List[String] =
82
+ if (index >= strLength) components.reverse
83
+ else empty(index + 1, Cons(str.substring(index, index + 1), components))
84
+
85
+ def go(lastIndex: Int, components: List[String]): List[String] =
86
+ indexOf(str, sep, lastIndex) match {
87
+ case None() =>
88
+ Cons(str.substring(lastIndex, strLength), components).reverse
89
+ case Some(index) =>
90
+ go(index + sepLength, Cons(str.substring(lastIndex, index), components))
91
+ }
92
+
93
+ if (sepLength == 0) empty(0, Nil())
94
+ else go(0, Nil())
95
+ }
96
+
97
+ def join(strings: List[String], sep: String): String = {
98
+ def go(strings: List[String], result: String): String = strings match {
99
+ case Nil() => result
100
+ case Cons(s, rest) => go(rest, result ++ sep ++ s)
101
+ }
102
+ strings match {
103
+ case Nil() => ""
104
+ case Cons(first, rest) => go(rest, first)
105
+ }
106
+ }
107
+
108
+ extern pure def trim(str: String): String =
109
+ js "${str}.trim()"
110
+
111
+ // Parsing
112
+ // -------
113
+ def toBool(s: String): Bool / Exception[WrongFormat] = s match {
114
+ case "true" => true
115
+ case "false" => false
116
+ case _ => wrongFormat("Not a boolean value: '" ++ s ++ "'")
117
+ }
118
+
119
+ // TODO optimize (right now this will be horribly slow (compared to the native JS version)
120
+ def toInt(str: String): Int / Exception[WrongFormat] = {
121
+
122
+ val zero = '0'.toInt
123
+
124
+ def go(index: Int, acc: Int): Int = {
125
+ result[Char, OutOfBounds] { str.charAt(index) } match {
126
+ case Success(c) and c >= '0' and c <= '9' =>
127
+ go(index + 1, 10 * acc + (c.toInt - zero))
128
+ case Success(c) => wrongFormat("Not a valid number: '" ++ str ++ "'")
129
+ // wrong index means we are done parsing
130
+ case Error(_, _) => acc
131
+ }
132
+ }
133
+
134
+ with default[OutOfBounds, Int] { wrongFormat("Empty string is not a valid number") };
135
+
136
+ str.charAt(0) match {
137
+ case '-' => 0 - go(1, 0)
138
+ case _ => go(0, 0)
139
+ }
140
+ }
141
+
142
+ // TODO optimize (right now this will be horribly slow (compared to the native JS version)
143
+ def toInt(str: String, base: Int): Int / Exception[WrongFormat] = {
144
+
145
+ if( base > 36 || base < 1 ) {
146
+ wrongFormat("Invalid base: " ++ base.show)
147
+ }
148
+
149
+ val zero = '0'.toInt
150
+ val l_a = 'a'.toInt
151
+ val u_a = 'A'.toInt
152
+
153
+ def parseDigit(c: Char): Option[Int] = {
154
+ if( c >= '0' and c <= '9' and c.toInt - zero < base ) {
155
+ Some(c.toInt - zero)
156
+ } else if( c >= 'a' and c <= 'z' and c.toInt - l_a < base - 10 ) {
157
+ Some(c.toInt - l_a + 10)
158
+ } else if( c >= 'A' and c <= 'Z' and c.toInt - u_a < base - 10) {
159
+ Some(c.toInt - u_a + 10)
160
+ } else {
161
+ None()
162
+ }
163
+ }
164
+
165
+ def go(index: Int, acc: Int): Int = {
166
+ result[Char, OutOfBounds] { str.charAt(index) } match {
167
+ case Success(c) and parseDigit(c) is Some(d) =>
168
+ go(index + 1, base * acc + d)
169
+ case Success(c) => wrongFormat("Not a valid number: '" ++ str ++ "'")
170
+ // wrong index means we are done parsing
171
+ case Error(_, _) => acc
172
+ }
173
+ }
174
+
175
+ with default[OutOfBounds, Int] { wrongFormat("Empty string is not a valid number") };
176
+
177
+ str.charAt(0) match {
178
+ case '-' => 0 - go(1, 0)
179
+ case _ => go(0, 0)
180
+ }
181
+ }
182
+
183
+ // Native versions of toInt (unused right now)
184
+ // ML: also accepts sml style `~2` for unary minus along with `+2` and `-2`
185
+ extern pure def unsafeToInt(str: String): Int =
186
+ js "(Number.isNaN(parseInt(${str})) ? undefined : parseInt(${str}))"
187
+ chez "(string->number ${str})"
188
+ ml "Int.fromString ${str}"
189
+
190
+
191
+ /**
192
+ * Returns the index of the first occurrence of `sub` in `str`
193
+ */
194
+ def indexOf(str: String, sub: String): Option[Int] =
195
+ indexOf(str, sub, 0)
196
+
197
+ def indexOf(str: String, sub: String, from: Int): Option[Int] = {
198
+ val len = str.length
199
+ def go(index: Int): Option[Int] =
200
+ if (index >= len) None()
201
+ else if (str.isSubstringAt(sub, index)) Some(index)
202
+ else go(index + 1)
203
+
204
+ go(from)
205
+ }
206
+
207
+ /**
208
+ * Returns the index of the last occurence of `sub` in `str`
209
+ */
210
+ def lastIndexOf(str: String, sub: String): Option[Int] =
211
+ lastIndexOf(str, sub, str.length)
212
+
213
+ def lastIndexOf(str: String, sub: String, from: Int): Option[Int] = {
214
+ def go(index: Int): Option[Int] =
215
+ if (index < 0) None()
216
+ else if (str.isSubstringAt(sub, index)) Some(index)
217
+ else go(index - 1)
218
+
219
+ go(from)
220
+ }
221
+
222
+ // TODO more efficient (native) implementations could be integrated as follows
223
+
224
+ // extern pure def indexOf(str: String, sub: String): Option[Int] =
225
+ // //js { nativeIndexOf(str, sub) }
226
+ // default { indexOf(str, sub, 0) }
227
+
228
+ // def nativeIndexOf(str: String, sub: String): Option[Int] = {
229
+ // val index = str.unsafeIndexOf(sub)
230
+ // if (index < 0) { None() } else { Some(index) }
231
+ // }
232
+
233
+ // extern pure def unsafeIndexOf(str: String, sub: String): Int =
234
+ // js "${str}.indexOf(${sub})"
235
+
236
+ interface Stream {
237
+ def emit(string: Char): Unit
238
+ }
239
+
240
+ def string[T] { prog: => T / Stream }: (T, String) = <>
241
+
242
+ // TODO implement after concurrent IO (we need to stub `process.stdout` in JS for browsers).
243
+ def printing[T] { prog: => T / Stream }: T = <>
244
+
245
+
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
+ // Characters
271
+ // ----------
272
+ //
273
+ // JS: Int (Unicode codepoints)
274
+ // CS: ?
275
+ // ML: 8-bit https://smlfamily.github.io/Basis/char.html
276
+ // LLVM: i64 representing utf-8 (varying length 1-4 bytes)
277
+
278
+ extern pure def toString(ch: Char): String =
279
+ js "String.fromCodePoint(${ch})"
280
+ chez "(string (integer->char ${ch}))"
281
+ llvm """
282
+ %z = call %Pos @c_buffer_show_Char(%Int ${ch})
283
+ ret %Pos %z
284
+ """
285
+
286
+ // Since we currently represent Char by integers in all backends, we could reuse comparison
287
+ extern pure def toInt(ch: Char): Int =
288
+ js "${ch}"
289
+ chez "${ch}"
290
+ ml "${ch}"
291
+ llvm "ret %Int ${ch}"
292
+
293
+ extern pure def toChar(codepoint: Int): Char =
294
+ js "${codepoint}"
295
+ chez "${codepoint}"
296
+ ml "${codepoint}"
297
+ llvm "ret %Int ${codepoint}"
298
+
299
+
300
+ extern pure def infixLt(x: Char, y: Char): Bool =
301
+ js "(${x} < ${y})"
302
+ chez "(< ${x} ${y})"
303
+ ml "(${x}: int) < ${y}"
304
+ llvm """
305
+ %z = icmp slt %Int ${x}, ${y}
306
+ %fat_z = zext i1 %z to i64
307
+ %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
308
+ ret %Pos %adt_boolean
309
+ """
310
+
311
+ extern pure def infixLte(x: Char, y: Char): Bool =
312
+ js "(${x} <= ${y})"
313
+ chez "(<= ${x} ${y})"
314
+ ml "(${x}: int) <= ${y}"
315
+ llvm """
316
+ %z = icmp sle %Int ${x}, ${y}
317
+ %fat_z = zext i1 %z to i64
318
+ %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
319
+ ret %Pos %adt_boolean
320
+ """
321
+
322
+ extern pure def infixGt(x: Char, y: Char): Bool =
323
+ js "(${x} > ${y})"
324
+ chez "(> ${x} ${y})"
325
+ ml "(${x}: int) > ${y}"
326
+ llvm """
327
+ %z = icmp sgt %Int ${x}, ${y}
328
+ %fat_z = zext i1 %z to i64
329
+ %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
330
+ ret %Pos %adt_boolean
331
+ """
332
+
333
+ extern pure def infixGte(x: Char, y: Char): Bool =
334
+ js "(${x} >= ${y})"
335
+ chez "(>= ${x} ${y})"
336
+ ml "(${x}: int) >= ${y}"
337
+ llvm """
338
+ %z = icmp sge %Int ${x}, ${y}
339
+ %fat_z = zext i1 %z to i64
340
+ %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
341
+ ret %Pos %adt_boolean
342
+ """
343
+
344
+ // TODO iterator over codepoints (maybe use the JS primitive feature???)
345
+
346
+ extern type StringIterator
347
+
348
+
349
+ /**
350
+ * Determines the number of bytes needed by a codepoint
351
+ *
352
+ * Also see: https://en.wikipedia.org/wiki/UTF-8
353
+ */
354
+ def utf8ByteCount(codepoint: Char): Int = codepoint match {
355
+ case c and c >= \u0000 and c <= \u007F => 1
356
+ case c and c >= \u0080 and c <= \u07FF => 2
357
+ case c and c >= \u0800 and c <= \uFFFF => 3
358
+ case c and c >= \u10000 and c <= \u10FFFF => 4
359
+ case c => panic("Not a valid code point")
360
+ }
361
+
362
+ def utf16UnitCount(codepoint: Char): Int = codepoint match {
363
+ case c and c >= \u0000 and c <= \uFFFF => 1
364
+ case c and c >= \u10000 and c <= \u10FFFF => 4
365
+ case c => panic("Not a valid code point")
366
+ }
367
+
368
+ // TODO this is copied from the LLVM-backend stdlib. Do we need this?
369
+ // def showQuoted(s: String): String =
370
+ // "\x22" ++ s.map { c =>
371
+ // if(c == "\x22") {
372
+ // "\\\x22"
373
+ // } else if (c == "\\") {
374
+ // "\\\\"
375
+ // } else c
376
+ // } ++ "\x22"
377
+
378
+ extern pure def charWidth(c: Char): Int =
379
+ // JavaScript strings are UTF-16 where every unicode character after 0xffff takes two units
380
+ js "(${c} > 0xffff) ? 2 : 1"
381
+
382
+ def charAt(str: String, index: Int): Char / Exception[OutOfBounds] =
383
+ if (index < 0 || index >= length(str))
384
+ do raise(OutOfBounds(), "Index out of bounds: " ++ show(index) ++ " in string: '" ++ str ++ "'")
385
+ else
386
+ unsafeCharAt(str, index)
387
+
388
+ extern pure def unsafeCharAt(str: String, n: Int): Char =
389
+ js "${str}.codePointAt(${n})"
390
+ chez "(char->integer (string-ref ${str} ${n}))"
391
+ ml "(Char.ord (String.sub (${str}, ${n})))"
392
+ llvm """
393
+ %x = call %Int @c_buffer_character_at(%Pos ${str}, i64 ${n})
394
+ ret %Int %x
395
+ """
@@ -0,0 +1,98 @@
1
+ import string
2
+ import process
3
+
4
+ interface Assertion {
5
+ def assert(condition: Bool, msg: String): Unit
6
+ }
7
+
8
+ def assertTrue(condition: Bool, msg: String) =
9
+ do assert(condition, msg)
10
+
11
+ def assertTrue(condition: Bool) =
12
+ do assert(condition, "Assertion failed")
13
+
14
+ def assertFalse(condition: Bool, msg: String) =
15
+ do assert(not(condition), msg)
16
+
17
+ def assertFalse(condition: Bool) =
18
+ do assert(not(condition), "Assertion failed")
19
+
20
+ def assert(obtained: Int, expected: Int, msg: String): Unit / Assertion =
21
+ assertEqual(obtained, expected, msg) { (x, y) => x == y }
22
+
23
+ def assert(obtained: Bool, expected: Bool, msg: String): Unit / Assertion =
24
+ assertEqual(obtained, expected, msg) { (x, y) => x == y }
25
+
26
+ def assert(obtained: Char, expected: Char, msg: String): Unit / Assertion =
27
+ assertEqual(obtained, expected, msg) { (x, y) => x == y }
28
+
29
+ def assert(obtained: String, expected: String, msg: String): Unit / Assertion =
30
+ assertEqual(obtained, expected, msg) { (x, y) => x == y }
31
+
32
+ def assert(obtained: Int, expected: Int): Unit / Assertion =
33
+ assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
34
+
35
+ def assert(obtained: Bool, expected: Bool): Unit / Assertion =
36
+ assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
37
+
38
+ def assert(obtained: Char, expected: Char): Unit / Assertion =
39
+ assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
40
+
41
+ def assert(obtained: String, expected: String): Unit / Assertion =
42
+ assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
43
+
44
+ def assertEqual[A](obtained: A, expected: A, msg: String): Unit / Assertion =
45
+ assertEqual(obtained, expected, msg) { (x, y) => x.equals(y) }
46
+
47
+ def assertEqual[A](obtained: A, expected: A): Unit / Assertion =
48
+ assertEqual(obtained, expected) { (x, y) => x.equals(y) } { x => x.genericShow }
49
+
50
+ def assertEqual[A](obtained: A, expected: A, msg: String) { equals: (A, A) => Bool }: Unit / Assertion =
51
+ do assert(equals(obtained, expected), msg)
52
+
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))
55
+
56
+
57
+ interface Test {
58
+ def success(name: String): Unit
59
+ def failure(name: String, msg: String): Unit
60
+ }
61
+
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
+ }
66
+
67
+ def suite(name: String) { body: => Unit / Test }: Bool = {
68
+ println("Running suite: " ++ name);
69
+ var failed = 0
70
+ var passed = 0
71
+
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(())
82
+ }
83
+ }
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
+ }
91
+ }
92
+
93
+ /// Use as `def main() = mainSuite("...") { ... }`
94
+ def mainSuite(name: String) { body: => Unit / Test }: Unit = {
95
+ val result = suite(name) { body() }
96
+ val exitCode = if (result) 0 else 1
97
+ exit(exitCode)
98
+ }
@@ -0,0 +1,57 @@
1
+ $effekt.show = function(obj) {
2
+ if (!!obj && !!obj.__reflect) {
3
+ const meta = obj.__reflect()
4
+ return meta.__name + "(" + meta.__data.map($effekt.show).join(", ") + ")"
5
+ }
6
+ else if (!!obj && obj.__unit) {
7
+ return "()";
8
+ } else {
9
+ return "" + obj;
10
+ }
11
+ }
12
+
13
+ $effekt.equals = function(obj1, obj2) {
14
+ if (!!obj1.__equals) {
15
+ return obj1.__equals(obj2)
16
+ } else {
17
+ return (obj1.__unit && obj2.__unit) || (obj1 === obj2);
18
+ }
19
+ }
20
+
21
+ function compare$prim(n1, n2) {
22
+ if (n1 == n2) { return 0; }
23
+ else if (n1 > n2) { return 1; }
24
+ else { return -1; }
25
+ }
26
+
27
+ $effekt.compare = function(obj1, obj2) {
28
+ if ($effekt.equals(obj1, obj2)) { return 0; }
29
+
30
+ if (!!obj1 && !!obj2) {
31
+ if (!!obj1.__reflect && !!obj2.__reflect) {
32
+ const tagOrdering = compare$prim(obj1.__tag, obj2.__tag)
33
+ if (tagOrdering != 0) { return tagOrdering; }
34
+
35
+ const meta1 = obj1.__reflect().__data
36
+ const meta2 = obj2.__reflect().__data
37
+
38
+ const lengthOrdering = compare$prim(meta1.length, meta2.length)
39
+ if (lengthOrdering != 0) { return lengthOrdering; }
40
+
41
+ for (let i = 0; i < meta1.length; i++) {
42
+ const contentOrdering = $effekt.compare(meta1[i], meta2[i])
43
+ if (contentOrdering != 0) { return contentOrdering; }
44
+ }
45
+
46
+ return 0;
47
+ }
48
+ }
49
+
50
+ return compare$prim(obj1, obj2);
51
+ }
52
+
53
+ $effekt.println = function println$impl(str) {
54
+ console.log(str); return $effekt.unit;
55
+ }
56
+
57
+ $effekt.unit = { __unit: true }