@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.
- package/README.md +6 -18
- package/bin/effekt +0 -0
- package/bin/effekt.sh +1 -1
- package/libraries/chez/common/effekt_primitives.ss +10 -2
- package/libraries/chez/common/immutable/cslist.effekt +13 -7
- package/libraries/chez/common/mutable/dict.effekt +8 -10
- package/libraries/chez/common/text/regex.effekt +7 -9
- package/libraries/common/args.effekt +68 -0
- package/libraries/common/array.effekt +220 -0
- package/libraries/common/bench.effekt +86 -0
- package/libraries/common/buffer.effekt +123 -0
- package/libraries/common/bytes.effekt +125 -0
- package/libraries/{js/immutable → common}/dequeue.effekt +18 -4
- package/libraries/common/effekt.effekt +725 -0
- package/libraries/common/exception.effekt +121 -0
- package/libraries/common/io/console.effekt +62 -0
- package/libraries/common/io/error.effekt +474 -0
- package/libraries/common/io/filesystem.effekt +274 -0
- package/libraries/common/io/network.effekt +81 -0
- package/libraries/common/io/time.effekt +12 -0
- package/libraries/common/io.effekt +88 -0
- package/libraries/{llvm/immutable → common}/list.effekt +101 -74
- package/libraries/common/option.effekt +72 -0
- package/libraries/common/process.effekt +9 -0
- package/libraries/common/queue.effekt +140 -0
- package/libraries/common/ref.effekt +60 -0
- package/libraries/common/result.effekt +45 -0
- package/libraries/{ml/immutable → common}/seq.effekt +30 -19
- package/libraries/common/string/tty.effekt +66 -0
- package/libraries/common/string.effekt +361 -0
- package/libraries/common/test.effekt +160 -0
- package/libraries/js/effekt_builtins.js +37 -6
- package/libraries/js/mutable/map.effekt +8 -9
- package/libraries/js/text/regex.effekt +7 -8
- package/libraries/js/unsafe/cont.effekt +2 -2
- package/libraries/js/web/dom.effekt +13 -13
- package/libraries/llvm/array.c +53 -0
- package/libraries/llvm/buffer.c +181 -76
- package/libraries/llvm/forward-declare-c.ll +27 -8
- package/libraries/llvm/io.c +423 -1
- package/libraries/llvm/main.c +6 -1
- package/libraries/llvm/ref.c +45 -0
- package/libraries/llvm/rts.ll +515 -354
- package/libraries/llvm/types.c +32 -0
- package/licenses/eclipse public license, version 2.0 - epl-2.0.html +4 -4
- package/licenses/mit license - mit-license.html +532 -62
- package/licenses/the bsd license - bsd-license.html +535 -60
- package/licenses/the mit license - mit.html +532 -62
- package/package.json +2 -2
- package/libraries/chez/callcc/effekt.effekt +0 -221
- package/libraries/chez/common/effekt_matching.ss +0 -100
- package/libraries/chez/common/immutable/dequeue.effekt +0 -78
- package/libraries/chez/common/immutable/list.effekt +0 -643
- package/libraries/chez/common/immutable/option.effekt +0 -39
- package/libraries/chez/common/immutable/result.effekt +0 -29
- package/libraries/chez/common/io/args.effekt +0 -20
- package/libraries/chez/common/mutable/array.effekt +0 -27
- package/libraries/chez/common/mutable/heap.effekt +0 -12
- package/libraries/chez/common/test.effekt +0 -59
- package/libraries/chez/common/text/string.effekt +0 -46
- package/libraries/chez/lift/effekt.effekt +0 -217
- package/libraries/chez/lift/effekt.ss +0 -118
- package/libraries/chez/monadic/effekt.effekt +0 -218
- package/libraries/js/effekt.effekt +0 -284
- package/libraries/js/effekt_matching.js +0 -79
- package/libraries/js/immutable/list.effekt +0 -643
- package/libraries/js/immutable/option.effekt +0 -49
- package/libraries/js/immutable/result.effekt +0 -29
- package/libraries/js/io/args.effekt +0 -9
- package/libraries/js/io/async.effekt +0 -28
- package/libraries/js/io/async.js +0 -2
- package/libraries/js/io/file.effekt +0 -50
- package/libraries/js/io/file_include.js +0 -13
- package/libraries/js/mutable/array.effekt +0 -98
- package/libraries/js/mutable/heap.effekt +0 -32
- package/libraries/js/test.effekt +0 -59
- package/libraries/js/text/string.effekt +0 -64
- package/libraries/llvm/effekt.effekt +0 -243
- package/libraries/llvm/immutable/option.effekt +0 -53
- package/libraries/llvm/immutable/result.effekt +0 -29
- package/libraries/llvm/io/args.effekt +0 -24
- package/libraries/llvm/text/string.effekt +0 -179
- package/libraries/ml/effekt.effekt +0 -267
- package/libraries/ml/effekt.sml +0 -63
- package/libraries/ml/immutable/dequeue.effekt +0 -81
- package/libraries/ml/immutable/list.effekt +0 -674
- package/libraries/ml/immutable/option.effekt +0 -53
- package/libraries/ml/immutable/result.effekt +0 -29
- package/libraries/ml/internal/option.effekt +0 -18
- package/libraries/ml/io/args.effekt +0 -41
- package/libraries/ml/mutable/array.effekt +0 -43
- package/libraries/ml/random.effekt +0 -85
- package/libraries/ml/test.effekt +0 -60
- package/libraries/ml/text/regex.effekt +0 -107
- package/libraries/ml/text/string.effekt +0 -81
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module
|
|
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]):
|
|
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]):
|
|
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
|
-
|
|
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
|
|
612
|
-
|
|
613
|
-
|
|
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
|
|
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
|
+
}
|