@effekt-lang/effekt 0.47.0 → 0.48.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/array.effekt +53 -0
- package/libraries/common/buffer.effekt +2 -2
- package/libraries/common/bytearray.effekt +55 -0
- package/libraries/common/char.effekt +87 -0
- package/libraries/common/effekt.effekt +3 -0
- package/libraries/common/io/error.effekt +11 -0
- package/libraries/common/io/filesystem.effekt +71 -9
- package/libraries/common/io/network.effekt +97 -65
- package/libraries/common/io.effekt +1 -2
- package/libraries/common/json.effekt +5 -5
- package/libraries/common/list.effekt +154 -18
- package/libraries/common/map.effekt +35 -1
- package/libraries/common/queue.effekt +1 -1
- package/libraries/common/random.effekt +1 -0
- package/libraries/common/ref.effekt +4 -0
- package/libraries/common/seq.effekt +1 -1
- package/libraries/common/set.effekt +24 -1
- package/libraries/common/stream.effekt +2 -360
- package/libraries/common/string.effekt +30 -0
- package/libraries/common/stringbuffer.effekt +1 -0
- package/libraries/llvm/io.c +281 -8
- package/libraries/llvm/main.c +1 -2
- package/libraries/llvm/rts.ll +14 -7
- package/libraries/llvm/types.c +3 -3
- package/licenses/eclipse public license, version 2.0 - epl-2.0.html +2 -2
- package/licenses/mit license - mit-license.html +46 -46
- package/licenses/the bsd license - bsd-license.html +48 -48
- package/licenses/the mit license - mit.html +46 -46
- package/package.json +1 -1
package/bin/effekt
CHANGED
|
Binary file
|
|
@@ -4,6 +4,7 @@ import effekt
|
|
|
4
4
|
import exception
|
|
5
5
|
import list
|
|
6
6
|
import option
|
|
7
|
+
import stream
|
|
7
8
|
|
|
8
9
|
/// A mutable 0-indexed fixed-sized array.
|
|
9
10
|
extern type Array[T]
|
|
@@ -641,3 +642,55 @@ def println(l: Array[Int]): Unit = println(show(l))
|
|
|
641
642
|
def println(l: Array[Double]): Unit = println(show(l))
|
|
642
643
|
def println(l: Array[Bool]): Unit = println(show(l))
|
|
643
644
|
def println(l: Array[String]): Unit = println(show(l))
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
// Streaming
|
|
648
|
+
// ---------
|
|
649
|
+
|
|
650
|
+
/// Turns an `array` into a producer of a push stream
|
|
651
|
+
/// by emitting each contained value from 0 to length - 1.
|
|
652
|
+
def each[T](array: Array[T]): Unit / emit[T] = {
|
|
653
|
+
val n = array.size
|
|
654
|
+
def go(i: Int): Unit = {
|
|
655
|
+
if (i < n) {
|
|
656
|
+
do emit(array.unsafeGet(i))
|
|
657
|
+
go(i + 1)
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
go(0)
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
def feed[T, R](array: Array[T]) { reader: () => R / read[T] }: R = {
|
|
664
|
+
var i = 0
|
|
665
|
+
try {
|
|
666
|
+
reader()
|
|
667
|
+
} with read[T] {
|
|
668
|
+
resume {
|
|
669
|
+
if (i < array.size) {
|
|
670
|
+
val c = i
|
|
671
|
+
i = c + 1
|
|
672
|
+
array.unsafeGet(c)
|
|
673
|
+
} else {
|
|
674
|
+
do stop()
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
def collect[A] { stream: () => Unit / emit[A] }: Array[A] =
|
|
681
|
+
returning::collect[A, Unit]{stream}.second
|
|
682
|
+
|
|
683
|
+
namespace returning {
|
|
684
|
+
def collect[A, R] { stream: () => R / emit[A] }: (R, Array[A]) = {
|
|
685
|
+
var i = 0
|
|
686
|
+
var a = allocate(1)
|
|
687
|
+
try {
|
|
688
|
+
(stream(), a.resize(i))
|
|
689
|
+
} with emit[A] { (v) =>
|
|
690
|
+
if (i >= a.size) { a = a.resize(2 * a.size) }
|
|
691
|
+
a.unsafeSet(i, v)
|
|
692
|
+
i = i + 1
|
|
693
|
+
resume(())
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
@@ -62,11 +62,11 @@ def arrayBuffer[T](initialCapacity: Int): Buffer[T] at global = {
|
|
|
62
62
|
tail.set(mod(tail.get + 1, initialCapacity))
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
buffer
|
|
65
|
+
box buffer
|
|
66
66
|
}
|
|
67
67
|
def refBuffer[T](): Buffer[T] at global = {
|
|
68
68
|
val content = ref(None())
|
|
69
|
-
new Buffer[T] {
|
|
69
|
+
box new Buffer[T] {
|
|
70
70
|
def capacity() = if (content.get.isEmpty) 1 else 0
|
|
71
71
|
def full?() = content.get.isDefined
|
|
72
72
|
def empty?() = isEmpty(content.get)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
module bytearray
|
|
2
2
|
|
|
3
|
+
import effekt
|
|
4
|
+
import stream
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* A memory managed, mutable, fixed-length array of bytes.
|
|
5
8
|
*
|
|
@@ -200,3 +203,55 @@ def compareStringBytes(left: String, right: String): Ordering = {
|
|
|
200
203
|
val r = right.fromString
|
|
201
204
|
compareByteArray(l, r)
|
|
202
205
|
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
// Streaming
|
|
209
|
+
// ---------
|
|
210
|
+
|
|
211
|
+
/// Turns `bytes` into a producer of a push stream
|
|
212
|
+
/// by emitting each contained value from 0 to length - 1.
|
|
213
|
+
def each(bytes: ByteArray): Unit / emit[Byte] = {
|
|
214
|
+
val n = bytes.size
|
|
215
|
+
def go(i: Int): Unit = {
|
|
216
|
+
if (i < n) {
|
|
217
|
+
do emit(bytes.unsafeGet(i))
|
|
218
|
+
go(i + 1)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
go(0)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
def feed[R](bytes: ByteArray) { reader: () => R / read[Byte] }: R = {
|
|
225
|
+
var i = 0
|
|
226
|
+
try {
|
|
227
|
+
reader()
|
|
228
|
+
} with read[Byte] {
|
|
229
|
+
resume {
|
|
230
|
+
if (i < bytes.size) {
|
|
231
|
+
val c = i
|
|
232
|
+
i = c + 1
|
|
233
|
+
bytes.unsafeGet(c)
|
|
234
|
+
} else {
|
|
235
|
+
do stop()
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
def collect { stream: () => Unit / emit[Byte] }: ByteArray =
|
|
242
|
+
returning::collect[Unit]{stream}.second
|
|
243
|
+
|
|
244
|
+
namespace returning {
|
|
245
|
+
def collect[R] { stream: () => R / emit[Byte] }: (R, ByteArray) = {
|
|
246
|
+
var i = 0
|
|
247
|
+
var a = allocate(1)
|
|
248
|
+
try {
|
|
249
|
+
(stream(), a.resize(i))
|
|
250
|
+
} with emit[Byte] { (v) =>
|
|
251
|
+
if (i >= a.size) { a = a.resize(2 * a.size) }
|
|
252
|
+
a.unsafeSet(i, v)
|
|
253
|
+
i = i + 1
|
|
254
|
+
resume(())
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -5,6 +5,7 @@ import effekt
|
|
|
5
5
|
import exception
|
|
6
6
|
import option
|
|
7
7
|
import result
|
|
8
|
+
import stream
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
/// Checks if the given character is an ASCII whitespace
|
|
@@ -175,3 +176,89 @@ def utf16UnitCount(codepoint: Char): Int = codepoint match {
|
|
|
175
176
|
extern def charWidth(c: Char) at {}: Int =
|
|
176
177
|
// JavaScript strings are UTF-16 where every unicode character after 0xffff takes two units
|
|
177
178
|
js "(${c} > 0xffff) ? 2 : 1"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def writeLine { body: () => Unit / emit[Char] }: Unit / emit[Char] =
|
|
182
|
+
returning::writeLine[Unit]{body}
|
|
183
|
+
|
|
184
|
+
def readLine { body: () => Unit / read[Char] }: Unit / {read[Char], stop} =
|
|
185
|
+
returning::readLine[Unit]{body}
|
|
186
|
+
|
|
187
|
+
namespace returning {
|
|
188
|
+
def writeLine[R] { body: () => R / emit[Char] }: R / emit[Char] = {
|
|
189
|
+
val result = body()
|
|
190
|
+
do emit('\n')
|
|
191
|
+
return result
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
def readLine[R] { body: () => R / read[Char] }: R / {read[Char], stop} = {
|
|
195
|
+
var stopped = false
|
|
196
|
+
try {
|
|
197
|
+
body()
|
|
198
|
+
} with read[Char] {
|
|
199
|
+
if(stopped){
|
|
200
|
+
resume { do stop() }
|
|
201
|
+
} else {
|
|
202
|
+
do read[Char] match {
|
|
203
|
+
case '\n' => stopped = true; resume { do stop() }
|
|
204
|
+
case char => resume { return char }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
def decodeChar(): Char / {read[Byte], stop} = {
|
|
212
|
+
val b = do read().toInt
|
|
213
|
+
if (b < 128) {
|
|
214
|
+
b.toChar
|
|
215
|
+
} else if (b < 224) {
|
|
216
|
+
val part1 = bitwiseShl(bitwiseAnd(b, 31), 6)
|
|
217
|
+
val part2 = bitwiseAnd(do read().toInt, 63)
|
|
218
|
+
bitwiseOr(part1, part2).toChar
|
|
219
|
+
} else if (b < 240) {
|
|
220
|
+
val part1 = bitwiseShl(bitwiseAnd(b, 15), 12)
|
|
221
|
+
val part2 = bitwiseShl(bitwiseAnd(do read().toInt, 63), 6)
|
|
222
|
+
val part3 = bitwiseAnd(do read().toInt, 63)
|
|
223
|
+
bitwiseOr(bitwiseOr(part1, part2), part3).toChar
|
|
224
|
+
} else {
|
|
225
|
+
val part1 = bitwiseShl(bitwiseAnd(b, 7), 18)
|
|
226
|
+
val part2 = bitwiseShl(bitwiseAnd(do read().toInt, 63), 12)
|
|
227
|
+
val part3 = bitwiseShl(bitwiseAnd(do read().toInt, 63), 6)
|
|
228
|
+
val part4 = bitwiseAnd(do read().toInt, 63)
|
|
229
|
+
bitwiseOr(bitwiseOr(bitwiseOr(part1, part2), part3), part4).toChar
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
def encodeChar(char: Char): Unit / emit[Byte] = {
|
|
234
|
+
val code = char.toInt
|
|
235
|
+
if (code < 128) {
|
|
236
|
+
do emit(code.toByte)
|
|
237
|
+
} else if (code < 2048) {
|
|
238
|
+
do emit(bitwiseOr(192, bitwiseShr(code, 6)).toByte)
|
|
239
|
+
do emit(bitwiseOr(128, bitwiseAnd(code, 63)).toByte)
|
|
240
|
+
} else if (code < 65536) {
|
|
241
|
+
do emit(bitwiseOr(224, bitwiseShr(code, 12)).toByte)
|
|
242
|
+
do emit(bitwiseOr(128, bitwiseAnd(bitwiseShr(code, 6), 63)).toByte)
|
|
243
|
+
do emit(bitwiseOr(128, bitwiseAnd(code, 63)).toByte)
|
|
244
|
+
} else {
|
|
245
|
+
do emit(bitwiseOr(240, bitwiseShr(code, 18)).toByte)
|
|
246
|
+
do emit(bitwiseOr(128, bitwiseAnd(bitwiseShr(code, 12), 63)).toByte)
|
|
247
|
+
do emit(bitwiseOr(128, bitwiseAnd(bitwiseShr(code, 6), 63)).toByte)
|
|
248
|
+
do emit(bitwiseOr(128, bitwiseAnd(code, 63)).toByte)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
def decodeUTF8[R] { reader: () => R / read[Char] }: R / read[Byte] =
|
|
253
|
+
try {
|
|
254
|
+
reader()
|
|
255
|
+
} with read[Char] {
|
|
256
|
+
resume { decodeChar() }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
def encodeUTF8[R] { stream: () => R / emit[Char] }: R / emit[Byte] =
|
|
260
|
+
try {
|
|
261
|
+
stream()
|
|
262
|
+
} with emit[Char] { char =>
|
|
263
|
+
resume(encodeChar(char))
|
|
264
|
+
}
|
|
@@ -464,3 +464,14 @@ namespace internal {
|
|
|
464
464
|
"""
|
|
465
465
|
|
|
466
466
|
}
|
|
467
|
+
|
|
468
|
+
namespace internal {
|
|
469
|
+
def checkResult(result: Int): Int / Exception[IOError] =
|
|
470
|
+
if (result < 0) {
|
|
471
|
+
val ioError = fromNumber(internal::errorNumber(result));
|
|
472
|
+
do raise[IOError](ioError, message(ioError))
|
|
473
|
+
} else {
|
|
474
|
+
result
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
@@ -71,6 +71,77 @@ def appendFile(path: String, contents: String): Unit / Exception[IOError] = {
|
|
|
71
71
|
go()
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
def writeFileUTF8[R](path: String) { stream: () => R / emit[Char] }: R / Exception[IOError] =
|
|
75
|
+
writeFile(path) { encodeUTF8 { stream() } }
|
|
76
|
+
|
|
77
|
+
def readFileUTF8[R](path: String) { reader: () => R / read[Char] }: R / Exception[IOError] =
|
|
78
|
+
readFile(path) { decodeUTF8 { reader() } }
|
|
79
|
+
|
|
80
|
+
def writeFile[R](path: String) { stream: () => R / emit[Byte] }: R / Exception[IOError] = {
|
|
81
|
+
|
|
82
|
+
val file = openForWriting(path);
|
|
83
|
+
with on[IOError].finalize { close(file) }
|
|
84
|
+
|
|
85
|
+
val chunkSize = 1048576 // 1MB
|
|
86
|
+
val buffer = bytearray::allocate(chunkSize)
|
|
87
|
+
var offset = 0
|
|
88
|
+
|
|
89
|
+
def push(i: Int, n: Int): Unit = {
|
|
90
|
+
val r = write(file, buffer, i, n, -1)
|
|
91
|
+
if (r < n) {
|
|
92
|
+
push(i + r, n - r)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
val r = stream()
|
|
98
|
+
push(0, offset)
|
|
99
|
+
return r
|
|
100
|
+
} with emit[Byte] { (byte) =>
|
|
101
|
+
if (offset >= buffer.size) {
|
|
102
|
+
push(0, buffer.size)
|
|
103
|
+
offset = 0
|
|
104
|
+
}
|
|
105
|
+
buffer.unsafeSet(offset, byte)
|
|
106
|
+
offset = offset + 1
|
|
107
|
+
resume(())
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def readFile[R](path: String) { reader: () => R / read[Byte] }: R / Exception[IOError] = {
|
|
112
|
+
|
|
113
|
+
val file = openForReading(path);
|
|
114
|
+
with on[IOError].finalize { close(file) }
|
|
115
|
+
|
|
116
|
+
val chunkSize = 1048576 // 1MB
|
|
117
|
+
val buffer = bytearray::allocate(chunkSize)
|
|
118
|
+
var offset = 0
|
|
119
|
+
var length = 0
|
|
120
|
+
|
|
121
|
+
def pull(): Unit / stop = {
|
|
122
|
+
read(file, buffer, 0, chunkSize, -1) match {
|
|
123
|
+
case 0 =>
|
|
124
|
+
do stop()
|
|
125
|
+
case n =>
|
|
126
|
+
length = n
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
reader()
|
|
132
|
+
} with read[Byte] {
|
|
133
|
+
resume {
|
|
134
|
+
if (offset >= length) {
|
|
135
|
+
pull()
|
|
136
|
+
offset = 0
|
|
137
|
+
}
|
|
138
|
+
val byte = buffer.unsafeGet(offset)
|
|
139
|
+
offset = offset + 1
|
|
140
|
+
return byte
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
74
145
|
/// An abstract interface applications can program against.
|
|
75
146
|
///
|
|
76
147
|
/// Can be interpreted with the `filesystem` handler, or virtualized etc.
|
|
@@ -192,7 +263,6 @@ namespace internal {
|
|
|
192
263
|
ret void
|
|
193
264
|
"""
|
|
194
265
|
|
|
195
|
-
/// The buffer must be kept alive by using it after the call
|
|
196
266
|
extern def read(file: Int, buffer: ByteArray, offset: Int, size: Int, position: Int) at async: Int =
|
|
197
267
|
jsNode "$effekt.capture(callback => read(${file}, ${buffer}, ${offset}, ${size}, ${position}, callback))"
|
|
198
268
|
llvm """
|
|
@@ -200,7 +270,6 @@ namespace internal {
|
|
|
200
270
|
ret void
|
|
201
271
|
"""
|
|
202
272
|
|
|
203
|
-
/// The buffer must be kept alive by using it after the call
|
|
204
273
|
extern def write(file: Int, buffer: ByteArray, offset: Int, size: Int, position: Int) at async: Int =
|
|
205
274
|
jsNode "$effekt.capture(callback => write(${file}, ${buffer}, ${offset}, ${size}, ${position}, callback))"
|
|
206
275
|
llvm """
|
|
@@ -222,13 +291,6 @@ namespace internal {
|
|
|
222
291
|
ret void
|
|
223
292
|
"""
|
|
224
293
|
|
|
225
|
-
def checkResult(result: Int): Int / Exception[IOError] =
|
|
226
|
-
if (result < 0) {
|
|
227
|
-
val ioError = fromNumber(internal::errorNumber(result));
|
|
228
|
-
do raise[IOError](ioError, message(ioError))
|
|
229
|
-
} else {
|
|
230
|
-
result
|
|
231
|
-
}
|
|
232
294
|
}
|
|
233
295
|
|
|
234
296
|
namespace examples {
|
|
@@ -1,84 +1,116 @@
|
|
|
1
1
|
module io/network
|
|
2
2
|
|
|
3
|
+
import stream
|
|
3
4
|
import bytearray
|
|
4
|
-
import io
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
extern jsNode """
|
|
8
|
-
const net = require('node:net');
|
|
6
|
+
import io/error
|
|
9
7
|
|
|
10
|
-
function listen(server, port, host, listener) {
|
|
11
|
-
server.listen(port, host);
|
|
12
|
-
server.on('connection', listener);
|
|
13
|
-
}
|
|
14
|
-
"""
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
extern def server() at io: JSServer =
|
|
19
|
-
jsNode "net.createServer()"
|
|
20
|
-
extern def listen(server: JSServer, port: Int, host: String, listener: JSSocket => Unit at {io, async, global}) at io: Unit =
|
|
21
|
-
jsNode "listen(${server}, ${port}, ${host}, (socket) => $effekt.runToplevel((ks, k) => (${listener})(socket, ks, k)))"
|
|
9
|
+
/// A TCP handle. Should not be inspected.
|
|
10
|
+
type Connection = Int
|
|
22
11
|
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
/// Reads data from a TCP connection into a buffer at the given offset.
|
|
13
|
+
def read(handle: Connection, buffer: ByteArray, offset: Int, size: Int): Int / Exception[IOError] =
|
|
14
|
+
internal::checkResult(internal::read(handle, buffer, offset, size))
|
|
25
15
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
extern def end(socket: JSSocket) at async: Unit =
|
|
30
|
-
jsNode "$effekt.capture(k => ${socket}.end(k))"
|
|
31
|
-
}
|
|
16
|
+
/// Writes data from a buffer at the given offset to a TCP connection.
|
|
17
|
+
def write(handle: Connection, buffer: ByteArray, offset: Int, size: Int): Int / Exception[IOError] =
|
|
18
|
+
internal::checkResult(internal::write(handle, buffer, offset, size))
|
|
32
19
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def end(): Unit
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
def server(host: String, port: Int, handler: () => Unit / Socket at {io, async, global}): Unit = {
|
|
40
|
-
val server = js::server();
|
|
41
|
-
js::listen(server, port, host, box { socket =>
|
|
42
|
-
println("New connection")
|
|
43
|
-
spawn(box {
|
|
44
|
-
try handler()
|
|
45
|
-
with Socket {
|
|
46
|
-
def send(message) =
|
|
47
|
-
resume(js::send(socket, message))
|
|
48
|
-
def receive() =
|
|
49
|
-
resume(js::receive(socket))
|
|
50
|
-
def end() =
|
|
51
|
-
resume(js::end(socket))
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
}
|
|
20
|
+
/// Establishes a TCP connection to the specified host and port.
|
|
21
|
+
def connect(host: String, port: Int): Connection / Exception[IOError] =
|
|
22
|
+
internal::checkResult(internal::connect(host, port))
|
|
56
23
|
|
|
24
|
+
/// Closes a TCP connection and releases associated resources.
|
|
25
|
+
def close(handle: Connection): Unit =
|
|
26
|
+
internal::close(handle)
|
|
57
27
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
val request = do receive().toString;
|
|
28
|
+
/// A TCP listener. Should not be inspected.
|
|
29
|
+
type Listener = Int
|
|
61
30
|
|
|
62
|
-
|
|
31
|
+
/// Creates a TCP listener bound to the specified host and port.
|
|
32
|
+
def bind(host: String, port: Int): Listener / Exception[IOError] =
|
|
33
|
+
internal::checkResult(internal::bind(host, port))
|
|
63
34
|
|
|
64
|
-
|
|
65
|
-
|
|
35
|
+
/// Starts listening for incoming connections and handles them with the provided handler function.
|
|
36
|
+
/// Runs until `shutdown` is called on this `Listener`.
|
|
37
|
+
def listen(listener: Listener, handler: Connection => Unit at {io, async, global}): Unit / Exception[IOError] = {
|
|
38
|
+
internal::checkResult(internal::listen(listener, handler)); ()
|
|
39
|
+
}
|
|
66
40
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
41
|
+
/// Stops a TCP listener and releases associated resources.
|
|
42
|
+
def shutdown(listener: Listener): Unit =
|
|
43
|
+
internal::shutdown(listener)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def streaming[R](connection: Connection) { program: () => R / {read[ByteArray], emit[ByteArray]} }: R / Exception[IOError] =
|
|
47
|
+
try {
|
|
48
|
+
program()
|
|
49
|
+
} with emit[ByteArray] { bytes =>
|
|
50
|
+
write(connection, bytes, 0, bytes.size)
|
|
51
|
+
resume(())
|
|
52
|
+
} with read[ByteArray] { () =>
|
|
53
|
+
val bytes = bytearray::allocate(4096)
|
|
54
|
+
read(connection, bytes, 0, bytes.size) match {
|
|
55
|
+
case r and r == bytes.size => resume { return bytes }
|
|
56
|
+
case r => resume { bytes.resize(r) }
|
|
71
57
|
}
|
|
72
|
-
do end()
|
|
73
58
|
}
|
|
74
59
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
60
|
+
namespace internal {
|
|
61
|
+
|
|
62
|
+
extern llvm """
|
|
63
|
+
declare void @c_tcp_connect(%Pos, %Int, %Stack)
|
|
64
|
+
declare void @c_tcp_read(%Int, %Pos, %Int, %Int, %Stack)
|
|
65
|
+
declare void @c_tcp_write(%Int, %Pos, %Int, %Int, %Stack)
|
|
66
|
+
declare void @c_tcp_close(%Int, %Stack)
|
|
67
|
+
declare void @c_tcp_bind(%Pos, %Int, %Int, %Stack)
|
|
68
|
+
declare void @c_tcp_listen(%Int, %Pos, %Stack)
|
|
69
|
+
declare void @c_tcp_shutdown(%Int, %Stack)
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
extern def connect(host: String, port: Int) at async: Int =
|
|
73
|
+
llvm """
|
|
74
|
+
call void @c_tcp_connect(%Pos ${host}, %Int ${port}, %Stack %stack)
|
|
75
|
+
ret void
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
extern def read(handle: Int, buffer: ByteArray, offset: Int, size: Int) at async: Int =
|
|
79
|
+
llvm """
|
|
80
|
+
call void @c_tcp_read(%Int ${handle}, %Pos ${buffer}, %Int ${offset}, %Int ${size}, %Stack %stack)
|
|
81
|
+
ret void
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
extern def write(handle: Int, buffer: ByteArray, offset: Int, size: Int) at async: Int =
|
|
85
|
+
llvm """
|
|
86
|
+
call void @c_tcp_write(%Int ${handle}, %Pos ${buffer}, %Int ${offset}, %Int ${size}, %Stack %stack)
|
|
87
|
+
ret void
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
extern def close(handle: Int) at async: Unit =
|
|
91
|
+
llvm """
|
|
92
|
+
call void @c_tcp_close(%Int ${handle}, %Stack %stack)
|
|
93
|
+
ret void
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
extern def bind(host: String, port: Int) at io: Int =
|
|
97
|
+
llvm """
|
|
98
|
+
%result = call %Int @c_tcp_bind(%Pos ${host}, %Int ${port})
|
|
99
|
+
ret %Int %result
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
extern def listen(listener: Int, handler: Int => Unit at {io, async, global}) at async: Int =
|
|
103
|
+
llvm """
|
|
104
|
+
call void @c_tcp_listen(%Int ${listener}, %Pos ${handler}, %Stack %stack)
|
|
105
|
+
ret void
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
extern def shutdown(handle: Int) at async: Unit =
|
|
109
|
+
llvm """
|
|
110
|
+
call void @c_tcp_shutdown(%Int ${handle}, %Stack %stack)
|
|
111
|
+
ret void
|
|
112
|
+
"""
|
|
79
113
|
|
|
80
|
-
server("localhost", port, box {
|
|
81
|
-
helloWorldApp()
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
114
|
}
|
|
115
|
+
|
|
116
|
+
|
|
@@ -16,8 +16,7 @@ extern def spawn(task: Task[Unit]) at async: Unit =
|
|
|
16
16
|
js "$effekt.capture(k => { setTimeout(() => k($effekt.unit), 0); return $effekt.run(${task}) })"
|
|
17
17
|
llvm """
|
|
18
18
|
call void @c_yield(%Stack %stack)
|
|
19
|
-
|
|
20
|
-
call void @run(%Neg %unboxed)
|
|
19
|
+
call void @run(%Pos ${task})
|
|
21
20
|
ret void
|
|
22
21
|
"""
|
|
23
22
|
|
|
@@ -165,7 +165,7 @@ def decodeJson(): Unit / {Scan[Char], JsonBuilder, Exception[WrongFormat]} = {
|
|
|
165
165
|
case 'f' => expectString("false"); do bool(false)
|
|
166
166
|
case '-' => do number(readDouble())
|
|
167
167
|
case d and d.isDigit => do number(readDouble())
|
|
168
|
-
case '"' => do string(
|
|
168
|
+
case '"' => do string(collect { readQuotedString() })
|
|
169
169
|
case '{' => do dict{ decodeJsonObject() }
|
|
170
170
|
case '[' => do list{ decodeJsonList() }
|
|
171
171
|
case _ => println("Unexpected " ++ c.toString); <>
|
|
@@ -179,7 +179,7 @@ def decodeJsonObject(): Unit / {Scan[Char], JsonObjectBuilder, Exception[WrongFo
|
|
|
179
179
|
skipWhitespace()
|
|
180
180
|
while(do peek[Char]() is c and (c != '}')) {
|
|
181
181
|
if (not(first)) { expectString(",") }
|
|
182
|
-
val k
|
|
182
|
+
val k = string::collect { readQuotedString() }
|
|
183
183
|
skipWhitespace()
|
|
184
184
|
expectString(":")
|
|
185
185
|
do field(k){
|
|
@@ -277,7 +277,7 @@ def build[R](){ body: => R / JsonBuilder }: (R, JsonValue) = {
|
|
|
277
277
|
}
|
|
278
278
|
(x, r)
|
|
279
279
|
}
|
|
280
|
-
def buildList[R](){ body: => R / JsonBuilder }: (R, List[JsonValue]) = returning::
|
|
280
|
+
def buildList[R](){ body: => R / JsonBuilder }: (R, List[JsonValue]) = list::returning::collect[JsonValue, R] {
|
|
281
281
|
try body() with JsonBuilder {
|
|
282
282
|
def number(n) = { do emit(Number(n)); resume(()) }
|
|
283
283
|
def bool(b) = { do emit(Bool(b)); resume(()) }
|
|
@@ -295,7 +295,7 @@ def buildList[R](){ body: => R / JsonBuilder }: (R, List[JsonValue]) = returning
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
|
-
def buildDict[R](){ body: => R / JsonObjectBuilder }: (R, List[(String, JsonValue)]) = returning::
|
|
298
|
+
def buildDict[R](){ body: => R / JsonObjectBuilder }: (R, List[(String, JsonValue)]) = list::returning::collect[(String, JsonValue), R] {
|
|
299
299
|
try body() with JsonObjectBuilder {
|
|
300
300
|
def field(k) = resume { {v} =>
|
|
301
301
|
val x = build{v}
|
|
@@ -315,7 +315,7 @@ namespace test {
|
|
|
315
315
|
// Read quoted string
|
|
316
316
|
feed("\"\ta\n\ra\"") {
|
|
317
317
|
with scanner[Char]
|
|
318
|
-
println(
|
|
318
|
+
println(string::collect { readQuotedString() })
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
println("")
|