@effekt-lang/effekt 0.29.0 → 0.31.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/bin/effekt +0 -0
- package/libraries/common/char.effekt +130 -35
- package/libraries/common/effekt.effekt +25 -1
- package/libraries/common/exception.effekt +1 -1
- package/libraries/common/json.effekt +1 -2
- package/libraries/common/result.effekt +6 -0
- package/libraries/common/scanner.effekt +11 -3
- package/libraries/common/stream.effekt +107 -2
- package/libraries/common/string.effekt +31 -136
- package/libraries/js/effekt_builtins.js +1 -1
- package/libraries/llvm/bytearray.c +1 -1
- package/libraries/llvm/forward-declare-c.ll +2 -4
- package/libraries/llvm/io.c +23 -16
- package/licenses/eclipse public license, version 2.0 - epl-2.0.html +2 -2
- package/licenses/mit license - mit-license.html +35 -20
- package/licenses/the bsd license - bsd-license.html +36 -21
- package/licenses/the mit license - mit.html +35 -20
- package/package.json +1 -1
package/bin/effekt
CHANGED
|
Binary file
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/// Warning: This library currently only works with ASCII characters, **not** unicode!
|
|
2
2
|
module char
|
|
3
3
|
|
|
4
|
+
import effekt
|
|
4
5
|
import exception
|
|
6
|
+
import option
|
|
7
|
+
import result
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
/// Checks if the given character is an ASCII whitespace
|
|
@@ -15,56 +18,51 @@ def isWhitespace(c: Char): Bool = c match {
|
|
|
15
18
|
case _ => false
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
/// Gets the value of a given ASCII digit in base 10
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
/// Gets the value of a given ASCII digit in base 10,
|
|
22
|
+
/// throwing an exception on wrong format
|
|
23
|
+
def digitValue(char: Char): Int / Exception[WrongFormat] =
|
|
21
24
|
if (char >= '0' && char <= '9') {
|
|
22
|
-
|
|
25
|
+
char.toInt - '0'.toInt
|
|
23
26
|
} else {
|
|
24
|
-
|
|
27
|
+
wrongFormat("Not a valid digit: '" ++ char.toString ++ "' in base 10")
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
/// Gets the value of a given ASCII digit in base 16
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
/// Gets the value of a given ASCII digit in base 16,
|
|
31
|
+
/// throwing an exception on wrong format
|
|
32
|
+
def hexDigitValue(char: Char): Int / Exception[WrongFormat] =
|
|
30
33
|
char match {
|
|
31
|
-
case char and char >= '0' && char <= '9' =>
|
|
32
|
-
case char and char >= 'A' && char <= 'F' =>
|
|
33
|
-
case char and char >= 'a' && char <= 'f' =>
|
|
34
|
-
case _ =>
|
|
34
|
+
case char and char >= '0' && char <= '9' => char.toInt - '0'.toInt
|
|
35
|
+
case char and char >= 'A' && char <= 'F' => (char.toInt - 'A'.toInt) + 10
|
|
36
|
+
case char and char >= 'a' && char <= 'f' => (char.toInt - 'a'.toInt) + 10
|
|
37
|
+
case _ => wrongFormat("Not a valid digit: '" ++ char.toString ++ "' in base 16")
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
/// Gets the value of a given ASCII digit in the given base up to 36
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
val
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
perhapsDigit match {
|
|
47
|
-
case Some(digit) =>
|
|
48
|
-
if (digit < base) {
|
|
49
|
-
Some(digit)
|
|
50
|
-
} else {
|
|
51
|
-
None()
|
|
52
|
-
}
|
|
53
|
-
case None() => None()
|
|
40
|
+
/// Gets the value of a given ASCII digit in the given base up to 36,
|
|
41
|
+
/// throwing an exception on wrong format
|
|
42
|
+
def digitValue(char: Char, base: Int): Int / Exception[WrongFormat] = {
|
|
43
|
+
val digit = char match {
|
|
44
|
+
case char and char >= '0' && char <= '9' => char.toInt - '0'.toInt
|
|
45
|
+
case char and char >= 'A' && char <= 'Z' => (char.toInt - 'A'.toInt) + 10
|
|
46
|
+
case char and char >= 'a' && char <= 'z' => (char.toInt - 'a'.toInt) + 10
|
|
47
|
+
case _ => wrongFormat("Not a valid digit: '" ++ char.toString ++ "'")
|
|
54
48
|
}
|
|
49
|
+
if (digit >= base) {
|
|
50
|
+
wrongFormat("Digit '" ++ digit.show ++ "' is too big for base " ++ base.show)
|
|
51
|
+
}
|
|
52
|
+
digit
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
/// Checks if the given character is an ASCII digit in base 10
|
|
58
|
-
///
|
|
59
|
-
def isDigit(char: Char): Bool = digitValue(char).
|
|
56
|
+
/// Prefer using `digitValue(c: Char)` to get the numeric value out.
|
|
57
|
+
def isDigit(char: Char): Bool = result[Int, WrongFormat] { digitValue(char) }.isSuccess
|
|
60
58
|
|
|
61
59
|
/// Checks if the given character is an ASCII digit in base 16
|
|
62
|
-
///
|
|
63
|
-
def isHexDigit(char: Char): Bool = hexDigitValue(char).
|
|
60
|
+
/// Prefer using `hexDigitValue(c: Char)` to get the numeric value out.
|
|
61
|
+
def isHexDigit(char: Char): Bool = result[Int, WrongFormat] { hexDigitValue(char)}.isSuccess
|
|
64
62
|
|
|
65
63
|
/// Checks if the given character is an ASCII digit in base 10
|
|
66
|
-
///
|
|
67
|
-
def isDigit(char: Char, base: Int): Bool = digitValue(char, base).
|
|
64
|
+
/// Prefer using `digitValue(c: Char, base: Int)` to get the numeric value out.
|
|
65
|
+
def isDigit(char: Char, base: Int): Bool = result[Int, WrongFormat] { digitValue(char, base) }.isSuccess
|
|
68
66
|
|
|
69
67
|
/// Checks if a given character is a 7-bit ASCII character
|
|
70
68
|
def isASCII(c: Char): Bool = { c.toInt < 128 }
|
|
@@ -80,3 +78,100 @@ def isAlphanumeric(c: Char): Bool = isDigit(c) || isLower(c) || isUpper(c)
|
|
|
80
78
|
|
|
81
79
|
/// Checks if a given character is an ASCII alphabetic character
|
|
82
80
|
def isAlphabetic(c: Char): Bool = isLower(c) || isUpper(c)
|
|
81
|
+
|
|
82
|
+
// Characters
|
|
83
|
+
// ----------
|
|
84
|
+
//
|
|
85
|
+
// JS: Int (Unicode codepoints)
|
|
86
|
+
// Chez: ?
|
|
87
|
+
// LLVM: i64 representing utf-8 (varying length 1-4 bytes)
|
|
88
|
+
|
|
89
|
+
extern pure def toString(ch: Char): String =
|
|
90
|
+
js "String.fromCodePoint(${ch})"
|
|
91
|
+
chez "(string (integer->char ${ch}))"
|
|
92
|
+
llvm """
|
|
93
|
+
%z = call %Pos @c_bytearray_show_Char(%Int ${ch})
|
|
94
|
+
ret %Pos %z
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
// Since we currently represent Char by integers in all backends, we could reuse comparison
|
|
98
|
+
extern pure def toInt(ch: Char): Int =
|
|
99
|
+
js "${ch}"
|
|
100
|
+
chez "${ch}"
|
|
101
|
+
llvm "ret %Int ${ch}"
|
|
102
|
+
vm "string::toInt(Char)"
|
|
103
|
+
|
|
104
|
+
extern pure def toChar(codepoint: Int): Char =
|
|
105
|
+
js "${codepoint}"
|
|
106
|
+
chez "${codepoint}"
|
|
107
|
+
llvm "ret %Int ${codepoint}"
|
|
108
|
+
vm "string::toChar(Int)"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
extern pure def infixLt(x: Char, y: Char): Bool =
|
|
112
|
+
js "(${x} < ${y})"
|
|
113
|
+
chez "(< ${x} ${y})"
|
|
114
|
+
llvm """
|
|
115
|
+
%z = icmp slt %Int ${x}, ${y}
|
|
116
|
+
%fat_z = zext i1 %z to i64
|
|
117
|
+
%adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
|
|
118
|
+
ret %Pos %adt_boolean
|
|
119
|
+
"""
|
|
120
|
+
vm "string::infixLt(Char, Char)"
|
|
121
|
+
|
|
122
|
+
extern pure def infixLte(x: Char, y: Char): Bool =
|
|
123
|
+
js "(${x} <= ${y})"
|
|
124
|
+
chez "(<= ${x} ${y})"
|
|
125
|
+
llvm """
|
|
126
|
+
%z = icmp sle %Int ${x}, ${y}
|
|
127
|
+
%fat_z = zext i1 %z to i64
|
|
128
|
+
%adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
|
|
129
|
+
ret %Pos %adt_boolean
|
|
130
|
+
"""
|
|
131
|
+
vm "string::infixLte(Char, Char)"
|
|
132
|
+
|
|
133
|
+
extern pure def infixGt(x: Char, y: Char): Bool =
|
|
134
|
+
js "(${x} > ${y})"
|
|
135
|
+
chez "(> ${x} ${y})"
|
|
136
|
+
llvm """
|
|
137
|
+
%z = icmp sgt %Int ${x}, ${y}
|
|
138
|
+
%fat_z = zext i1 %z to i64
|
|
139
|
+
%adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
|
|
140
|
+
ret %Pos %adt_boolean
|
|
141
|
+
"""
|
|
142
|
+
vm "string::infixGt(Char, Char)"
|
|
143
|
+
|
|
144
|
+
extern pure def infixGte(x: Char, y: Char): Bool =
|
|
145
|
+
js "(${x} >= ${y})"
|
|
146
|
+
chez "(>= ${x} ${y})"
|
|
147
|
+
llvm """
|
|
148
|
+
%z = icmp sge %Int ${x}, ${y}
|
|
149
|
+
%fat_z = zext i1 %z to i64
|
|
150
|
+
%adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
|
|
151
|
+
ret %Pos %adt_boolean
|
|
152
|
+
"""
|
|
153
|
+
vm "string::infixGte(Char, Char)"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Determines the number of bytes needed by a codepoint
|
|
158
|
+
*
|
|
159
|
+
* Also see: https://en.wikipedia.org/wiki/UTF-8
|
|
160
|
+
*/
|
|
161
|
+
def utf8ByteCount(codepoint: Char): Int = codepoint match {
|
|
162
|
+
case c and c >= \u0000 and c <= \u007F => 1
|
|
163
|
+
case c and c >= \u0080 and c <= \u07FF => 2
|
|
164
|
+
case c and c >= \u0800 and c <= \uFFFF => 3
|
|
165
|
+
case c and c >= \u10000 and c <= \u10FFFF => 4
|
|
166
|
+
case c => panic("Not a valid code point")
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
def utf16UnitCount(codepoint: Char): Int = codepoint match {
|
|
170
|
+
case c and c >= \u0000 and c <= \uFFFF => 1
|
|
171
|
+
case c and c >= \u10000 and c <= \u10FFFF => 4
|
|
172
|
+
case c => panic("Not a valid code point")
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
extern pure def charWidth(c: Char): Int =
|
|
176
|
+
// JavaScript strings are UTF-16 where every unicode character after 0xffff takes two units
|
|
177
|
+
js "(${c} > 0xffff) ? 2 : 1"
|
|
@@ -42,11 +42,32 @@ extern def println(value: String): Unit =
|
|
|
42
42
|
js "$effekt.println(${value})"
|
|
43
43
|
chez "(println_impl ${value})"
|
|
44
44
|
llvm """
|
|
45
|
-
call void @
|
|
45
|
+
call void @c_io_println(%Pos ${value})
|
|
46
46
|
ret %Pos zeroinitializer ; Unit
|
|
47
47
|
"""
|
|
48
48
|
vm "effekt::println(String)"
|
|
49
49
|
|
|
50
|
+
extern jsNode """
|
|
51
|
+
$effekt.readln = function readln$impl(callback) {
|
|
52
|
+
const readline = require('node:readline').createInterface({
|
|
53
|
+
input: process.stdin,
|
|
54
|
+
output: process.stdout,
|
|
55
|
+
});
|
|
56
|
+
readline.question('', (answer) => {
|
|
57
|
+
readline.close();
|
|
58
|
+
callback(answer);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
extern async def readln(): String =
|
|
64
|
+
jsNode "$effekt.capture(callback => $effekt.readln(callback))"
|
|
65
|
+
llvm """
|
|
66
|
+
%result = call %Pos @c_io_readln()
|
|
67
|
+
tail call void @resume_Pos(%Stack %stack, %Pos %result)
|
|
68
|
+
ret void
|
|
69
|
+
"""
|
|
70
|
+
|
|
50
71
|
def println(value: Int): Unit = println(value.show)
|
|
51
72
|
def println(value: Unit): Unit = println(value.show)
|
|
52
73
|
def println(value: Double): Unit = println(value.show)
|
|
@@ -736,6 +757,9 @@ def repeat(n: Int) { action: () => Unit } = each(0, n) { n => action() }
|
|
|
736
757
|
|
|
737
758
|
def repeat(n: Int) { action: () {Control} => Unit } = each(0, n) { (n) {l} => action() {l} }
|
|
738
759
|
|
|
760
|
+
// NOTE: This is emitted by the close hole code action: do not remove unless you also adjust the code action
|
|
761
|
+
/// Scopes a local computation
|
|
762
|
+
def locally[R] { p: => R } : R = p()
|
|
739
763
|
|
|
740
764
|
// Splices
|
|
741
765
|
// =======
|
|
@@ -54,7 +54,7 @@ extern io def panic(msg: String): Nothing =
|
|
|
54
54
|
js "(function() { throw ${msg} })()"
|
|
55
55
|
chez "(raise ${msg})"
|
|
56
56
|
llvm """
|
|
57
|
-
call void @
|
|
57
|
+
call void @c_io_println(%Pos ${msg})
|
|
58
58
|
call void @exit(i32 1)
|
|
59
59
|
ret %Pos zeroinitializer ; Unit
|
|
60
60
|
"""
|
|
@@ -35,3 +35,9 @@ def toOption[A, E](r: Result[A, E]): Option[A] = r match {
|
|
|
35
35
|
case Success(a) => Some(a)
|
|
36
36
|
case Error(exc, msg) => None()
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
def isError[A, E](r: Result[A, E]): Bool =
|
|
40
|
+
if (r is Error(_, _)) true else false
|
|
41
|
+
|
|
42
|
+
def isSuccess[A, E](r: Result[A, E]): Bool =
|
|
43
|
+
if (r is Success(_)) true else false
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module scanner
|
|
2
2
|
|
|
3
|
-
import char
|
|
4
3
|
import stream
|
|
5
4
|
|
|
6
5
|
interface Scan[A] {
|
|
@@ -49,6 +48,15 @@ def readSome[A, B] { convert: A => Option[B] }: B / { Scan[A], stop } =
|
|
|
49
48
|
do stop()
|
|
50
49
|
}
|
|
51
50
|
|
|
51
|
+
/// like `readSome`, but for functions `A => B / Exception[WrongFormat]` such as `char::digitValue`
|
|
52
|
+
def tryRead[A, B] { convert: A => B / Exception[WrongFormat] }: B / { Scan[A], stop } = {
|
|
53
|
+
with on[WrongFormat].default { do stop() }
|
|
54
|
+
|
|
55
|
+
val t = convert(do peek())
|
|
56
|
+
do skip[A]()
|
|
57
|
+
return t
|
|
58
|
+
}
|
|
59
|
+
|
|
52
60
|
/// Reads until the predicate does not hold for the next token.
|
|
53
61
|
/// Emits the tokens read.
|
|
54
62
|
def readWhile[A] { predicate: A => Bool }: Unit / { Scan[A], emit[A] } =
|
|
@@ -78,7 +86,7 @@ def readString(string: String): Unit / { Scan[Char], stop } =
|
|
|
78
86
|
|
|
79
87
|
/// Check that the next character is a digit in base 10, and if so read and return it.
|
|
80
88
|
def readDigit(): Int / { Scan[Char], stop } =
|
|
81
|
-
|
|
89
|
+
tryRead[Char, Int]{ char => digitValue(char) }
|
|
82
90
|
|
|
83
91
|
/// Read a positive decimal number.
|
|
84
92
|
def readDecimal(): Int / Scan[Char] = {
|
|
@@ -91,7 +99,7 @@ def readDecimal(): Int / Scan[Char] = {
|
|
|
91
99
|
|
|
92
100
|
/// Check that the next character is a digit in base 16, and if so read and return it.
|
|
93
101
|
def readHexDigit(): Int / { Scan[Char], stop } =
|
|
94
|
-
|
|
102
|
+
tryRead[Char, Int]{ char => hexDigitValue(char) }
|
|
95
103
|
|
|
96
104
|
/// Read a hexadecimal number.
|
|
97
105
|
def readHexadecimal(): Int / Scan[Char] = {
|
|
@@ -173,10 +173,23 @@ def replicate[A](number: Int) { action: () => A }: Unit / emit[A] =
|
|
|
173
173
|
replicate(number - 1) {action}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/// Creates an infinite iterated stream given by an `initial` seed and a `step` function:
|
|
177
|
+
/// iterate(a){f} ~> a, f(a), f(f(a)), f(f(f(a))), ...
|
|
178
|
+
def iterate[A](initial: A) { step: A => A }: Unit / emit[A] = {
|
|
179
|
+
var current = initial
|
|
180
|
+
while (true) {
|
|
181
|
+
do emit(current)
|
|
182
|
+
current = step(current)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
176
185
|
|
|
177
|
-
|
|
186
|
+
|
|
187
|
+
def sum { stream: () => Unit / emit[Int] }: Int =
|
|
178
188
|
returning::sum[Unit]{stream}.second
|
|
179
189
|
|
|
190
|
+
def product { stream: () => Unit / emit[Int] }: Int =
|
|
191
|
+
returning::product[Unit]{stream}.second
|
|
192
|
+
|
|
180
193
|
def collectList[A] { stream: () => Unit / emit[A] }: List[A] =
|
|
181
194
|
returning::collectList[A, Unit]{stream}.second
|
|
182
195
|
|
|
@@ -409,6 +422,88 @@ def each(string: String): Unit / emit[Char] =
|
|
|
409
422
|
def collectString { stream: () => Unit / emit[Char] }: String =
|
|
410
423
|
returning::collectString[Unit]{stream}.second
|
|
411
424
|
|
|
425
|
+
namespace internal {
|
|
426
|
+
effect snapshot(): Unit
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/// Use cons to handle prod, but also emit to the outside.
|
|
430
|
+
/// The outside controls iteration, i.e., if cons aborts, the rest of prod will still be emitted.
|
|
431
|
+
///
|
|
432
|
+
/// Starts by executing cons. Will stop cons during execution if the outside stops consuming.
|
|
433
|
+
///
|
|
434
|
+
/// Example, printing all values consumed by the outside:
|
|
435
|
+
///
|
|
436
|
+
/// with teeing{ {s} => for{s}{ e => println(e) } }
|
|
437
|
+
/// prod() // some producer
|
|
438
|
+
///
|
|
439
|
+
/// Laws-ish (hopefully):
|
|
440
|
+
/// - hnd{ prd() } === hnd{ teeing{t}{ prd() } } for all hnd and t that calls its argument at most once (and has no other captures)
|
|
441
|
+
def teeing[A]{ cons: { => Unit / emit[A] } => Unit }{ prod: => Unit / emit[A] }: Unit / emit[A] = region r {
|
|
442
|
+
var consDone = false
|
|
443
|
+
var k in r = box { prod() } // what still needs to run of prod after cons exits
|
|
444
|
+
try {
|
|
445
|
+
cons{
|
|
446
|
+
try {
|
|
447
|
+
prod()
|
|
448
|
+
k = box { () }
|
|
449
|
+
} with emit[A] { v =>
|
|
450
|
+
do internal::snapshot() // remember that we need to continue here if cons aborts
|
|
451
|
+
if(not(consDone)) do emit(v)
|
|
452
|
+
outer.emit(v)
|
|
453
|
+
resume(())
|
|
454
|
+
}
|
|
455
|
+
if(consDone) do stop() // cons already exited, so skip continuing there
|
|
456
|
+
} // end cons
|
|
457
|
+
consDone = true
|
|
458
|
+
}
|
|
459
|
+
with stop { () => () }
|
|
460
|
+
with outer: emit[A]{ v => resume(do emit(v)) }
|
|
461
|
+
with internal::snapshot{ () =>
|
|
462
|
+
k = box { resume(()) }
|
|
463
|
+
resume(())
|
|
464
|
+
}
|
|
465
|
+
k()
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/// Binds a `teeing`-like function in the body, stopping the inner producer once all consumers in tees are done consuming.
|
|
469
|
+
///
|
|
470
|
+
/// Example of use, equivalent to `tee[A]{cns1}{cns2}{prd}`:
|
|
471
|
+
///
|
|
472
|
+
/// manyTee[A] { {tee} =>
|
|
473
|
+
/// with tee{cns1}
|
|
474
|
+
/// with tee{cns2}
|
|
475
|
+
/// prd()
|
|
476
|
+
/// }
|
|
477
|
+
///
|
|
478
|
+
def manyTee[A]{ body: { { { => Unit / emit[A] } => Unit }{ => Unit / emit[A] } => Unit / emit[A] } => Unit / emit[A] }: Unit = {
|
|
479
|
+
var running = 0
|
|
480
|
+
try {
|
|
481
|
+
body{ {hnd}{prod} =>
|
|
482
|
+
running = running + 1
|
|
483
|
+
teeing[A]{ {b} => hnd{b}; running = running - 1 }{prod}
|
|
484
|
+
}
|
|
485
|
+
} with emit[A] { _ =>
|
|
486
|
+
if(running > 0) resume(())
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/// Streams prod to both cons1 and cons2. Stops once both cons1 and cons2 stopped.
|
|
491
|
+
/// Only runs prod once, resuming at most once at each emit.
|
|
492
|
+
///
|
|
493
|
+
/// var sumRes = 0
|
|
494
|
+
/// var productRes = 0
|
|
495
|
+
/// tee{ s => sumRes = sum{s} }{ s => productRes = product{s} }{ range(0, 10) }
|
|
496
|
+
/// assertEquals(sum{ range(0, 10) }, sumRes)
|
|
497
|
+
/// assertEquals(product{ range(0, 10) }, productRes)
|
|
498
|
+
///
|
|
499
|
+
def tee[A]{ cons1: { => Unit / emit[A] } => Unit }{ cons2: { => Unit / emit[A] } => Unit }{ prod: => Unit / emit[A] }: Unit = {
|
|
500
|
+
manyTee[A]{ {tee} =>
|
|
501
|
+
with tee{cons1}
|
|
502
|
+
with tee{cons2}
|
|
503
|
+
prod()
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
412
507
|
namespace returning {
|
|
413
508
|
|
|
414
509
|
/// Canonical handler of push streams that performs `action` for every
|
|
@@ -461,7 +556,7 @@ def limit[A, R](number: Int) { stream: () => R / emit[A] }: R / { emit[A], stop
|
|
|
461
556
|
}
|
|
462
557
|
}
|
|
463
558
|
|
|
464
|
-
def sum[R] { stream
|
|
559
|
+
def sum[R] { stream: () => R / emit[Int] }: (R, Int) = {
|
|
465
560
|
var s = 0;
|
|
466
561
|
try {
|
|
467
562
|
(stream(), s)
|
|
@@ -471,6 +566,16 @@ def sum[R] { stream : () => R / emit[Int] }: (R, Int) = {
|
|
|
471
566
|
}
|
|
472
567
|
}
|
|
473
568
|
|
|
569
|
+
def product[R] { stream: () => R / emit[Int] }: (R, Int) = {
|
|
570
|
+
var s = 1;
|
|
571
|
+
try {
|
|
572
|
+
(stream(), s)
|
|
573
|
+
} with emit[Int] { v =>
|
|
574
|
+
s = s * v;
|
|
575
|
+
resume(())
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
474
579
|
def collectList[A, R] { stream: () => R / emit[A] }: (R, List[A]) =
|
|
475
580
|
try {
|
|
476
581
|
(stream(), Nil())
|
|
@@ -5,6 +5,7 @@ import option
|
|
|
5
5
|
import list
|
|
6
6
|
import exception
|
|
7
7
|
import result
|
|
8
|
+
import char
|
|
8
9
|
|
|
9
10
|
// TODO
|
|
10
11
|
// - [ ] handle unicode codepoints (that can span two indices) correctly
|
|
@@ -61,12 +62,22 @@ def endsWith(str: String, suffix: String): Bool =
|
|
|
61
62
|
/// TODO use a more efficient way of appending strings like a buffer
|
|
62
63
|
def repeat(str: String, n: Int): String = {
|
|
63
64
|
def go(n: Int, result: String): String = {
|
|
64
|
-
if (n
|
|
65
|
+
if (n <= 0) result
|
|
65
66
|
else go(n - 1, result ++ str)
|
|
66
67
|
}
|
|
67
68
|
go(n, "")
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
/// Left-pad the given string with spaces
|
|
72
|
+
def padLeft(str: String, n: Int): String = {
|
|
73
|
+
" ".repeat(n - str.length) ++ str
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Right-pad the given string with spaces
|
|
77
|
+
def padRight(str: String, n: Int): String = {
|
|
78
|
+
str ++ " ".repeat(n - str.length)
|
|
79
|
+
}
|
|
80
|
+
|
|
70
81
|
// TODO use .split() in JS
|
|
71
82
|
def split(str: String, sep: String): List[String] = {
|
|
72
83
|
val strLength = str.length
|
|
@@ -113,20 +124,18 @@ def toBool(s: String): Bool / Exception[WrongFormat] = s match {
|
|
|
113
124
|
|
|
114
125
|
// TODO optimize (right now this will be horribly slow (compared to the native JS version)
|
|
115
126
|
def toInt(str: String): Int / Exception[WrongFormat] = {
|
|
116
|
-
|
|
117
|
-
val zero = '0'.toInt
|
|
118
|
-
|
|
119
127
|
def go(index: Int, acc: Int): Int = {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
128
|
+
// wrong index means we're done parsing
|
|
129
|
+
with on[OutOfBounds].default { () => acc };
|
|
130
|
+
|
|
131
|
+
val c = str.charAt(index)
|
|
132
|
+
val d = char::digitValue(c)
|
|
133
|
+
go(index + 1, 10 * acc + d)
|
|
127
134
|
}
|
|
128
135
|
|
|
129
|
-
with
|
|
136
|
+
with on[OutOfBounds].default {
|
|
137
|
+
wrongFormat("Empty string is not a valid number")
|
|
138
|
+
}
|
|
130
139
|
|
|
131
140
|
str.charAt(0) match {
|
|
132
141
|
case '-' => 0 - go(1, 0)
|
|
@@ -136,38 +145,22 @@ def toInt(str: String): Int / Exception[WrongFormat] = {
|
|
|
136
145
|
|
|
137
146
|
// TODO optimize (right now this will be horribly slow (compared to the native JS version)
|
|
138
147
|
def toInt(str: String, base: Int): Int / Exception[WrongFormat] = {
|
|
139
|
-
|
|
140
|
-
if( base > 36 || base < 1 ) {
|
|
148
|
+
if (base > 36 || base < 1) {
|
|
141
149
|
wrongFormat("Invalid base: " ++ base.show)
|
|
142
150
|
}
|
|
143
151
|
|
|
144
|
-
val zero = '0'.toInt
|
|
145
|
-
val l_a = 'a'.toInt
|
|
146
|
-
val u_a = 'A'.toInt
|
|
147
|
-
|
|
148
|
-
def parseDigit(c: Char): Option[Int] = {
|
|
149
|
-
if( c >= '0' and c <= '9' and c.toInt - zero < base ) {
|
|
150
|
-
Some(c.toInt - zero)
|
|
151
|
-
} else if( c >= 'a' and c <= 'z' and c.toInt - l_a < base - 10 ) {
|
|
152
|
-
Some(c.toInt - l_a + 10)
|
|
153
|
-
} else if( c >= 'A' and c <= 'Z' and c.toInt - u_a < base - 10) {
|
|
154
|
-
Some(c.toInt - u_a + 10)
|
|
155
|
-
} else {
|
|
156
|
-
None()
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
152
|
def go(index: Int, acc: Int): Int = {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
153
|
+
// wrong index means we're done parsing
|
|
154
|
+
with on[OutOfBounds].default { () => acc };
|
|
155
|
+
|
|
156
|
+
val c = str.charAt(index)
|
|
157
|
+
val d = char::digitValue(c, base)
|
|
158
|
+
go(index + 1, base * acc + d)
|
|
168
159
|
}
|
|
169
160
|
|
|
170
|
-
with
|
|
161
|
+
with on[OutOfBounds].default {
|
|
162
|
+
wrongFormat("Empty string is not a valid number")
|
|
163
|
+
}
|
|
171
164
|
|
|
172
165
|
str.charAt(0) match {
|
|
173
166
|
case '-' => 0 - go(1, 0)
|
|
@@ -222,104 +215,6 @@ def lastIndexOf(str: String, sub: String, from: Int): Option[Int] = {
|
|
|
222
215
|
// extern pure def unsafeIndexOf(str: String, sub: String): Int =
|
|
223
216
|
// js "${str}.indexOf(${sub})"
|
|
224
217
|
|
|
225
|
-
|
|
226
|
-
// Characters
|
|
227
|
-
// ----------
|
|
228
|
-
//
|
|
229
|
-
// JS: Int (Unicode codepoints)
|
|
230
|
-
// Chez: ?
|
|
231
|
-
// LLVM: i64 representing utf-8 (varying length 1-4 bytes)
|
|
232
|
-
|
|
233
|
-
extern pure def toString(ch: Char): String =
|
|
234
|
-
js "String.fromCodePoint(${ch})"
|
|
235
|
-
chez "(string (integer->char ${ch}))"
|
|
236
|
-
llvm """
|
|
237
|
-
%z = call %Pos @c_bytearray_show_Char(%Int ${ch})
|
|
238
|
-
ret %Pos %z
|
|
239
|
-
"""
|
|
240
|
-
|
|
241
|
-
// Since we currently represent Char by integers in all backends, we could reuse comparison
|
|
242
|
-
extern pure def toInt(ch: Char): Int =
|
|
243
|
-
js "${ch}"
|
|
244
|
-
chez "${ch}"
|
|
245
|
-
llvm "ret %Int ${ch}"
|
|
246
|
-
vm "string::toInt(Char)"
|
|
247
|
-
|
|
248
|
-
extern pure def toChar(codepoint: Int): Char =
|
|
249
|
-
js "${codepoint}"
|
|
250
|
-
chez "${codepoint}"
|
|
251
|
-
llvm "ret %Int ${codepoint}"
|
|
252
|
-
vm "string::toChar(Int)"
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
extern pure def infixLt(x: Char, y: Char): Bool =
|
|
256
|
-
js "(${x} < ${y})"
|
|
257
|
-
chez "(< ${x} ${y})"
|
|
258
|
-
llvm """
|
|
259
|
-
%z = icmp slt %Int ${x}, ${y}
|
|
260
|
-
%fat_z = zext i1 %z to i64
|
|
261
|
-
%adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
|
|
262
|
-
ret %Pos %adt_boolean
|
|
263
|
-
"""
|
|
264
|
-
vm "string::infixLt(Char, Char)"
|
|
265
|
-
|
|
266
|
-
extern pure def infixLte(x: Char, y: Char): Bool =
|
|
267
|
-
js "(${x} <= ${y})"
|
|
268
|
-
chez "(<= ${x} ${y})"
|
|
269
|
-
llvm """
|
|
270
|
-
%z = icmp sle %Int ${x}, ${y}
|
|
271
|
-
%fat_z = zext i1 %z to i64
|
|
272
|
-
%adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
|
|
273
|
-
ret %Pos %adt_boolean
|
|
274
|
-
"""
|
|
275
|
-
vm "string::infixLte(Char, Char)"
|
|
276
|
-
|
|
277
|
-
extern pure def infixGt(x: Char, y: Char): Bool =
|
|
278
|
-
js "(${x} > ${y})"
|
|
279
|
-
chez "(> ${x} ${y})"
|
|
280
|
-
llvm """
|
|
281
|
-
%z = icmp sgt %Int ${x}, ${y}
|
|
282
|
-
%fat_z = zext i1 %z to i64
|
|
283
|
-
%adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
|
|
284
|
-
ret %Pos %adt_boolean
|
|
285
|
-
"""
|
|
286
|
-
vm "string::infixGt(Char, Char)"
|
|
287
|
-
|
|
288
|
-
extern pure def infixGte(x: Char, y: Char): Bool =
|
|
289
|
-
js "(${x} >= ${y})"
|
|
290
|
-
chez "(>= ${x} ${y})"
|
|
291
|
-
llvm """
|
|
292
|
-
%z = icmp sge %Int ${x}, ${y}
|
|
293
|
-
%fat_z = zext i1 %z to i64
|
|
294
|
-
%adt_boolean = insertvalue %Pos zeroinitializer, i64 %fat_z, 0
|
|
295
|
-
ret %Pos %adt_boolean
|
|
296
|
-
"""
|
|
297
|
-
vm "string::infixGte(Char, Char)"
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Determines the number of bytes needed by a codepoint
|
|
302
|
-
*
|
|
303
|
-
* Also see: https://en.wikipedia.org/wiki/UTF-8
|
|
304
|
-
*/
|
|
305
|
-
def utf8ByteCount(codepoint: Char): Int = codepoint match {
|
|
306
|
-
case c and c >= \u0000 and c <= \u007F => 1
|
|
307
|
-
case c and c >= \u0080 and c <= \u07FF => 2
|
|
308
|
-
case c and c >= \u0800 and c <= \uFFFF => 3
|
|
309
|
-
case c and c >= \u10000 and c <= \u10FFFF => 4
|
|
310
|
-
case c => panic("Not a valid code point")
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
def utf16UnitCount(codepoint: Char): Int = codepoint match {
|
|
314
|
-
case c and c >= \u0000 and c <= \uFFFF => 1
|
|
315
|
-
case c and c >= \u10000 and c <= \u10FFFF => 4
|
|
316
|
-
case c => panic("Not a valid code point")
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
extern pure def charWidth(c: Char): Int =
|
|
320
|
-
// JavaScript strings are UTF-16 where every unicode character after 0xffff takes two units
|
|
321
|
-
js "(${c} > 0xffff) ? 2 : 1"
|
|
322
|
-
|
|
323
218
|
def charAt(str: String, index: Int): Char / Exception[OutOfBounds] =
|
|
324
219
|
if (index < 0 || index >= length(str))
|
|
325
220
|
do raise(OutOfBounds(), "Index out of bounds: " ++ show(index) ++ " in string: '" ++ str ++ "'")
|