@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.
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/bin/effekt +0 -0
- package/bin/effekt.sh +2 -0
- package/libraries/chez/THIRD-PARTY.txt +11 -0
- package/libraries/chez/callcc/datatype.ss +188 -0
- package/libraries/chez/callcc/effekt.ss +31 -0
- package/libraries/chez/callcc/seq.ss +44 -0
- package/libraries/chez/callcc/seq0.ss +69 -0
- package/libraries/chez/callcc/tail.ss +76 -0
- package/libraries/chez/callcc/tail0.ss +89 -0
- package/libraries/chez/common/effekt_primitives.ss +105 -0
- package/libraries/chez/common/immutable/cslist.effekt +37 -0
- package/libraries/chez/common/mutable/dict.effekt +28 -0
- package/libraries/chez/common/text/pregexp.scm +772 -0
- package/libraries/chez/common/text/regex.effekt +31 -0
- package/libraries/chez/lift/effekt.ss +118 -0
- package/libraries/chez/monadic/effekt.ss +84 -0
- package/libraries/chez/monadic/seq0.ss +76 -0
- package/libraries/common/args.effekt +95 -0
- package/libraries/common/array.effekt +225 -0
- package/libraries/common/bench.effekt +60 -0
- package/libraries/common/buffer.effekt +123 -0
- package/libraries/common/bytes.effekt +125 -0
- package/libraries/common/dequeue.effekt +92 -0
- package/libraries/common/effekt.effekt +764 -0
- package/libraries/common/exception.effekt +122 -0
- package/libraries/common/io/console.effekt +66 -0
- package/libraries/common/io/error.effekt +365 -0
- package/libraries/common/io/files.effekt +282 -0
- package/libraries/common/io/network.effekt +105 -0
- package/libraries/common/io/time.effekt +15 -0
- package/libraries/common/io.effekt +158 -0
- package/libraries/common/list.effekt +687 -0
- package/libraries/common/option.effekt +72 -0
- package/libraries/common/process.effekt +10 -0
- package/libraries/common/queue.effekt +140 -0
- package/libraries/common/ref.effekt +63 -0
- package/libraries/common/result.effekt +45 -0
- package/libraries/common/seq.effekt +914 -0
- package/libraries/common/string.effekt +395 -0
- package/libraries/common/test.effekt +98 -0
- package/libraries/js/effekt_builtins.js +57 -0
- package/libraries/js/effekt_runtime.js +385 -0
- package/libraries/js/io.js +143 -0
- package/libraries/js/mutable/map.effekt +31 -0
- package/libraries/js/text/regex.effekt +33 -0
- package/libraries/js/unsafe/cont.effekt +11 -0
- package/libraries/js/web/dom.effekt +45 -0
- package/libraries/llvm/array.c +53 -0
- package/libraries/llvm/buffer.c +286 -0
- package/libraries/llvm/forward-declare-c.ll +43 -0
- package/libraries/llvm/hole.c +11 -0
- package/libraries/llvm/io.c +566 -0
- package/libraries/llvm/io.ll +19 -0
- package/libraries/llvm/main.c +41 -0
- package/libraries/llvm/ref.c +45 -0
- package/libraries/llvm/rts.ll +761 -0
- package/libraries/llvm/sanity.c +16 -0
- package/libraries/llvm/types.c +43 -0
- package/libraries/ml/effekt.sml +65 -0
- package/libraries/ml/internal/mllist.effekt +9 -0
- package/libraries/ml/internal/mloption.effekt +18 -0
- package/libraries/ml/random.effekt +85 -0
- package/libraries/ml/text/regex.effekt +102 -0
- package/licenses/THIRD-PARTY.txt +21 -0
- package/licenses/apache-2.0 - license-2.0.txt +202 -0
- package/licenses/eclipse public license, version 2.0 - epl-2.0.html +61 -0
- package/licenses/kiama-license.txt +373 -0
- package/licenses/kiama-readme.txt +186 -0
- package/licenses/mit license - mit-license.html +1239 -0
- package/licenses/the apache software license, version 2.0 - license-2.0.txt +202 -0
- package/licenses/the bsd license - bsd-license.html +1237 -0
- package/licenses/the mit license - mit.html +1239 -0
- 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 }
|