@effekt-lang/effekt 0.3.0 → 0.5.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 +34 -63
- package/libraries/common/bench.effekt +40 -22
- package/libraries/common/buffer.effekt +8 -20
- package/libraries/common/bytes.effekt +5 -15
- package/libraries/common/dequeue.effekt +5 -5
- package/libraries/common/effekt.effekt +51 -92
- package/libraries/common/exception.effekt +0 -1
- package/libraries/common/io/console.effekt +30 -36
- package/libraries/common/io/error.effekt +131 -30
- package/libraries/common/io/filesystem.effekt +262 -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 +345 -327
- package/libraries/common/process.effekt +0 -1
- package/libraries/common/queue.effekt +1 -3
- package/libraries/common/ref.effekt +11 -24
- package/libraries/common/result.effekt +6 -14
- package/libraries/common/seq.effekt +28 -49
- package/libraries/common/string/tty.effekt +66 -0
- package/libraries/common/string.effekt +7 -49
- 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 +21 -20
- package/licenses/the bsd license - bsd-license.html +20 -21
- package/licenses/the mit license - mit.html +22 -21
- 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,21 +3,17 @@ 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
|
-
|
|
13
|
-
* Global, mutable references
|
|
14
|
-
*/
|
|
12
|
+
/// Global, mutable references
|
|
15
13
|
extern type Ref[T]
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* Prefer using `ref` constructor instead to ensure that the value is defined.
|
|
20
|
-
*/
|
|
15
|
+
/// Allocates a new reference of size `size`, keeping its value _undefined_.
|
|
16
|
+
/// Prefer using `ref` constructor instead to ensure that the value is defined.
|
|
21
17
|
extern global def allocate[T](): Ref[T] =
|
|
22
18
|
js "{ value: undefined }"
|
|
23
19
|
chez "(box #f)"
|
|
@@ -26,38 +22,29 @@ extern global def allocate[T](): Ref[T] =
|
|
|
26
22
|
ret %Pos %z
|
|
27
23
|
"""
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
* Creates a new reference with the initial value `init`.
|
|
31
|
-
*/
|
|
25
|
+
/// Creates a new reference with the initial value `init`.
|
|
32
26
|
extern global def ref[T](init: T): Ref[T] =
|
|
33
27
|
js "{ value: ${init} }"
|
|
34
28
|
chez "(box ${init})"
|
|
35
|
-
ml "ref ${init}"
|
|
36
29
|
llvm """
|
|
37
30
|
%z = call %Pos @c_ref_fresh(%Pos ${init})
|
|
38
31
|
ret %Pos %z
|
|
39
32
|
"""
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
* Gets the referenced element of the `ref` in constant time.
|
|
43
|
-
*/
|
|
34
|
+
/// Gets the referenced element of the `ref` in constant time.
|
|
44
35
|
extern global def get[T](ref: Ref[T]): T =
|
|
45
36
|
js "${ref}.value"
|
|
46
37
|
chez "(unbox ${ref})"
|
|
47
|
-
ml "!${ref}"
|
|
48
38
|
llvm """
|
|
49
39
|
%z = call %Pos @c_ref_get(%Pos ${ref})
|
|
50
40
|
ret %Pos %z
|
|
51
41
|
"""
|
|
52
42
|
|
|
53
|
-
|
|
54
|
-
* Sets the referenced element of the `ref` to `value` in constant time.
|
|
55
|
-
*/
|
|
43
|
+
/// Sets the referenced element of the `ref` to `value` in constant time.
|
|
56
44
|
extern global def set[T](ref: Ref[T], value: T): Unit =
|
|
57
45
|
js "set$impl(${ref}, ${value})"
|
|
58
46
|
chez "(set-box! ${ref} ${value})"
|
|
59
|
-
ml "${ref} := ${value}"
|
|
60
47
|
llvm """
|
|
61
48
|
%z = call %Pos @c_ref_set(%Pos ${ref}, %Pos ${value})
|
|
62
49
|
ret %Pos %z
|
|
63
|
-
"""
|
|
50
|
+
"""
|
|
@@ -4,18 +4,14 @@ import effekt
|
|
|
4
4
|
import option
|
|
5
5
|
import exception
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* It is the "reification" of the Exception[E] effect.
|
|
10
|
-
*/
|
|
7
|
+
/// Represents the result of a potentially failing computation.
|
|
8
|
+
/// It is the "reification" of the Exception[E] effect.
|
|
11
9
|
type Result[A, E] {
|
|
12
10
|
Error(exception: E, msg: String);
|
|
13
11
|
Success(a: A)
|
|
14
12
|
}
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
* Represent ("reify") the result of a potentially failing computation.
|
|
18
|
-
*/
|
|
14
|
+
/// Represent ("reify") the result of a potentially failing computation.
|
|
19
15
|
def result[A, E] { f: => A / Exception[E] }: Result[A, E] = try {
|
|
20
16
|
Success(f())
|
|
21
17
|
} with Exception[E] {
|
|
@@ -27,18 +23,14 @@ def result[A, E](proxy: on[E]) { f: => A / Exception[E] }: Result[A, E] = try {
|
|
|
27
23
|
def raise(exc, msg) = Error(exc, msg)
|
|
28
24
|
}
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* Monadic "reflection"
|
|
33
|
-
*/
|
|
26
|
+
/// Extracts the value of a result, if available
|
|
27
|
+
/// Monadic "reflection"
|
|
34
28
|
def value[A, E](r: Result[A, E]): A / Exception[E] = r match {
|
|
35
29
|
case Success(a) => a
|
|
36
30
|
case Error(exc, msg) => do raise(exc, msg)
|
|
37
31
|
}
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
* Converts a result to an option forgetting the error details
|
|
41
|
-
*/
|
|
33
|
+
/// Converts a result to an option forgetting the error details
|
|
42
34
|
def toOption[A, E](r: Result[A, E]): Option[A] = r match {
|
|
43
35
|
case Success(a) => Some(a)
|
|
44
36
|
case Error(exc, msg) => None()
|
|
@@ -1,41 +1,32 @@
|
|
|
1
|
+
/// This file implements the [[Seq]] type as a general purpose functional sequence.
|
|
2
|
+
///
|
|
3
|
+
/// Implemented as a 2-3 finger tree, it supports
|
|
4
|
+
/// - prepend and append in amortized O(1),
|
|
5
|
+
/// - concat(m, n) in O(log(min(m, n)))
|
|
6
|
+
/// - first and last in amortized O(1)
|
|
7
|
+
/// - size in amortized O(1)
|
|
8
|
+
///
|
|
9
|
+
/// More information on finger trees:
|
|
10
|
+
/// https://www.staff.city.ac.uk/~ross/papers/FingerTree.pdf
|
|
1
11
|
module seq
|
|
2
12
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* Implemented as a 2-3 finger tree, it supports
|
|
7
|
-
* - prepend and append in amortized O(1),
|
|
8
|
-
* - concat(m, n) in O(log(min(m, n)))
|
|
9
|
-
* - first and last in amortized O(1)
|
|
10
|
-
* - size in amortized O(1)
|
|
11
|
-
*
|
|
12
|
-
* More information on finger trees:
|
|
13
|
-
* https://www.staff.city.ac.uk/~ross/papers/FingerTree.pdf
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Sequences of elements
|
|
18
|
-
*
|
|
19
|
-
* They are represented as 2-3 finger trees.
|
|
20
|
-
*/
|
|
13
|
+
/// Sequences of elements
|
|
14
|
+
///
|
|
15
|
+
/// They are represented as 2-3 finger trees.
|
|
21
16
|
type Seq[A] {
|
|
22
17
|
Empty()
|
|
23
18
|
Single(value: A)
|
|
24
19
|
Deep(size: Int, prefix: Digit[A], middle: Tree[A], suffix: Digit[A])
|
|
25
20
|
}
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
* Result of splitting a sequence at the front or back.
|
|
29
|
-
*/
|
|
22
|
+
/// Result of splitting a sequence at the front or back.
|
|
30
23
|
type View[A] {
|
|
31
24
|
IsEmpty()
|
|
32
25
|
View(element: A, remainder: Seq[A])
|
|
33
26
|
}
|
|
34
27
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* or splitting empty collections.
|
|
38
|
-
*/
|
|
28
|
+
/// Exception[NoSuchElement] is raised when accessing non-existing elements
|
|
29
|
+
/// or splitting empty collections.
|
|
39
30
|
record NoSuchElement()
|
|
40
31
|
|
|
41
32
|
|
|
@@ -61,11 +52,9 @@ record IndexOutOfBounds()
|
|
|
61
52
|
// Internal Types
|
|
62
53
|
// --------------
|
|
63
54
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
* It is called digit since Okasaki (1998) calls finger trees "numerical operations".
|
|
68
|
-
*/
|
|
55
|
+
/// Internal grouping of 1-4 elements
|
|
56
|
+
///
|
|
57
|
+
/// It is called digit since Okasaki (1998) calls finger trees "numerical operations".
|
|
69
58
|
type Digit[A] {
|
|
70
59
|
One(value: A)
|
|
71
60
|
Two(first: A, second: A)
|
|
@@ -73,9 +62,7 @@ type Digit[A] {
|
|
|
73
62
|
Four(first: A, second: A, third: A, fourth: A)
|
|
74
63
|
}
|
|
75
64
|
|
|
76
|
-
|
|
77
|
-
* Internal representation of a 2-3 tree
|
|
78
|
-
*/
|
|
65
|
+
/// Internal representation of a 2-3 tree
|
|
79
66
|
type Node[A] {
|
|
80
67
|
// These are necessary to avoid polymorphic recursion
|
|
81
68
|
Leaf2(first: A, second: A)
|
|
@@ -84,12 +71,10 @@ type Node[A] {
|
|
|
84
71
|
Node3(size: Int, first: Node[A], second: Node[A], third: Node[A])
|
|
85
72
|
}
|
|
86
73
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
* not support mutually recursive data type declarations.
|
|
92
|
-
*/
|
|
74
|
+
/// Internal duplicate of Seq
|
|
75
|
+
///
|
|
76
|
+
/// splitting into two type parameters is necessary since our MLton backend does
|
|
77
|
+
/// not support mutually recursive data type declarations.
|
|
93
78
|
type Finger[A, N] {
|
|
94
79
|
NoFinger()
|
|
95
80
|
SingleFinger(value: Node[A])
|
|
@@ -141,9 +126,7 @@ def size[A](tree: Tree[A]): Int = tree match {
|
|
|
141
126
|
case DeepFinger(size, _, _, _) => size
|
|
142
127
|
}
|
|
143
128
|
|
|
144
|
-
|
|
145
|
-
* The size of the given sequence
|
|
146
|
-
*/
|
|
129
|
+
/// The size of the given sequence
|
|
147
130
|
def size[A](seq: Seq[A]): Int = seq match {
|
|
148
131
|
case Empty() => 0
|
|
149
132
|
case Single(value) => 1
|
|
@@ -408,9 +391,7 @@ def reverse[A](seq: Seq[A]): Seq[A] = {
|
|
|
408
391
|
}
|
|
409
392
|
|
|
410
393
|
|
|
411
|
-
|
|
412
|
-
* Random access
|
|
413
|
-
*/
|
|
394
|
+
/// Random access
|
|
414
395
|
def index[A](seq: Seq[A], index: Int): A / Exception[NoSuchElement] = {
|
|
415
396
|
|
|
416
397
|
// invariant for all internal functions: element is in the sequence
|
|
@@ -485,9 +466,7 @@ def index[A](seq: Seq[A], index: Int): A / Exception[NoSuchElement] = {
|
|
|
485
466
|
}
|
|
486
467
|
}
|
|
487
468
|
|
|
488
|
-
|
|
489
|
-
* Random access
|
|
490
|
-
*/
|
|
469
|
+
/// Random access
|
|
491
470
|
def update[A](seq: Seq[A], index: Int, value: A): Seq[A] / Exception[NoSuchElement] = {
|
|
492
471
|
|
|
493
472
|
// invariant for all internal functions: element is in the sequence
|
|
@@ -911,4 +890,4 @@ def map[A, B](seq: Seq[A]) { f: (A) {Control} => B }: Seq[B] = {
|
|
|
911
890
|
|
|
912
891
|
|
|
913
892
|
// TODO equality comparison
|
|
914
|
-
//
|
|
893
|
+
//
|
|
@@ -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
|
+
}
|
|
@@ -34,9 +34,7 @@ def substring(str: String, from: Int): String =
|
|
|
34
34
|
str.substring(from, str.length)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
* Checks whether str starts with the given prefix at `from`
|
|
39
|
-
*/
|
|
37
|
+
/// Checks whether str starts with the given prefix at `from`
|
|
40
38
|
def isSubstringAt(str: String, prefix: String, from: Int): Bool = {
|
|
41
39
|
with default[OutOfBounds, Bool] { false };
|
|
42
40
|
|
|
@@ -59,11 +57,9 @@ def startsWith(str: String, prefix: String): Bool =
|
|
|
59
57
|
def endsWith(str: String, suffix: String): Bool =
|
|
60
58
|
isSubstringAt(str, suffix, str.length - suffix.length)
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
* TODO use a more efficient way of appending strings like a buffer
|
|
66
|
-
*/
|
|
60
|
+
/// Repeats the given string n-times.
|
|
61
|
+
///
|
|
62
|
+
/// TODO use a more efficient way of appending strings like a buffer
|
|
67
63
|
def repeat(str: String, n: Int): String = {
|
|
68
64
|
def go(n: Int, result: String): String = {
|
|
69
65
|
if (n == 0) result
|
|
@@ -180,17 +176,13 @@ def toInt(str: String, base: Int): Int / Exception[WrongFormat] = {
|
|
|
180
176
|
}
|
|
181
177
|
}
|
|
182
178
|
|
|
183
|
-
|
|
184
|
-
// ML: also accepts sml style `~2` for unary minus along with `+2` and `-2`
|
|
179
|
+
/// Native versions of toInt (unused right now)
|
|
185
180
|
extern pure def unsafeToInt(str: String): Int =
|
|
186
181
|
js "(Number.isNaN(parseInt(${str})) ? undefined : parseInt(${str}))"
|
|
187
182
|
chez "(string->number ${str})"
|
|
188
|
-
ml "Int.fromString ${str}"
|
|
189
183
|
|
|
190
184
|
|
|
191
|
-
|
|
192
|
-
* Returns the index of the first occurrence of `sub` in `str`
|
|
193
|
-
*/
|
|
185
|
+
/// Returns the index of the first occurrence of `sub` in `str`
|
|
194
186
|
def indexOf(str: String, sub: String): Option[Int] =
|
|
195
187
|
indexOf(str, sub, 0)
|
|
196
188
|
|
|
@@ -204,9 +196,7 @@ def indexOf(str: String, sub: String, from: Int): Option[Int] = {
|
|
|
204
196
|
go(from)
|
|
205
197
|
}
|
|
206
198
|
|
|
207
|
-
|
|
208
|
-
* Returns the index of the last occurence of `sub` in `str`
|
|
209
|
-
*/
|
|
199
|
+
/// Returns the index of the last occurence of `sub` in `str`
|
|
210
200
|
def lastIndexOf(str: String, sub: String): Option[Int] =
|
|
211
201
|
lastIndexOf(str, sub, str.length)
|
|
212
202
|
|
|
@@ -243,36 +233,11 @@ def string[T] { prog: => T / Stream }: (T, String) = <>
|
|
|
243
233
|
def printing[T] { prog: => T / Stream }: T = <>
|
|
244
234
|
|
|
245
235
|
|
|
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
236
|
// Characters
|
|
271
237
|
// ----------
|
|
272
238
|
//
|
|
273
239
|
// JS: Int (Unicode codepoints)
|
|
274
240
|
// CS: ?
|
|
275
|
-
// ML: 8-bit https://smlfamily.github.io/Basis/char.html
|
|
276
241
|
// LLVM: i64 representing utf-8 (varying length 1-4 bytes)
|
|
277
242
|
|
|
278
243
|
extern pure def toString(ch: Char): String =
|
|
@@ -287,20 +252,17 @@ extern pure def toString(ch: Char): String =
|
|
|
287
252
|
extern pure def toInt(ch: Char): Int =
|
|
288
253
|
js "${ch}"
|
|
289
254
|
chez "${ch}"
|
|
290
|
-
ml "${ch}"
|
|
291
255
|
llvm "ret %Int ${ch}"
|
|
292
256
|
|
|
293
257
|
extern pure def toChar(codepoint: Int): Char =
|
|
294
258
|
js "${codepoint}"
|
|
295
259
|
chez "${codepoint}"
|
|
296
|
-
ml "${codepoint}"
|
|
297
260
|
llvm "ret %Int ${codepoint}"
|
|
298
261
|
|
|
299
262
|
|
|
300
263
|
extern pure def infixLt(x: Char, y: Char): Bool =
|
|
301
264
|
js "(${x} < ${y})"
|
|
302
265
|
chez "(< ${x} ${y})"
|
|
303
|
-
ml "(${x}: int) < ${y}"
|
|
304
266
|
llvm """
|
|
305
267
|
%z = icmp slt %Int ${x}, ${y}
|
|
306
268
|
%fat_z = zext i1 %z to i64
|
|
@@ -311,7 +273,6 @@ extern pure def infixLt(x: Char, y: Char): Bool =
|
|
|
311
273
|
extern pure def infixLte(x: Char, y: Char): Bool =
|
|
312
274
|
js "(${x} <= ${y})"
|
|
313
275
|
chez "(<= ${x} ${y})"
|
|
314
|
-
ml "(${x}: int) <= ${y}"
|
|
315
276
|
llvm """
|
|
316
277
|
%z = icmp sle %Int ${x}, ${y}
|
|
317
278
|
%fat_z = zext i1 %z to i64
|
|
@@ -322,7 +283,6 @@ extern pure def infixLte(x: Char, y: Char): Bool =
|
|
|
322
283
|
extern pure def infixGt(x: Char, y: Char): Bool =
|
|
323
284
|
js "(${x} > ${y})"
|
|
324
285
|
chez "(> ${x} ${y})"
|
|
325
|
-
ml "(${x}: int) > ${y}"
|
|
326
286
|
llvm """
|
|
327
287
|
%z = icmp sgt %Int ${x}, ${y}
|
|
328
288
|
%fat_z = zext i1 %z to i64
|
|
@@ -333,7 +293,6 @@ extern pure def infixGt(x: Char, y: Char): Bool =
|
|
|
333
293
|
extern pure def infixGte(x: Char, y: Char): Bool =
|
|
334
294
|
js "(${x} >= ${y})"
|
|
335
295
|
chez "(>= ${x} ${y})"
|
|
336
|
-
ml "(${x}: int) >= ${y}"
|
|
337
296
|
llvm """
|
|
338
297
|
%z = icmp sge %Int ${x}, ${y}
|
|
339
298
|
%fat_z = zext i1 %z to i64
|
|
@@ -388,7 +347,6 @@ def charAt(str: String, index: Int): Char / Exception[OutOfBounds] =
|
|
|
388
347
|
extern pure def unsafeCharAt(str: String, n: Int): Char =
|
|
389
348
|
js "${str}.codePointAt(${n})"
|
|
390
349
|
chez "(char->integer (string-ref ${str} ${n}))"
|
|
391
|
-
ml "(Char.ord (String.sub (${str}, ${n})))"
|
|
392
350
|
llvm """
|
|
393
351
|
%x = call %Int @c_buffer_character_at(%Pos ${str}, i64 ${n})
|
|
394
352
|
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})"
|