@effekt-lang/effekt 0.2.2 → 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 (96) hide show
  1. package/bin/effekt +0 -0
  2. package/bin/effekt.sh +1 -1
  3. package/libraries/chez/common/effekt_primitives.ss +10 -2
  4. package/libraries/chez/common/immutable/cslist.effekt +13 -7
  5. package/libraries/chez/common/mutable/dict.effekt +8 -10
  6. package/libraries/chez/common/text/regex.effekt +7 -9
  7. package/libraries/common/args.effekt +95 -0
  8. package/libraries/common/array.effekt +225 -0
  9. package/libraries/common/bench.effekt +60 -0
  10. package/libraries/common/buffer.effekt +123 -0
  11. package/libraries/common/bytes.effekt +125 -0
  12. package/libraries/{chez/common/immutable → common}/dequeue.effekt +18 -4
  13. package/libraries/common/effekt.effekt +764 -0
  14. package/libraries/common/exception.effekt +122 -0
  15. package/libraries/common/io/console.effekt +66 -0
  16. package/libraries/common/io/error.effekt +365 -0
  17. package/libraries/common/io/files.effekt +282 -0
  18. package/libraries/common/io/network.effekt +105 -0
  19. package/libraries/common/io/time.effekt +15 -0
  20. package/libraries/common/io.effekt +158 -0
  21. package/libraries/{llvm/immutable → common}/list.effekt +83 -74
  22. package/libraries/common/option.effekt +72 -0
  23. package/libraries/common/process.effekt +10 -0
  24. package/libraries/common/queue.effekt +140 -0
  25. package/libraries/common/ref.effekt +63 -0
  26. package/libraries/common/result.effekt +45 -0
  27. package/libraries/{ml/immutable → common}/seq.effekt +30 -19
  28. package/libraries/common/string.effekt +395 -0
  29. package/libraries/common/test.effekt +98 -0
  30. package/libraries/js/effekt_builtins.js +37 -6
  31. package/libraries/js/io.js +143 -0
  32. package/libraries/js/mutable/map.effekt +8 -9
  33. package/libraries/js/text/regex.effekt +7 -8
  34. package/libraries/js/unsafe/cont.effekt +2 -2
  35. package/libraries/js/web/dom.effekt +13 -13
  36. package/libraries/llvm/array.c +53 -0
  37. package/libraries/llvm/buffer.c +181 -76
  38. package/libraries/llvm/forward-declare-c.ll +27 -8
  39. package/libraries/llvm/io.c +543 -1
  40. package/libraries/llvm/io.ll +19 -0
  41. package/libraries/llvm/main.c +3 -1
  42. package/libraries/llvm/ref.c +45 -0
  43. package/libraries/llvm/rts.ll +496 -354
  44. package/libraries/llvm/types.c +23 -0
  45. package/libraries/ml/effekt.sml +2 -0
  46. package/libraries/ml/internal/mllist.effekt +9 -0
  47. package/libraries/ml/internal/mloption.effekt +18 -0
  48. package/libraries/ml/random.effekt +9 -9
  49. package/libraries/ml/text/regex.effekt +14 -19
  50. package/licenses/eclipse public license, version 2.0 - epl-2.0.html +2 -2
  51. package/licenses/mit license - mit-license.html +527 -57
  52. package/licenses/the bsd license - bsd-license.html +530 -55
  53. package/licenses/the mit license - mit.html +528 -58
  54. package/package.json +2 -2
  55. package/libraries/chez/callcc/effekt.effekt +0 -221
  56. package/libraries/chez/common/effekt_matching.ss +0 -100
  57. package/libraries/chez/common/immutable/list.effekt +0 -643
  58. package/libraries/chez/common/immutable/option.effekt +0 -39
  59. package/libraries/chez/common/immutable/result.effekt +0 -29
  60. package/libraries/chez/common/io/args.effekt +0 -20
  61. package/libraries/chez/common/mutable/array.effekt +0 -27
  62. package/libraries/chez/common/mutable/heap.effekt +0 -12
  63. package/libraries/chez/common/test.effekt +0 -59
  64. package/libraries/chez/common/text/string.effekt +0 -46
  65. package/libraries/chez/lift/effekt.effekt +0 -217
  66. package/libraries/chez/monadic/effekt.effekt +0 -218
  67. package/libraries/js/effekt.effekt +0 -284
  68. package/libraries/js/effekt_matching.js +0 -79
  69. package/libraries/js/immutable/dequeue.effekt +0 -78
  70. package/libraries/js/immutable/list.effekt +0 -643
  71. package/libraries/js/immutable/option.effekt +0 -49
  72. package/libraries/js/immutable/result.effekt +0 -29
  73. package/libraries/js/io/args.effekt +0 -9
  74. package/libraries/js/io/async.effekt +0 -28
  75. package/libraries/js/io/async.js +0 -2
  76. package/libraries/js/io/file.effekt +0 -50
  77. package/libraries/js/io/file_include.js +0 -13
  78. package/libraries/js/mutable/array.effekt +0 -98
  79. package/libraries/js/mutable/heap.effekt +0 -32
  80. package/libraries/js/test.effekt +0 -59
  81. package/libraries/js/text/string.effekt +0 -64
  82. package/libraries/llvm/effekt.effekt +0 -243
  83. package/libraries/llvm/immutable/option.effekt +0 -53
  84. package/libraries/llvm/immutable/result.effekt +0 -29
  85. package/libraries/llvm/io/args.effekt +0 -24
  86. package/libraries/llvm/text/string.effekt +0 -179
  87. package/libraries/ml/effekt.effekt +0 -267
  88. package/libraries/ml/immutable/dequeue.effekt +0 -81
  89. package/libraries/ml/immutable/list.effekt +0 -674
  90. package/libraries/ml/immutable/option.effekt +0 -53
  91. package/libraries/ml/immutable/result.effekt +0 -29
  92. package/libraries/ml/internal/option.effekt +0 -18
  93. package/libraries/ml/io/args.effekt +0 -41
  94. package/libraries/ml/mutable/array.effekt +0 -43
  95. package/libraries/ml/test.effekt +0 -60
  96. package/libraries/ml/text/string.effekt +0 -81
@@ -1,4 +1,4 @@
1
- module immutable/seq
1
+ module seq
2
2
 
3
3
  /**
4
4
  * This file implements the [[Seq]] type as a general purpose functional sequence.
@@ -361,12 +361,12 @@ def concat[A](first: Seq[A], second: Seq[A]): Seq[A] = {
361
361
  // Predicates
362
362
  // ----------
363
363
 
364
- def isEmpty[A](seq: Seq[A]): Boolean = seq match {
364
+ def isEmpty[A](seq: Seq[A]): Bool = seq match {
365
365
  case Empty() => true
366
366
  case _ => false
367
367
  }
368
368
 
369
- def nonEmpty[A](seq: Seq[A]): Boolean = seq match {
369
+ def nonEmpty[A](seq: Seq[A]): Bool = seq match {
370
370
  case Empty() => false
371
371
  case _ => true
372
372
  }
@@ -563,13 +563,8 @@ def update[A](seq: Seq[A], index: Int, value: A): Seq[A] / Exception[NoSuchEleme
563
563
  }
564
564
  }
565
565
 
566
- def each[A](seq: Seq[A]) { f: A => Unit / Control }: Unit = try {
567
566
 
568
- def consume(a: A): Unit =
569
- try { f(a) } with Control {
570
- def break() = outer.break()
571
- def continue() = ()
572
- }
567
+ def each[A](seq: Seq[A]) { consume: (A) => Unit }: Unit = {
573
568
 
574
569
  def go(digit: Digit[A]): Unit = digit match {
575
570
  case One(a) => consume(a)
@@ -603,18 +598,21 @@ def each[A](seq: Seq[A]) { f: A => Unit / Control }: Unit = try {
603
598
  case Single(v) => consume(v)
604
599
  case Deep(_, p, m, s) => go(p); go(m); go(s)
605
600
  }
606
- } with outer: Control {
607
- def break() = ()
608
- def continue() = ()
609
601
  }
610
602
 
611
- def eachReverse[A](seq: Seq[A]) { f: A => Unit / Control }: Unit = try {
612
-
613
- def consume(a: A): Unit =
614
- try { f(a) } with Control {
603
+ def each[A](seq: Seq[A]) { f: (A) {Control} => Unit }: Unit = try {
604
+ seq.each { (el) =>
605
+ try { f(el) {inner} } with inner: Control {
615
606
  def break() = outer.break()
616
607
  def continue() = ()
617
608
  }
609
+ }
610
+ } with outer: Control {
611
+ def break() = ()
612
+ def continue() = ()
613
+ }
614
+
615
+ def eachReverse[A](seq: Seq[A]) { consume: A => Unit }: Unit = {
618
616
 
619
617
  def go(digit: Digit[A]): Unit = digit match {
620
618
  case One(a) => consume(a)
@@ -648,6 +646,15 @@ def eachReverse[A](seq: Seq[A]) { f: A => Unit / Control }: Unit = try {
648
646
  case Single(v) => consume(v)
649
647
  case Deep(_, p, m, s) => go(s); go(m); go(p)
650
648
  }
649
+ }
650
+
651
+ def eachReverse[A](seq: Seq[A]) { f: (A) {Control} => Unit }: Unit = try {
652
+ seq.eachReverse { (el) =>
653
+ try { f(el) {inner} } with inner: Control {
654
+ def break() = outer.break()
655
+ def continue() = ()
656
+ }
657
+ }
651
658
  } with outer: Control {
652
659
  def break() = ()
653
660
  def continue() = ()
@@ -668,8 +675,6 @@ def toSeq[A](l: List[A]): Seq[A] = {
668
675
  }
669
676
 
670
677
 
671
-
672
-
673
678
  record Split[A, R](prefix: R, element: A, suffix: R)
674
679
 
675
680
  def splitAt[A](seq: Seq[A], index: Int): (Seq[A], Seq[A]) / Exception[IndexOutOfBounds] = {
@@ -864,12 +869,18 @@ def toList[A](digit: Digit[A]): List[A] = digit match {
864
869
  case Four(a, b, c, d) => Cons(a, Cons(b, Cons(c, Cons(d, Nil()))))
865
870
  }
866
871
 
867
- def map[A, B](seq: Seq[A]) { f: A => B / Control }: Seq[B] = {
872
+ def map[A, B](seq: Seq[A]) { f: A => B }: Seq[B] = {
868
873
  var result: Seq[B] = Empty()
869
874
  seq.each { a => result = result.consRight(f(a)) }
870
875
  result
871
876
  }
872
877
 
878
+ def map[A, B](seq: Seq[A]) { f: (A) {Control} => B }: Seq[B] = {
879
+ var result: Seq[B] = Empty()
880
+ seq.each { (a) {label} => result = result.consRight(f(a) {label}) }
881
+ result
882
+ }
883
+
873
884
 
874
885
  // TODO iterator interface
875
886
  //
@@ -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
+ }
@@ -1,7 +1,7 @@
1
- function show$impl(obj) {
1
+ $effekt.show = function(obj) {
2
2
  if (!!obj && !!obj.__reflect) {
3
3
  const meta = obj.__reflect()
4
- return meta.__name + "(" + meta.__data.map(show$impl).join(", ") + ")"
4
+ return meta.__name + "(" + meta.__data.map($effekt.show).join(", ") + ")"
5
5
  }
6
6
  else if (!!obj && obj.__unit) {
7
7
  return "()";
@@ -10,7 +10,7 @@ function show$impl(obj) {
10
10
  }
11
11
  }
12
12
 
13
- function equals$impl(obj1, obj2) {
13
+ $effekt.equals = function(obj1, obj2) {
14
14
  if (!!obj1.__equals) {
15
15
  return obj1.__equals(obj2)
16
16
  } else {
@@ -18,9 +18,40 @@ function equals$impl(obj1, obj2) {
18
18
  }
19
19
  }
20
20
 
21
- function println$impl(obj) {
22
- //return $effekt.delayed(() => { console.log(show(obj)); return $effekt.unit; });
23
- console.log(show$impl(obj)); return $effekt.unit;
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;
24
55
  }
25
56
 
26
57
  $effekt.unit = { __unit: true }