@effekt-lang/effekt 0.2.2 → 0.4.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 (95) hide show
  1. package/README.md +6 -18
  2. package/bin/effekt +0 -0
  3. package/bin/effekt.sh +1 -1
  4. package/libraries/chez/common/effekt_primitives.ss +10 -2
  5. package/libraries/chez/common/immutable/cslist.effekt +13 -7
  6. package/libraries/chez/common/mutable/dict.effekt +8 -10
  7. package/libraries/chez/common/text/regex.effekt +7 -9
  8. package/libraries/common/args.effekt +68 -0
  9. package/libraries/common/array.effekt +220 -0
  10. package/libraries/common/bench.effekt +86 -0
  11. package/libraries/common/buffer.effekt +123 -0
  12. package/libraries/common/bytes.effekt +125 -0
  13. package/libraries/{js/immutable → common}/dequeue.effekt +18 -4
  14. package/libraries/common/effekt.effekt +725 -0
  15. package/libraries/common/exception.effekt +121 -0
  16. package/libraries/common/io/console.effekt +62 -0
  17. package/libraries/common/io/error.effekt +474 -0
  18. package/libraries/common/io/filesystem.effekt +274 -0
  19. package/libraries/common/io/network.effekt +81 -0
  20. package/libraries/common/io/time.effekt +12 -0
  21. package/libraries/common/io.effekt +88 -0
  22. package/libraries/{llvm/immutable → common}/list.effekt +101 -74
  23. package/libraries/common/option.effekt +72 -0
  24. package/libraries/common/process.effekt +9 -0
  25. package/libraries/common/queue.effekt +140 -0
  26. package/libraries/common/ref.effekt +60 -0
  27. package/libraries/common/result.effekt +45 -0
  28. package/libraries/{ml/immutable → common}/seq.effekt +30 -19
  29. package/libraries/common/string/tty.effekt +66 -0
  30. package/libraries/common/string.effekt +361 -0
  31. package/libraries/common/test.effekt +160 -0
  32. package/libraries/js/effekt_builtins.js +37 -6
  33. package/libraries/js/mutable/map.effekt +8 -9
  34. package/libraries/js/text/regex.effekt +7 -8
  35. package/libraries/js/unsafe/cont.effekt +2 -2
  36. package/libraries/js/web/dom.effekt +13 -13
  37. package/libraries/llvm/array.c +53 -0
  38. package/libraries/llvm/buffer.c +181 -76
  39. package/libraries/llvm/forward-declare-c.ll +27 -8
  40. package/libraries/llvm/io.c +423 -1
  41. package/libraries/llvm/main.c +6 -1
  42. package/libraries/llvm/ref.c +45 -0
  43. package/libraries/llvm/rts.ll +515 -354
  44. package/libraries/llvm/types.c +32 -0
  45. package/licenses/eclipse public license, version 2.0 - epl-2.0.html +4 -4
  46. package/licenses/mit license - mit-license.html +532 -62
  47. package/licenses/the bsd license - bsd-license.html +535 -60
  48. package/licenses/the mit license - mit.html +532 -62
  49. package/package.json +2 -2
  50. package/libraries/chez/callcc/effekt.effekt +0 -221
  51. package/libraries/chez/common/effekt_matching.ss +0 -100
  52. package/libraries/chez/common/immutable/dequeue.effekt +0 -78
  53. package/libraries/chez/common/immutable/list.effekt +0 -643
  54. package/libraries/chez/common/immutable/option.effekt +0 -39
  55. package/libraries/chez/common/immutable/result.effekt +0 -29
  56. package/libraries/chez/common/io/args.effekt +0 -20
  57. package/libraries/chez/common/mutable/array.effekt +0 -27
  58. package/libraries/chez/common/mutable/heap.effekt +0 -12
  59. package/libraries/chez/common/test.effekt +0 -59
  60. package/libraries/chez/common/text/string.effekt +0 -46
  61. package/libraries/chez/lift/effekt.effekt +0 -217
  62. package/libraries/chez/lift/effekt.ss +0 -118
  63. package/libraries/chez/monadic/effekt.effekt +0 -218
  64. package/libraries/js/effekt.effekt +0 -284
  65. package/libraries/js/effekt_matching.js +0 -79
  66. package/libraries/js/immutable/list.effekt +0 -643
  67. package/libraries/js/immutable/option.effekt +0 -49
  68. package/libraries/js/immutable/result.effekt +0 -29
  69. package/libraries/js/io/args.effekt +0 -9
  70. package/libraries/js/io/async.effekt +0 -28
  71. package/libraries/js/io/async.js +0 -2
  72. package/libraries/js/io/file.effekt +0 -50
  73. package/libraries/js/io/file_include.js +0 -13
  74. package/libraries/js/mutable/array.effekt +0 -98
  75. package/libraries/js/mutable/heap.effekt +0 -32
  76. package/libraries/js/test.effekt +0 -59
  77. package/libraries/js/text/string.effekt +0 -64
  78. package/libraries/llvm/effekt.effekt +0 -243
  79. package/libraries/llvm/immutable/option.effekt +0 -53
  80. package/libraries/llvm/immutable/result.effekt +0 -29
  81. package/libraries/llvm/io/args.effekt +0 -24
  82. package/libraries/llvm/text/string.effekt +0 -179
  83. package/libraries/ml/effekt.effekt +0 -267
  84. package/libraries/ml/effekt.sml +0 -63
  85. package/libraries/ml/immutable/dequeue.effekt +0 -81
  86. package/libraries/ml/immutable/list.effekt +0 -674
  87. package/libraries/ml/immutable/option.effekt +0 -53
  88. package/libraries/ml/immutable/result.effekt +0 -29
  89. package/libraries/ml/internal/option.effekt +0 -18
  90. package/libraries/ml/io/args.effekt +0 -41
  91. package/libraries/ml/mutable/array.effekt +0 -43
  92. package/libraries/ml/random.effekt +0 -85
  93. package/libraries/ml/test.effekt +0 -60
  94. package/libraries/ml/text/regex.effekt +0 -107
  95. 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,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
+ }
@@ -0,0 +1,361 @@
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
+ extern pure def unsafeToInt(str: String): Int =
185
+ js "(Number.isNaN(parseInt(${str})) ? undefined : parseInt(${str}))"
186
+ chez "(string->number ${str})"
187
+
188
+
189
+ /**
190
+ * Returns the index of the first occurrence of `sub` in `str`
191
+ */
192
+ def indexOf(str: String, sub: String): Option[Int] =
193
+ indexOf(str, sub, 0)
194
+
195
+ def indexOf(str: String, sub: String, from: Int): Option[Int] = {
196
+ val len = str.length
197
+ def go(index: Int): Option[Int] =
198
+ if (index >= len) None()
199
+ else if (str.isSubstringAt(sub, index)) Some(index)
200
+ else go(index + 1)
201
+
202
+ go(from)
203
+ }
204
+
205
+ /**
206
+ * Returns the index of the last occurence of `sub` in `str`
207
+ */
208
+ def lastIndexOf(str: String, sub: String): Option[Int] =
209
+ lastIndexOf(str, sub, str.length)
210
+
211
+ def lastIndexOf(str: String, sub: String, from: Int): Option[Int] = {
212
+ def go(index: Int): Option[Int] =
213
+ if (index < 0) None()
214
+ else if (str.isSubstringAt(sub, index)) Some(index)
215
+ else go(index - 1)
216
+
217
+ go(from)
218
+ }
219
+
220
+ // TODO more efficient (native) implementations could be integrated as follows
221
+
222
+ // extern pure def indexOf(str: String, sub: String): Option[Int] =
223
+ // //js { nativeIndexOf(str, sub) }
224
+ // default { indexOf(str, sub, 0) }
225
+
226
+ // def nativeIndexOf(str: String, sub: String): Option[Int] = {
227
+ // val index = str.unsafeIndexOf(sub)
228
+ // if (index < 0) { None() } else { Some(index) }
229
+ // }
230
+
231
+ // extern pure def unsafeIndexOf(str: String, sub: String): Int =
232
+ // js "${str}.indexOf(${sub})"
233
+
234
+ interface Stream {
235
+ def emit(string: Char): Unit
236
+ }
237
+
238
+ def string[T] { prog: => T / Stream }: (T, String) = <>
239
+
240
+ // TODO implement after concurrent IO (we need to stub `process.stdout` in JS for browsers).
241
+ def printing[T] { prog: => T / Stream }: T = <>
242
+
243
+
244
+ // Characters
245
+ // ----------
246
+ //
247
+ // JS: Int (Unicode codepoints)
248
+ // CS: ?
249
+ // LLVM: i64 representing utf-8 (varying length 1-4 bytes)
250
+
251
+ extern pure def toString(ch: Char): String =
252
+ js "String.fromCodePoint(${ch})"
253
+ chez "(string (integer->char ${ch}))"
254
+ llvm """
255
+ %z = call %Pos @c_buffer_show_Char(%Int ${ch})
256
+ ret %Pos %z
257
+ """
258
+
259
+ // Since we currently represent Char by integers in all backends, we could reuse comparison
260
+ extern pure def toInt(ch: Char): Int =
261
+ js "${ch}"
262
+ chez "${ch}"
263
+ llvm "ret %Int ${ch}"
264
+
265
+ extern pure def toChar(codepoint: Int): Char =
266
+ js "${codepoint}"
267
+ chez "${codepoint}"
268
+ llvm "ret %Int ${codepoint}"
269
+
270
+
271
+ extern pure def infixLt(x: Char, y: Char): Bool =
272
+ js "(${x} < ${y})"
273
+ chez "(< ${x} ${y})"
274
+ llvm """
275
+ %z = icmp slt %Int ${x}, ${y}
276
+ %fat_z = zext i1 %z to i64
277
+ %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
278
+ ret %Pos %adt_boolean
279
+ """
280
+
281
+ extern pure def infixLte(x: Char, y: Char): Bool =
282
+ js "(${x} <= ${y})"
283
+ chez "(<= ${x} ${y})"
284
+ llvm """
285
+ %z = icmp sle %Int ${x}, ${y}
286
+ %fat_z = zext i1 %z to i64
287
+ %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
288
+ ret %Pos %adt_boolean
289
+ """
290
+
291
+ extern pure def infixGt(x: Char, y: Char): Bool =
292
+ js "(${x} > ${y})"
293
+ chez "(> ${x} ${y})"
294
+ llvm """
295
+ %z = icmp sgt %Int ${x}, ${y}
296
+ %fat_z = zext i1 %z to i64
297
+ %adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
298
+ ret %Pos %adt_boolean
299
+ """
300
+
301
+ extern pure def infixGte(x: Char, y: Char): Bool =
302
+ js "(${x} >= ${y})"
303
+ chez "(>= ${x} ${y})"
304
+ llvm """
305
+ %z = icmp sge %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
+ // TODO iterator over codepoints (maybe use the JS primitive feature???)
312
+
313
+ extern type StringIterator
314
+
315
+
316
+ /**
317
+ * Determines the number of bytes needed by a codepoint
318
+ *
319
+ * Also see: https://en.wikipedia.org/wiki/UTF-8
320
+ */
321
+ def utf8ByteCount(codepoint: Char): Int = codepoint match {
322
+ case c and c >= \u0000 and c <= \u007F => 1
323
+ case c and c >= \u0080 and c <= \u07FF => 2
324
+ case c and c >= \u0800 and c <= \uFFFF => 3
325
+ case c and c >= \u10000 and c <= \u10FFFF => 4
326
+ case c => panic("Not a valid code point")
327
+ }
328
+
329
+ def utf16UnitCount(codepoint: Char): Int = codepoint match {
330
+ case c and c >= \u0000 and c <= \uFFFF => 1
331
+ case c and c >= \u10000 and c <= \u10FFFF => 4
332
+ case c => panic("Not a valid code point")
333
+ }
334
+
335
+ // TODO this is copied from the LLVM-backend stdlib. Do we need this?
336
+ // def showQuoted(s: String): String =
337
+ // "\x22" ++ s.map { c =>
338
+ // if(c == "\x22") {
339
+ // "\\\x22"
340
+ // } else if (c == "\\") {
341
+ // "\\\\"
342
+ // } else c
343
+ // } ++ "\x22"
344
+
345
+ extern pure def charWidth(c: Char): Int =
346
+ // JavaScript strings are UTF-16 where every unicode character after 0xffff takes two units
347
+ js "(${c} > 0xffff) ? 2 : 1"
348
+
349
+ def charAt(str: String, index: Int): Char / Exception[OutOfBounds] =
350
+ if (index < 0 || index >= length(str))
351
+ do raise(OutOfBounds(), "Index out of bounds: " ++ show(index) ++ " in string: '" ++ str ++ "'")
352
+ else
353
+ unsafeCharAt(str, index)
354
+
355
+ extern pure def unsafeCharAt(str: String, n: Int): Char =
356
+ js "${str}.codePointAt(${n})"
357
+ chez "(char->integer (string-ref ${str} ${n}))"
358
+ llvm """
359
+ %x = call %Int @c_buffer_character_at(%Pos ${str}, i64 ${n})
360
+ ret %Int %x
361
+ """
@@ -0,0 +1,160 @@
1
+ import string/tty
2
+ import process
3
+ import bench
4
+
5
+ interface Assertion {
6
+ def assert(condition: Bool, msg: String): Unit
7
+ }
8
+
9
+ def assertTrue(condition: Bool, msg: String) =
10
+ do assert(condition, msg)
11
+
12
+ def assertTrue(condition: Bool) =
13
+ do assert(condition, "Assertion failed")
14
+
15
+ def assertFalse(condition: Bool, msg: String) =
16
+ do assert(not(condition), msg)
17
+
18
+ def assertFalse(condition: Bool) =
19
+ do assert(not(condition), "Assertion failed")
20
+
21
+ def assert(obtained: Int, expected: Int, msg: String): Unit / Assertion =
22
+ assertEqual(obtained, expected, msg) { (x, y) => x == y }
23
+
24
+ def assert(obtained: Bool, expected: Bool, msg: String): Unit / Assertion =
25
+ assertEqual(obtained, expected, msg) { (x, y) => x == y }
26
+
27
+ def assert(obtained: Char, expected: Char, msg: String): Unit / Assertion =
28
+ assertEqual(obtained, expected, msg) { (x, y) => x == y }
29
+
30
+ def assert(obtained: String, expected: String, msg: String): Unit / Assertion =
31
+ assertEqual(obtained, expected, msg) { (x, y) => x == y }
32
+
33
+ def assert(obtained: Int, expected: Int): Unit / { Assertion, Formatted } =
34
+ assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
35
+
36
+ def assert(obtained: Bool, expected: Bool): Unit / { Assertion, Formatted } =
37
+ assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
38
+
39
+ def assert(obtained: Char, expected: Char): Unit / { Assertion, Formatted } =
40
+ assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
41
+
42
+ def assert(obtained: String, expected: String): Unit / { Assertion, Formatted } =
43
+ assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
44
+
45
+ def assertEqual[A](obtained: A, expected: A, msg: String): Unit / Assertion =
46
+ assertEqual(obtained, expected, msg) { (x, y) => x.equals(y) }
47
+
48
+ def assertEqual[A](obtained: A, expected: A): Unit / { Assertion, Formatted } =
49
+ assertEqual(obtained, expected) { (x, y) => x.equals(y) } { x => x.genericShow }
50
+
51
+ def assertEqual[A](obtained: A, expected: A, msg: String) { equals: (A, A) => Bool }: Unit / Assertion =
52
+ do assert(equals(obtained, expected), msg)
53
+
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?
58
+
59
+
60
+ interface Test {
61
+ def success(name: String, duration: Int): Unit
62
+ def failure(name: String, msg: String, duration: Int): Unit
63
+ }
64
+
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
+ }
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) }
109
+
110
+ var failed = 0
111
+ var passed = 0
112
+
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
+ }
133
+ }
134
+ }
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
144
+ }
145
+
146
+ /// See `suite` above.
147
+ def suite(name: String) { body: => Unit / { Test, Formatted } }: Bool / {} =
148
+ suite(name, true) { body }
149
+
150
+ /// Use as `def main() = mainSuite("...") { ... }`
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 }
158
+ val exitCode = if (result) 0 else 1
159
+ exit(exitCode)
160
+ }