@effekt-lang/effekt 0.3.0 → 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.
- package/README.md +6 -18
- package/bin/effekt +0 -0
- package/libraries/common/args.effekt +0 -27
- package/libraries/common/array.effekt +11 -16
- package/libraries/common/bench.effekt +32 -6
- package/libraries/common/effekt.effekt +30 -69
- package/libraries/common/exception.effekt +0 -1
- package/libraries/common/io/console.effekt +32 -36
- package/libraries/common/io/error.effekt +111 -2
- package/libraries/common/io/filesystem.effekt +274 -0
- package/libraries/common/io/network.effekt +30 -54
- package/libraries/common/io/time.effekt +9 -12
- package/libraries/common/io.effekt +54 -124
- package/libraries/common/list.effekt +18 -0
- package/libraries/common/process.effekt +0 -1
- package/libraries/common/ref.effekt +4 -7
- package/libraries/common/string/tty.effekt +66 -0
- package/libraries/common/string.effekt +0 -34
- package/libraries/common/test.effekt +96 -34
- package/libraries/js/unsafe/cont.effekt +1 -1
- package/libraries/llvm/io.c +258 -378
- package/libraries/llvm/main.c +3 -0
- package/libraries/llvm/rts.ll +57 -38
- package/libraries/llvm/types.c +10 -1
- package/licenses/eclipse public license, version 2.0 - epl-2.0.html +4 -4
- package/licenses/mit license - mit-license.html +19 -19
- package/licenses/the bsd license - bsd-license.html +19 -19
- package/licenses/the mit license - mit.html +20 -20
- package/package.json +1 -1
- package/libraries/chez/lift/effekt.ss +0 -118
- package/libraries/common/io/files.effekt +0 -282
- package/libraries/js/io.js +0 -143
- package/libraries/llvm/io.ll +0 -19
- package/libraries/ml/effekt.sml +0 -65
- package/libraries/ml/internal/mllist.effekt +0 -9
- package/libraries/ml/internal/mloption.effekt +0 -18
- package/libraries/ml/random.effekt +0 -85
- package/libraries/ml/text/regex.effekt +0 -102
|
@@ -3,52 +3,71 @@ module io
|
|
|
3
3
|
import ref
|
|
4
4
|
import queue
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// Asynchronous I/O
|
|
10
|
-
// ----------------
|
|
11
|
-
|
|
12
|
-
interface AsyncIO {
|
|
13
|
-
/**
|
|
14
|
-
* Runs the provided computation and conceptually waits until it completes by
|
|
15
|
-
* calling the writer-continuation.
|
|
16
|
-
*
|
|
17
|
-
* The provided writer-continuation should only ever be called ONCE.
|
|
18
|
-
* The computation is passed as a thunk in order to prevent accidental usage of
|
|
19
|
-
* other control effects that would violate the contract of calling the writer
|
|
20
|
-
* once.
|
|
21
|
-
*/
|
|
22
|
-
def performIO[T](computation: (T => Unit at {io, global}) => Unit at {io, global}): T
|
|
23
|
-
}
|
|
6
|
+
// Event Loop
|
|
7
|
+
// ----------
|
|
24
8
|
|
|
25
|
-
|
|
26
|
-
* Run an IO computation (that uses callbacks) here.
|
|
27
|
-
*/
|
|
28
|
-
def blocking(program: () => Unit / AsyncIO at {io, global}): Unit =
|
|
29
|
-
try { program() }
|
|
30
|
-
with AsyncIO {
|
|
31
|
-
def performIO[T](computation) = computation(resume)
|
|
32
|
-
}
|
|
9
|
+
type Task[T] = () => T at {io, async, global}
|
|
33
10
|
|
|
11
|
+
extern llvm """
|
|
12
|
+
declare void @c_yield(%Stack)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
extern async def spawn(task: Task[Unit]): Unit =
|
|
16
|
+
js "$effekt.callcc(callback => { setTimeout(() => callback($effekt.unit), 0); ${task}().run()})"
|
|
17
|
+
llvm """
|
|
18
|
+
call void @c_yield(%Stack %stack)
|
|
19
|
+
call void @run(%Neg ${task})
|
|
20
|
+
ret void
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
extern async def yield(): Unit =
|
|
24
|
+
js "$effekt.callcc(callback => setTimeout(() => callback($effekt.unit), 0))"
|
|
25
|
+
llvm """
|
|
26
|
+
call void @c_yield(%Stack %stack)
|
|
27
|
+
ret void
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
extern async def abort(): Nothing =
|
|
31
|
+
js "$effekt.callcc(callback => { return })"
|
|
32
|
+
llvm """
|
|
33
|
+
call void @eraseStack(%Stack %stack)
|
|
34
|
+
ret void
|
|
35
|
+
"""
|
|
34
36
|
|
|
35
|
-
interface Concurrent {
|
|
36
|
-
def yield(): Unit
|
|
37
|
-
def abort(): Nothing
|
|
38
|
-
}
|
|
39
37
|
|
|
40
38
|
// Promises
|
|
41
39
|
// --------
|
|
42
40
|
|
|
43
41
|
extern type Promise[T]
|
|
44
|
-
// = js "{
|
|
45
|
-
// = llvm "Promise"
|
|
42
|
+
// = js "{resolve: ƒ, promise: Promise}"
|
|
43
|
+
// = llvm "{tag: 0, obj: Promise*}"
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
def promise[T](task: Task[T]): Promise[T] = {
|
|
46
|
+
val p = promise::make[T]();
|
|
47
|
+
spawn(box { p.resolve(task()) });
|
|
48
|
+
return p
|
|
50
49
|
}
|
|
51
50
|
|
|
51
|
+
extern llvm """
|
|
52
|
+
declare %Pos @c_promise_make()
|
|
53
|
+
declare void @c_promise_resolve(%Pos, %Pos)
|
|
54
|
+
declare void @c_promise_await(%Pos, %Neg)
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
extern async def await[T](promise: Promise[T]): T =
|
|
58
|
+
js "$effekt.callcc(callback => ${promise}.promise.then(res => callback(res)))"
|
|
59
|
+
llvm """
|
|
60
|
+
call void @c_promise_await(%Pos ${promise}, %Stack %stack)
|
|
61
|
+
ret void
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
extern async def resolve[T](promise: Promise[T], value: T): Unit =
|
|
65
|
+
js "$effekt.callcc(callback => { ${promise}.resolve(${value}); callback($effekt.unit)})"
|
|
66
|
+
llvm """
|
|
67
|
+
call void @c_promise_resolve(%Pos ${promise}, %Pos ${value}, %Stack %stack)
|
|
68
|
+
ret void
|
|
69
|
+
"""
|
|
70
|
+
|
|
52
71
|
namespace promise {
|
|
53
72
|
extern js """
|
|
54
73
|
function promise$make() {
|
|
@@ -63,96 +82,7 @@ namespace promise {
|
|
|
63
82
|
extern io def make[T](): Promise[T] =
|
|
64
83
|
js "promise$make()"
|
|
65
84
|
llvm """
|
|
66
|
-
; Get the default loop and run it.
|
|
67
85
|
%promise = call %Pos @c_promise_make()
|
|
68
86
|
ret %Pos %promise
|
|
69
87
|
"""
|
|
70
|
-
|
|
71
|
-
extern io def resolve[T](p: Promise[T], value: T): Unit =
|
|
72
|
-
js "${p}.resolve(${value})"
|
|
73
|
-
llvm """
|
|
74
|
-
; Get the default loop and run it.
|
|
75
|
-
call void @c_promise_resolve(%Pos ${p}, %Pos ${value})
|
|
76
|
-
ret %Pos zeroinitializer
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
extern io def await[T](p: Promise[T], callback: T => Unit at {io, global}): Unit =
|
|
80
|
-
js "${p}.promise.then(res => (${callback})(res).run())"
|
|
81
|
-
llvm """
|
|
82
|
-
; Get the default loop and run it.
|
|
83
|
-
call void @c_promise_await(%Pos ${p}, %Neg ${callback})
|
|
84
|
-
ret %Pos zeroinitializer
|
|
85
|
-
"""
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// TODO we cannot add effects Files, Console, etc. to Task since modules cannot be recursive, at the moment.
|
|
90
|
-
effect IO = { AsyncIO, Concurrent, Promises }
|
|
91
|
-
|
|
92
|
-
type Task[T] = () => T / IO at {io, global}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// Event Loop
|
|
96
|
-
// ----------
|
|
97
|
-
|
|
98
|
-
def eventloop(prog: Task[Unit]) = {
|
|
99
|
-
|
|
100
|
-
def handle(p: Task[Unit]): Unit =
|
|
101
|
-
try { p() }
|
|
102
|
-
with Concurrent {
|
|
103
|
-
def yield() = eventloop::schedule(box { resume(()) })
|
|
104
|
-
def abort() = ()
|
|
105
|
-
} with AsyncIO {
|
|
106
|
-
// TODO there is a bug in our LLVM backend where eta-reducing the following will break
|
|
107
|
-
def performIO[T](computation) = computation(box { t =>
|
|
108
|
-
eventloop::schedule(box { resume(t) })
|
|
109
|
-
})
|
|
110
|
-
} with Promises {
|
|
111
|
-
def promise[T](task: Task[T]) = {
|
|
112
|
-
val p = promise::make[T]();
|
|
113
|
-
handle(box {
|
|
114
|
-
val t = task()
|
|
115
|
-
promise::resolve(p, t)
|
|
116
|
-
})
|
|
117
|
-
eventloop::schedule( box { resume(p) })
|
|
118
|
-
}
|
|
119
|
-
def await[T](p: Promise[T]) = promise::await(p, box { t => resume(t) })
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
handle(prog)
|
|
123
|
-
|
|
124
|
-
eventloop::start()
|
|
125
|
-
eventloop::stop()
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
namespace eventloop {
|
|
129
|
-
extern io def schedule(program: () => Unit at {io, global}): Unit =
|
|
130
|
-
js "setTimeout(() => (${program})().run(), 0)"
|
|
131
|
-
llvm """
|
|
132
|
-
call void @c_timer_wait(i64 0, %Neg ${program})
|
|
133
|
-
ret %Pos zeroinitializer
|
|
134
|
-
"""
|
|
135
|
-
|
|
136
|
-
extern io def start(): Unit =
|
|
137
|
-
js "$effekt.unit"
|
|
138
|
-
llvm """
|
|
139
|
-
; Get the default loop and run it.
|
|
140
|
-
%loop = call ptr @uv_default_loop()
|
|
141
|
-
%run_result = call i32 @uv_run(ptr %loop, i32 0)
|
|
142
|
-
|
|
143
|
-
; return Unit
|
|
144
|
-
ret %Pos zeroinitializer
|
|
145
|
-
"""
|
|
146
|
-
|
|
147
|
-
extern io def stop(): Unit =
|
|
148
|
-
js "$effekt.unit"
|
|
149
|
-
llvm """
|
|
150
|
-
; Get the default loop, stop and close it.
|
|
151
|
-
%loop = call ptr @uv_default_loop()
|
|
152
|
-
call void @uv_stop(ptr %loop)
|
|
153
|
-
call void @uv_loop_close(ptr %loop)
|
|
154
|
-
|
|
155
|
-
; return Unit
|
|
156
|
-
ret %Pos zeroinitializer
|
|
157
|
-
"""
|
|
158
88
|
}
|
|
@@ -222,6 +222,24 @@ def any[A](list: List[A]) { predicate: A => Bool }: Bool = {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
// Fold a list using `f`, starting from the left given a starting value.
|
|
226
|
+
//
|
|
227
|
+
// O(N)
|
|
228
|
+
def foldLeft[A, B](l: List[A], init: B) { f: (B, A) => B }: B = {
|
|
229
|
+
var acc = init;
|
|
230
|
+
l.foreach { x => acc = f(acc, x) };
|
|
231
|
+
acc
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Fold a list using `f`, starting from the right given a starting value.
|
|
235
|
+
//
|
|
236
|
+
// O(N)
|
|
237
|
+
def foldRight[A, B](l: List[A], init: B) { f: (A, B) => B }: B = {
|
|
238
|
+
var acc = init;
|
|
239
|
+
l.reverse.foreach { x => acc = f(x, acc) };
|
|
240
|
+
acc
|
|
241
|
+
}
|
|
242
|
+
|
|
225
243
|
// Sum the elements of the list.
|
|
226
244
|
//
|
|
227
245
|
// O(N)
|
|
@@ -3,10 +3,10 @@ module ref
|
|
|
3
3
|
import effekt
|
|
4
4
|
|
|
5
5
|
extern js """
|
|
6
|
-
function set$impl(ref, value) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
6
|
+
function set$impl(ref, value) {
|
|
7
|
+
ref.value = value;
|
|
8
|
+
return $effekt.unit;
|
|
9
|
+
}
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -32,7 +32,6 @@ extern global def allocate[T](): Ref[T] =
|
|
|
32
32
|
extern global def ref[T](init: T): Ref[T] =
|
|
33
33
|
js "{ value: ${init} }"
|
|
34
34
|
chez "(box ${init})"
|
|
35
|
-
ml "ref ${init}"
|
|
36
35
|
llvm """
|
|
37
36
|
%z = call %Pos @c_ref_fresh(%Pos ${init})
|
|
38
37
|
ret %Pos %z
|
|
@@ -44,7 +43,6 @@ extern global def ref[T](init: T): Ref[T] =
|
|
|
44
43
|
extern global def get[T](ref: Ref[T]): T =
|
|
45
44
|
js "${ref}.value"
|
|
46
45
|
chez "(unbox ${ref})"
|
|
47
|
-
ml "!${ref}"
|
|
48
46
|
llvm """
|
|
49
47
|
%z = call %Pos @c_ref_get(%Pos ${ref})
|
|
50
48
|
ret %Pos %z
|
|
@@ -56,7 +54,6 @@ extern global def get[T](ref: Ref[T]): T =
|
|
|
56
54
|
extern global def set[T](ref: Ref[T], value: T): Unit =
|
|
57
55
|
js "set$impl(${ref}, ${value})"
|
|
58
56
|
chez "(set-box! ${ref} ${value})"
|
|
59
|
-
ml "${ref} := ${value}"
|
|
60
57
|
llvm """
|
|
61
58
|
%z = call %Pos @c_ref_set(%Pos ${ref}, %Pos ${value})
|
|
62
59
|
ret %Pos %z
|
|
@@ -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
|
+
}
|
|
@@ -181,11 +181,9 @@ def toInt(str: String, base: Int): Int / Exception[WrongFormat] = {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// Native versions of toInt (unused right now)
|
|
184
|
-
// ML: also accepts sml style `~2` for unary minus along with `+2` and `-2`
|
|
185
184
|
extern pure def unsafeToInt(str: String): Int =
|
|
186
185
|
js "(Number.isNaN(parseInt(${str})) ? undefined : parseInt(${str}))"
|
|
187
186
|
chez "(string->number ${str})"
|
|
188
|
-
ml "Int.fromString ${str}"
|
|
189
187
|
|
|
190
188
|
|
|
191
189
|
/**
|
|
@@ -243,36 +241,11 @@ def string[T] { prog: => T / Stream }: (T, String) = <>
|
|
|
243
241
|
def printing[T] { prog: => T / Stream }: T = <>
|
|
244
242
|
|
|
245
243
|
|
|
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
244
|
// Characters
|
|
271
245
|
// ----------
|
|
272
246
|
//
|
|
273
247
|
// JS: Int (Unicode codepoints)
|
|
274
248
|
// CS: ?
|
|
275
|
-
// ML: 8-bit https://smlfamily.github.io/Basis/char.html
|
|
276
249
|
// LLVM: i64 representing utf-8 (varying length 1-4 bytes)
|
|
277
250
|
|
|
278
251
|
extern pure def toString(ch: Char): String =
|
|
@@ -287,20 +260,17 @@ extern pure def toString(ch: Char): String =
|
|
|
287
260
|
extern pure def toInt(ch: Char): Int =
|
|
288
261
|
js "${ch}"
|
|
289
262
|
chez "${ch}"
|
|
290
|
-
ml "${ch}"
|
|
291
263
|
llvm "ret %Int ${ch}"
|
|
292
264
|
|
|
293
265
|
extern pure def toChar(codepoint: Int): Char =
|
|
294
266
|
js "${codepoint}"
|
|
295
267
|
chez "${codepoint}"
|
|
296
|
-
ml "${codepoint}"
|
|
297
268
|
llvm "ret %Int ${codepoint}"
|
|
298
269
|
|
|
299
270
|
|
|
300
271
|
extern pure def infixLt(x: Char, y: Char): Bool =
|
|
301
272
|
js "(${x} < ${y})"
|
|
302
273
|
chez "(< ${x} ${y})"
|
|
303
|
-
ml "(${x}: int) < ${y}"
|
|
304
274
|
llvm """
|
|
305
275
|
%z = icmp slt %Int ${x}, ${y}
|
|
306
276
|
%fat_z = zext i1 %z to i64
|
|
@@ -311,7 +281,6 @@ extern pure def infixLt(x: Char, y: Char): Bool =
|
|
|
311
281
|
extern pure def infixLte(x: Char, y: Char): Bool =
|
|
312
282
|
js "(${x} <= ${y})"
|
|
313
283
|
chez "(<= ${x} ${y})"
|
|
314
|
-
ml "(${x}: int) <= ${y}"
|
|
315
284
|
llvm """
|
|
316
285
|
%z = icmp sle %Int ${x}, ${y}
|
|
317
286
|
%fat_z = zext i1 %z to i64
|
|
@@ -322,7 +291,6 @@ extern pure def infixLte(x: Char, y: Char): Bool =
|
|
|
322
291
|
extern pure def infixGt(x: Char, y: Char): Bool =
|
|
323
292
|
js "(${x} > ${y})"
|
|
324
293
|
chez "(> ${x} ${y})"
|
|
325
|
-
ml "(${x}: int) > ${y}"
|
|
326
294
|
llvm """
|
|
327
295
|
%z = icmp sgt %Int ${x}, ${y}
|
|
328
296
|
%fat_z = zext i1 %z to i64
|
|
@@ -333,7 +301,6 @@ extern pure def infixGt(x: Char, y: Char): Bool =
|
|
|
333
301
|
extern pure def infixGte(x: Char, y: Char): Bool =
|
|
334
302
|
js "(${x} >= ${y})"
|
|
335
303
|
chez "(>= ${x} ${y})"
|
|
336
|
-
ml "(${x}: int) >= ${y}"
|
|
337
304
|
llvm """
|
|
338
305
|
%z = icmp sge %Int ${x}, ${y}
|
|
339
306
|
%fat_z = zext i1 %z to i64
|
|
@@ -388,7 +355,6 @@ def charAt(str: String, index: Int): Char / Exception[OutOfBounds] =
|
|
|
388
355
|
extern pure def unsafeCharAt(str: String, n: Int): Char =
|
|
389
356
|
js "${str}.codePointAt(${n})"
|
|
390
357
|
chez "(char->integer (string-ref ${str} ${n}))"
|
|
391
|
-
ml "(Char.ord (String.sub (${str}, ${n})))"
|
|
392
358
|
llvm """
|
|
393
359
|
%x = call %Int @c_buffer_character_at(%Pos ${str}, i64 ${n})
|
|
394
360
|
ret %Int %x
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import string
|
|
1
|
+
import string/tty
|
|
2
2
|
import process
|
|
3
|
+
import bench
|
|
3
4
|
|
|
4
5
|
interface Assertion {
|
|
5
6
|
def assert(condition: Bool, msg: String): Unit
|
|
@@ -29,70 +30,131 @@ def assert(obtained: Char, expected: Char, msg: String): Unit / Assertion =
|
|
|
29
30
|
def assert(obtained: String, expected: String, msg: String): Unit / Assertion =
|
|
30
31
|
assertEqual(obtained, expected, msg) { (x, y) => x == y }
|
|
31
32
|
|
|
32
|
-
def assert(obtained: Int, expected: Int): Unit / Assertion =
|
|
33
|
+
def assert(obtained: Int, expected: Int): Unit / { Assertion, Formatted } =
|
|
33
34
|
assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
|
|
34
35
|
|
|
35
|
-
def assert(obtained: Bool, expected: Bool): Unit / Assertion =
|
|
36
|
+
def assert(obtained: Bool, expected: Bool): Unit / { Assertion, Formatted } =
|
|
36
37
|
assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
|
|
37
38
|
|
|
38
|
-
def assert(obtained: Char, expected: Char): Unit / Assertion =
|
|
39
|
+
def assert(obtained: Char, expected: Char): Unit / { Assertion, Formatted } =
|
|
39
40
|
assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
|
|
40
41
|
|
|
41
|
-
def assert(obtained: String, expected: String): Unit / Assertion =
|
|
42
|
+
def assert(obtained: String, expected: String): Unit / { Assertion, Formatted } =
|
|
42
43
|
assertEqual(obtained, expected) { (x, y) => x == y } { x => show(x) }
|
|
43
44
|
|
|
44
45
|
def assertEqual[A](obtained: A, expected: A, msg: String): Unit / Assertion =
|
|
45
46
|
assertEqual(obtained, expected, msg) { (x, y) => x.equals(y) }
|
|
46
47
|
|
|
47
|
-
def assertEqual[A](obtained: A, expected: A): Unit / Assertion =
|
|
48
|
+
def assertEqual[A](obtained: A, expected: A): Unit / { Assertion, Formatted } =
|
|
48
49
|
assertEqual(obtained, expected) { (x, y) => x.equals(y) } { x => x.genericShow }
|
|
49
50
|
|
|
50
51
|
def assertEqual[A](obtained: A, expected: A, msg: String) { equals: (A, A) => Bool }: Unit / Assertion =
|
|
51
52
|
do assert(equals(obtained, expected), msg)
|
|
52
53
|
|
|
53
|
-
def assertEqual[A](obtained: A, expected: A) { equals: (A, A) => Bool } { show: A => String }: Unit / Assertion =
|
|
54
|
-
do assert(equals(obtained, expected), "
|
|
54
|
+
def assertEqual[A](obtained: A, expected: A) { equals: (A, A) => Bool } { show: A => String }: Unit / { Assertion, Formatted } =
|
|
55
|
+
do assert(equals(obtained, expected), Formatted::tryEmit(ANSI::RESET) ++ "Expected: ".dim ++ show(expected).green ++ "\n Obtained: ".dim ++ show(obtained).red)
|
|
56
|
+
// NOTE: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
57
|
+
// Here's an accidental capture! Can we prevent this somehow nicely?
|
|
55
58
|
|
|
56
59
|
|
|
57
60
|
interface Test {
|
|
58
|
-
def success(name: String): Unit
|
|
59
|
-
def failure(name: String, msg: String): Unit
|
|
61
|
+
def success(name: String, duration: Int): Unit
|
|
62
|
+
def failure(name: String, msg: String, duration: Int): Unit
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
|
|
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
|
+
}
|
|
65
80
|
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Run a test suite with a given `name`.
|
|
84
|
+
/// - If `printTimes` is `true` (or missing), prints out time in milliseconds.
|
|
85
|
+
/// - Formats automatically using ANSI escapes.
|
|
86
|
+
/// - Returns `true` if all tests succeed, otherwise returns `false`. If you want to exit the program on failure, see `mainSuite`.
|
|
87
|
+
///
|
|
88
|
+
/// Example:
|
|
89
|
+
/// ```effekt
|
|
90
|
+
/// suite("My Tests") {
|
|
91
|
+
/// test("1 + 1 == 2") {
|
|
92
|
+
/// assertEqual(1 + 1, 2)
|
|
93
|
+
/// }
|
|
94
|
+
/// }
|
|
95
|
+
/// ```
|
|
96
|
+
def suite(name: String, printTimes: Bool) { body: => Unit / { Test, Formatted } }: Bool / {} = {
|
|
97
|
+
with Formatted::formatting;
|
|
98
|
+
|
|
99
|
+
def ms(duration: Int): String / Formatted =
|
|
100
|
+
if (printTimes) {
|
|
101
|
+
" " ++ ("[" ++ bench::formatMs(duration) ++ "]").dim
|
|
102
|
+
} else {
|
|
103
|
+
""
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
def dimWhenZeroElse(s: String, n: Int) { colorIfNonZero: String => String / Formatted } =
|
|
107
|
+
if (n == 0) { dim(s) }
|
|
108
|
+
else { colorIfNonZero(s) }
|
|
66
109
|
|
|
67
|
-
def suite(name: String) { body: => Unit / Test }: Bool = {
|
|
68
|
-
println("Running suite: " ++ name);
|
|
69
110
|
var failed = 0
|
|
70
111
|
var passed = 0
|
|
71
112
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
113
|
+
// 1) Print the name of the test
|
|
114
|
+
println(name.bold)
|
|
115
|
+
|
|
116
|
+
// 2) Run the tests, timing them
|
|
117
|
+
val totalDuration = timed {
|
|
118
|
+
try { body() } with Test {
|
|
119
|
+
// 2a) Handle a passing test on success
|
|
120
|
+
def success(name, duration) = {
|
|
121
|
+
passed = passed + 1
|
|
122
|
+
println("✓".green ++ " " ++ name ++ duration.ms)
|
|
123
|
+
resume(())
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 2b) Handle a failing test on failure, additionally printing its message
|
|
127
|
+
def failure(name, msg, duration) = {
|
|
128
|
+
failed = failed + 1
|
|
129
|
+
println("✕".red ++ " " ++ name ++ duration.ms)
|
|
130
|
+
println(" " ++ msg.red)
|
|
131
|
+
resume(())
|
|
132
|
+
}
|
|
82
133
|
}
|
|
83
134
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
135
|
+
|
|
136
|
+
// 3) Format the test results
|
|
137
|
+
println("")
|
|
138
|
+
println(" " ++ (passed.show ++ " pass").dimWhenZeroElse(passed) { green })
|
|
139
|
+
println(" " ++ (failed.show ++ " fail").dimWhenZeroElse(failed) { red })
|
|
140
|
+
println(" " ++ (passed + failed).show ++ " tests total" ++ totalDuration.ms)
|
|
141
|
+
|
|
142
|
+
// 4) Return true if all tests succeeded, otherwise false
|
|
143
|
+
return failed == 0
|
|
91
144
|
}
|
|
92
145
|
|
|
146
|
+
/// See `suite` above.
|
|
147
|
+
def suite(name: String) { body: => Unit / { Test, Formatted } }: Bool / {} =
|
|
148
|
+
suite(name, true) { body }
|
|
149
|
+
|
|
93
150
|
/// Use as `def main() = mainSuite("...") { ... }`
|
|
94
|
-
|
|
95
|
-
|
|
151
|
+
/// Recommended for standalone test files ran by CI.
|
|
152
|
+
///
|
|
153
|
+
/// Exits after running all tests:
|
|
154
|
+
/// - if all tests succeed, exits the program with success (exit code 0)
|
|
155
|
+
/// - otherwise exits the program with failure (exit code 1)
|
|
156
|
+
def mainSuite(name: String) { body: => Unit / { Test, Formatted } }: Unit = {
|
|
157
|
+
val result = suite(name, true) { body }
|
|
96
158
|
val exitCode = if (result) 0 else 1
|
|
97
159
|
exit(exitCode)
|
|
98
160
|
}
|
|
@@ -8,4 +8,4 @@ extern io def cont[A, B] { r: A => B / {} }: Cont[A, B] = js "${box r}"
|
|
|
8
8
|
|
|
9
9
|
// This is unsafe if the continuation left the scope of the
|
|
10
10
|
// capabilities it closed over
|
|
11
|
-
extern
|
|
11
|
+
extern async def apply[A, B](k: Cont[A, B], a: A): B / {} = js "${k}(${a})"
|