@effekt-lang/effekt 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -18
- package/bin/effekt +0 -0
- package/libraries/common/args.effekt +0 -27
- package/libraries/common/array.effekt +11 -16
- package/libraries/common/bench.effekt +32 -6
- package/libraries/common/effekt.effekt +30 -69
- package/libraries/common/exception.effekt +0 -1
- package/libraries/common/io/console.effekt +32 -36
- package/libraries/common/io/error.effekt +111 -2
- package/libraries/common/io/filesystem.effekt +274 -0
- package/libraries/common/io/network.effekt +30 -54
- package/libraries/common/io/time.effekt +9 -12
- package/libraries/common/io.effekt +54 -124
- package/libraries/common/list.effekt +18 -0
- package/libraries/common/process.effekt +0 -1
- package/libraries/common/ref.effekt +4 -7
- package/libraries/common/string/tty.effekt +66 -0
- package/libraries/common/string.effekt +0 -34
- package/libraries/common/test.effekt +96 -34
- package/libraries/js/unsafe/cont.effekt +1 -1
- package/libraries/llvm/io.c +258 -378
- package/libraries/llvm/main.c +3 -0
- package/libraries/llvm/rts.ll +57 -38
- package/libraries/llvm/types.c +10 -1
- package/licenses/eclipse public license, version 2.0 - epl-2.0.html +4 -4
- package/licenses/mit license - mit-license.html +19 -19
- package/licenses/the bsd license - bsd-license.html +19 -19
- package/licenses/the mit license - mit.html +20 -20
- package/package.json +1 -1
- package/libraries/chez/lift/effekt.ss +0 -118
- package/libraries/common/io/files.effekt +0 -282
- package/libraries/js/io.js +0 -143
- package/libraries/llvm/io.ll +0 -19
- package/libraries/ml/effekt.sml +0 -65
- package/libraries/ml/internal/mllist.effekt +0 -9
- package/libraries/ml/internal/mloption.effekt +0 -18
- package/libraries/ml/random.effekt +0 -85
- package/libraries/ml/text/regex.effekt +0 -102
|
@@ -1,53 +1,49 @@
|
|
|
1
1
|
module io/console
|
|
2
2
|
|
|
3
3
|
import io
|
|
4
|
-
import io/time // just for testing
|
|
5
|
-
|
|
6
|
-
extern js """
|
|
7
|
-
const readline = require('node:readline');
|
|
8
|
-
"""
|
|
9
4
|
|
|
10
5
|
interface Console {
|
|
11
6
|
def readLine(): String
|
|
12
7
|
def writeLine(content: String): Unit
|
|
13
8
|
}
|
|
14
9
|
|
|
15
|
-
namespace js {
|
|
16
|
-
extern type JSConsole
|
|
17
|
-
extern {io} def newConsole(): JSConsole =
|
|
18
|
-
js """readline.createInterface({
|
|
19
|
-
input: process.stdin,
|
|
20
|
-
output: process.stdout,
|
|
21
|
-
})"""
|
|
22
|
-
extern {io} def close(console: JSConsole): Unit =
|
|
23
|
-
js "${console}.close()"
|
|
24
|
-
|
|
25
|
-
extern {io} def readline(console: JSConsole, k: String => Unit at {io, global}): Unit =
|
|
26
|
-
js "${console}.question('', input => (${k})(input).run())"
|
|
27
|
-
}
|
|
28
|
-
|
|
29
10
|
/**
|
|
30
11
|
* Handler for the console effect, using the Console available in js
|
|
31
12
|
*/
|
|
32
|
-
def console[R] {
|
|
13
|
+
def console[R] { program: () => R / Console }: R = {
|
|
33
14
|
val c = js::newConsole()
|
|
34
|
-
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def writeLine(
|
|
15
|
+
try {
|
|
16
|
+
val r = program()
|
|
17
|
+
js::close(c)
|
|
18
|
+
return r
|
|
19
|
+
} with Console {
|
|
20
|
+
def readLine() = resume(js::readLine(c))
|
|
21
|
+
def writeLine(message) = resume(js::writeLine(c, message))
|
|
41
22
|
}
|
|
42
23
|
}
|
|
43
24
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
extern
|
|
47
|
-
|
|
25
|
+
namespace js {
|
|
26
|
+
|
|
27
|
+
extern jsNode """
|
|
28
|
+
const readline = require('node:readline');
|
|
29
|
+
"""
|
|
48
30
|
|
|
49
|
-
|
|
50
|
-
|
|
31
|
+
extern type JSConsole
|
|
32
|
+
|
|
33
|
+
extern io def newConsole(): JSConsole =
|
|
34
|
+
jsNode """readline.createInterface({
|
|
35
|
+
input: process.stdin,
|
|
36
|
+
output: process.stdout,
|
|
37
|
+
})"""
|
|
38
|
+
|
|
39
|
+
extern io def close(console: JSConsole): Unit =
|
|
40
|
+
jsNode "${console}.close()"
|
|
41
|
+
|
|
42
|
+
extern async def readLine(console: JSConsole): String =
|
|
43
|
+
jsNode "$effekt.callcc(callback => ${console}.once('line', callback))"
|
|
44
|
+
|
|
45
|
+
extern io def writeLine(console: JSConsole, message: String): Unit =
|
|
46
|
+
jsNode "${console}.output.write(${message} + '\\n')"
|
|
51
47
|
}
|
|
52
48
|
|
|
53
49
|
namespace examples {
|
|
@@ -58,9 +54,9 @@ namespace examples {
|
|
|
58
54
|
do writeLine("Hi " ++ name ++ ", nice to meet you!")
|
|
59
55
|
}
|
|
60
56
|
|
|
61
|
-
def main() =
|
|
57
|
+
def main() = {
|
|
62
58
|
with console;
|
|
63
59
|
greeter()
|
|
64
|
-
}
|
|
60
|
+
}
|
|
65
61
|
|
|
66
|
-
}
|
|
62
|
+
}
|
|
@@ -280,7 +280,7 @@ def name(error: IOError): String = error match {
|
|
|
280
280
|
*
|
|
281
281
|
* See documentation of type IOError for further information.
|
|
282
282
|
*/
|
|
283
|
-
def
|
|
283
|
+
def fromNumber(number: Int): IOError = number match {
|
|
284
284
|
case 1 => EPERM()
|
|
285
285
|
case 2 => ENOENT()
|
|
286
286
|
case 3 => ESRCH()
|
|
@@ -361,5 +361,114 @@ def fromCode(code: Int): IOError = code match {
|
|
|
361
361
|
case 219 => EUNATCH()
|
|
362
362
|
case 220 => ESHUTDOWN()
|
|
363
363
|
|
|
364
|
-
case _ => panic("Not a valid error code: " ++ show(
|
|
364
|
+
case _ => panic("Not a valid error code: " ++ show(number))
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
namespace internal {
|
|
368
|
+
|
|
369
|
+
extern llvm """
|
|
370
|
+
declare %Int @c_error_number(%Int)
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
extern jsNode """
|
|
374
|
+
const os = require('node:os');
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Maps the error code to a Effekt-stable (platform independent) numeric value.
|
|
378
|
+
*
|
|
379
|
+
* Tries to use most common errno integer values, but introduces fresh values (> 200)
|
|
380
|
+
* for those without common errno values.
|
|
381
|
+
*/
|
|
382
|
+
function errorNumber(errno) {
|
|
383
|
+
const errnoMap = {
|
|
384
|
+
[os.constants.errno.EPERM]: 1,
|
|
385
|
+
[os.constants.errno.ENOENT]: 2,
|
|
386
|
+
[os.constants.errno.ESRCH]: 3,
|
|
387
|
+
[os.constants.errno.EINTR]: 4,
|
|
388
|
+
[os.constants.errno.EIO]: 5,
|
|
389
|
+
[os.constants.errno.ENXIO]: 6,
|
|
390
|
+
[os.constants.errno.E2BIG]: 7,
|
|
391
|
+
[os.constants.errno.EBADF]: 9,
|
|
392
|
+
[os.constants.errno.EAGAIN]: 11,
|
|
393
|
+
[os.constants.errno.ENOMEM]: 12,
|
|
394
|
+
[os.constants.errno.EACCES]: 13,
|
|
395
|
+
[os.constants.errno.EFAULT]: 14,
|
|
396
|
+
[os.constants.errno.EBUSY]: 16,
|
|
397
|
+
[os.constants.errno.EEXIST]: 17,
|
|
398
|
+
[os.constants.errno.EXDEV]: 18,
|
|
399
|
+
[os.constants.errno.ENODEV]: 19,
|
|
400
|
+
[os.constants.errno.ENOTDIR]: 20,
|
|
401
|
+
[os.constants.errno.EISDIR]: 21,
|
|
402
|
+
[os.constants.errno.EINVAL]: 22,
|
|
403
|
+
[os.constants.errno.ENFILE]: 23,
|
|
404
|
+
[os.constants.errno.EMFILE]: 24,
|
|
405
|
+
[os.constants.errno.ENOTTY]: 25,
|
|
406
|
+
[os.constants.errno.ETXTBSY]: 26,
|
|
407
|
+
[os.constants.errno.EFBIG]: 27,
|
|
408
|
+
[os.constants.errno.ENOSPC]: 28,
|
|
409
|
+
[os.constants.errno.ESPIPE]: 29,
|
|
410
|
+
[os.constants.errno.EROFS]: 30,
|
|
411
|
+
[os.constants.errno.EMLINK]: 31,
|
|
412
|
+
[os.constants.errno.EPIPE]: 32,
|
|
413
|
+
[os.constants.errno.ERANGE]: 34,
|
|
414
|
+
[os.constants.errno.ENAMETOOLONG]: 36,
|
|
415
|
+
[os.constants.errno.ELOOP]: 40,
|
|
416
|
+
[os.constants.errno.EOVERFLOW]: 75,
|
|
417
|
+
[os.constants.errno.EFTYPE]: 79,
|
|
418
|
+
[os.constants.errno.EILSEQ]: 84,
|
|
419
|
+
[os.constants.errno.ENOTSOCK]: 88,
|
|
420
|
+
[os.constants.errno.EDESTADDRREQ]: 89,
|
|
421
|
+
[os.constants.errno.EMSGSIZE]: 90,
|
|
422
|
+
[os.constants.errno.EPROTOTYPE]: 91,
|
|
423
|
+
[os.constants.errno.ENOPROTOOPT]: 92,
|
|
424
|
+
[os.constants.errno.EPROTONOSUPPORT]: 93,
|
|
425
|
+
[os.constants.errno.ESOCKTNOSUPPORT]: 94,
|
|
426
|
+
[os.constants.errno.ENOTSUP]: 95,
|
|
427
|
+
[os.constants.errno.EAFNOSUPPORT]: 97,
|
|
428
|
+
[os.constants.errno.EADDRINUSE]: 98,
|
|
429
|
+
[os.constants.errno.EADDRNOTAVAIL]: 99,
|
|
430
|
+
[os.constants.errno.ENETDOWN]: 100,
|
|
431
|
+
[os.constants.errno.ENETUNREACH]: 101,
|
|
432
|
+
[os.constants.errno.ECONNABORTED]: 103,
|
|
433
|
+
[os.constants.errno.ECONNRESET]: 104,
|
|
434
|
+
[os.constants.errno.ENOBUFS]: 105,
|
|
435
|
+
[os.constants.errno.EISCONN]: 106,
|
|
436
|
+
[os.constants.errno.ENOTCONN]: 107,
|
|
437
|
+
[os.constants.errno.ETIMEDOUT]: 110,
|
|
438
|
+
[os.constants.errno.ECONNREFUSED]: 111,
|
|
439
|
+
[os.constants.errno.EHOSTUNREACH]: 113,
|
|
440
|
+
[os.constants.errno.EALREADY]: 114,
|
|
441
|
+
[os.constants.errno.ECANCELED]: 125,
|
|
442
|
+
[os.constants.errno.EAI_ADDRFAMILY]: 200,
|
|
443
|
+
[os.constants.errno.EAI_AGAIN]: 201,
|
|
444
|
+
[os.constants.errno.EAI_BADFLAGS]: 202,
|
|
445
|
+
[os.constants.errno.EAI_BADHINTS]: 203,
|
|
446
|
+
[os.constants.errno.EAI_CANCELED]: 204,
|
|
447
|
+
[os.constants.errno.EAI_FAIL]: 205,
|
|
448
|
+
[os.constants.errno.EAI_FAMILY]: 206,
|
|
449
|
+
[os.constants.errno.EAI_MEMORY]: 207,
|
|
450
|
+
[os.constants.errno.EAI_NODATA]: 208,
|
|
451
|
+
[os.constants.errno.EAI_NONAME]: 209,
|
|
452
|
+
[os.constants.errno.EAI_OVERFLOW]: 210,
|
|
453
|
+
[os.constants.errno.EAI_PROTOCOL]: 211,
|
|
454
|
+
[os.constants.errno.EAI_SERVICE]: 212,
|
|
455
|
+
[os.constants.errno.EAI_SOCKTYPE]: 213,
|
|
456
|
+
[os.constants.errno.ECHARSET]: 215,
|
|
457
|
+
[os.constants.errno.ENONET]: 216,
|
|
458
|
+
[os.constants.errno.UNKNOWN]: 217,
|
|
459
|
+
[os.constants.errno.EOF]: 218,
|
|
460
|
+
[os.constants.errno.EUNATCH]: 219,
|
|
461
|
+
[os.constants.errno.ESHUTDOWN]: 220
|
|
462
|
+
};
|
|
463
|
+
return errnoMap[-errno] || -1; // Default to -1 for unknown error names
|
|
464
|
+
}
|
|
465
|
+
"""
|
|
466
|
+
|
|
467
|
+
extern pure def errorNumber(errno: Int): Int =
|
|
468
|
+
jsNode "errorNumber(${errno})"
|
|
469
|
+
llvm """
|
|
470
|
+
%z = call %Int @c_error_number(%Int ${errno})
|
|
471
|
+
ret %Int %z
|
|
472
|
+
"""
|
|
473
|
+
|
|
365
474
|
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
module io/filesystem
|
|
2
|
+
|
|
3
|
+
import bytes
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
import io/error
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Represents the file opening modes with their respective flags
|
|
11
|
+
* Backends rely on the order of these
|
|
12
|
+
*/
|
|
13
|
+
type Mode {
|
|
14
|
+
// Most common modes
|
|
15
|
+
ReadOnly() // 'r' exception if does not exist
|
|
16
|
+
WriteOnly() // 'w' created if does not exist, truncated if exists
|
|
17
|
+
AppendOnly() // 'a' created if does not exist
|
|
18
|
+
|
|
19
|
+
// Other modes
|
|
20
|
+
ReadWrite() // 'w+' created if does not exist, truncated if exists
|
|
21
|
+
ReadAppend() // 'a+' created if does not exist
|
|
22
|
+
AppendExclusive() // 'ax' created if does not exist, fails if exists
|
|
23
|
+
ReadAppendExclusive() // 'ax+' created if does not exist, fails if exists
|
|
24
|
+
AppendSync() // 'as' created if does not exist, append in synchronous mode
|
|
25
|
+
ReadAppendSync() // 'as+' created if does not exist, append in synchronous mode
|
|
26
|
+
ReadSync() // 'rs' exception if does not exist, read in synchronous mode
|
|
27
|
+
ReadWriteSync() // 'rs+' exception if does not exist, read/write in synchronous mode
|
|
28
|
+
WriteExclusive() // 'wx' created if does not exist, truncated if exists, fails if exists
|
|
29
|
+
ReadWriteExclusive() // 'wx+' created if does not exist, truncated if exists, fails if exists
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A file descriptor. Should not be inspected.
|
|
35
|
+
*/
|
|
36
|
+
type File = Int
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Reads a file at given path as utf8 encoded string.
|
|
41
|
+
*/
|
|
42
|
+
def readFile(path: String): String / Exception[IOError] = {
|
|
43
|
+
val fd = open(path, ReadOnly());
|
|
44
|
+
with on[IOError].finalize { close(fd) }
|
|
45
|
+
|
|
46
|
+
val readSize = 1048576 // 1MB
|
|
47
|
+
var size = readSize
|
|
48
|
+
var buffer = bytes(size)
|
|
49
|
+
var offset = 0;
|
|
50
|
+
|
|
51
|
+
def go(): String = {
|
|
52
|
+
read(fd, buffer.slice(offset, min(readSize, size - offset)), -1) match {
|
|
53
|
+
case 0 =>
|
|
54
|
+
buffer.slice(0, offset).toUTF8
|
|
55
|
+
case n and n < 0 => panic("Error!")
|
|
56
|
+
case n =>
|
|
57
|
+
offset = offset + n
|
|
58
|
+
|
|
59
|
+
// we are at maximum capacity
|
|
60
|
+
if (n == readSize && (offset + readSize) > size) {
|
|
61
|
+
val newSize = size * 2
|
|
62
|
+
val newBuffer = bytes(newSize)
|
|
63
|
+
copy(buffer, newBuffer, 0, 0, size)
|
|
64
|
+
buffer = newBuffer
|
|
65
|
+
size = newSize
|
|
66
|
+
}
|
|
67
|
+
go()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
go()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Writes the (utf8 encoded) string `contents` into the specified file.
|
|
76
|
+
*/
|
|
77
|
+
def writeFile(path: String, contents: String): Unit / Exception[IOError] = {
|
|
78
|
+
val fd = open(path, WriteOnly());
|
|
79
|
+
with on[IOError].finalize { close(fd) }
|
|
80
|
+
|
|
81
|
+
val writeSize = 1048576 // 1MB
|
|
82
|
+
|
|
83
|
+
// this induces a memcpy that is not strictly necessary, since we use the buffer read-only
|
|
84
|
+
val buffer = contents.fromUTF8
|
|
85
|
+
val size = buffer.size
|
|
86
|
+
|
|
87
|
+
var offset = 0;
|
|
88
|
+
def remaining() = size - offset
|
|
89
|
+
|
|
90
|
+
def go(): Unit =
|
|
91
|
+
write(fd, buffer.slice(offset, min(writeSize, remaining())), -1) match {
|
|
92
|
+
case 0 => ()
|
|
93
|
+
case n and n < 0 => panic("Error!")
|
|
94
|
+
case n =>
|
|
95
|
+
offset = offset + n;
|
|
96
|
+
if (remaining() > 0) go()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
go()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* An abstract interface applications can program against.
|
|
105
|
+
*
|
|
106
|
+
* Can be interpreted with the `filesystem` handler, or virtualized etc.
|
|
107
|
+
*
|
|
108
|
+
* See example below.
|
|
109
|
+
*/
|
|
110
|
+
interface Files {
|
|
111
|
+
def readFile(path: String): String
|
|
112
|
+
def writeFile(path: String, contents: String): Unit
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Using the file system via node.js
|
|
117
|
+
*/
|
|
118
|
+
def filesystem[R] { program: => R / Files }: R / Exception[IOError] = // TODO move Exception to be bidirectional
|
|
119
|
+
try { program() }
|
|
120
|
+
with Files {
|
|
121
|
+
def readFile(path) = resume(readFile(path))
|
|
122
|
+
def writeFile(path, contents) = resume(writeFile(path, contents))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def open(path: String, mode: Mode): File / Exception[IOError] =
|
|
127
|
+
internal::checkResult(internal::open(path, mode))
|
|
128
|
+
|
|
129
|
+
def read(fd: File, buffer: Bytes, offset: Int): Int / Exception[IOError] =
|
|
130
|
+
internal::checkResult(internal::read(fd, buffer, offset))
|
|
131
|
+
|
|
132
|
+
def write(fd: File, buffer: Bytes, offset: Int): Int / Exception[IOError] =
|
|
133
|
+
internal::checkResult(internal::write(fd, buffer, offset))
|
|
134
|
+
|
|
135
|
+
def close(fd: File): Unit / Exception[IOError] = {
|
|
136
|
+
internal::checkResult(internal::close(fd)); ()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
namespace internal {
|
|
140
|
+
|
|
141
|
+
extern js """
|
|
142
|
+
function modeName(mode) {
|
|
143
|
+
switch (mode.__tag) {
|
|
144
|
+
case 0: // ReadOnly()
|
|
145
|
+
return 'r';
|
|
146
|
+
case 1: // WriteOnly()
|
|
147
|
+
return 'w';
|
|
148
|
+
case 2: // AppendOnly()
|
|
149
|
+
return 'a';
|
|
150
|
+
case 3: // ReadWrite()
|
|
151
|
+
return 'w+';
|
|
152
|
+
case 4: // ReadAppend()
|
|
153
|
+
return 'a+';
|
|
154
|
+
case 5: // AppendExclusive()
|
|
155
|
+
return 'ax';
|
|
156
|
+
case 6: // ReadAppendExclusive()
|
|
157
|
+
return 'ax+';
|
|
158
|
+
case 7: // AppendSync()
|
|
159
|
+
return 'as';
|
|
160
|
+
case 8: // ReadAppendSync()
|
|
161
|
+
return 'as+';
|
|
162
|
+
case 9: // ReadSync()
|
|
163
|
+
return 'rs';
|
|
164
|
+
case 10: // ReadWriteSync()
|
|
165
|
+
return 'rs+';
|
|
166
|
+
case 11: // WriteExclusive()
|
|
167
|
+
return 'wx';
|
|
168
|
+
case 12: // ReadWriteExclusive()
|
|
169
|
+
return 'wx+';
|
|
170
|
+
default:
|
|
171
|
+
// Invalid tag value
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Nodejs file operations expect buffers, but we represent buffers as typed arrays.
|
|
178
|
+
* This function converts between the two without copying.
|
|
179
|
+
*/
|
|
180
|
+
function toBuffer(buffer) {
|
|
181
|
+
return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
|
|
182
|
+
}
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
extern jsNode """
|
|
186
|
+
const fs = require("fs");
|
|
187
|
+
|
|
188
|
+
function open(path, mode, callback) {
|
|
189
|
+
fs.open(path, modeName(mode), (err, fd) => {
|
|
190
|
+
if (err) { callback(err.errno) } else { callback(fd) }
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function read(fd, buffer, offset, callback) {
|
|
195
|
+
let position = offset === -1 ? null : offset;
|
|
196
|
+
fs.read(fd, toBuffer(buffer), 0, buffer.length, position, (err, bytesRead) => {
|
|
197
|
+
if (err) { callback(err.errno) } else { callback(bytesRead) }
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function write(fd, buffer, offset, callback) {
|
|
202
|
+
let position = offset === -1 ? null : offset;
|
|
203
|
+
fs.write(fd, toBuffer(buffer), 0, buffer.length, position, (err, bytesWritten) => {
|
|
204
|
+
if (err) { callback(err.errno) } else { callback(bytesWritten) }
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function close(fd, callback) {
|
|
209
|
+
fs.close(fd, (err) => {
|
|
210
|
+
if (err) { callback(err.errno) } else { callback(0) }
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
extern llvm """
|
|
216
|
+
declare void @c_fs_open(%Pos, %Pos, %Stack)
|
|
217
|
+
declare void @c_fs_read(%Int, %Pos, %Int, %Stack)
|
|
218
|
+
declare void @c_fs_write(%Int, %Pos, %Int, %Stack)
|
|
219
|
+
declare void @c_fs_close(%Int, %Stack)
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
extern async def open(path: String, mode: Mode): Int =
|
|
223
|
+
jsNode "$effekt.callcc(callback => open(${path}, ${mode}, callback))"
|
|
224
|
+
llvm """
|
|
225
|
+
call void @c_fs_open(%Pos ${path}, %Pos ${mode}, %Stack %stack)
|
|
226
|
+
ret void
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
extern async def read(fd: Int, buffer: Bytes, offset: Int): Int =
|
|
230
|
+
jsNode "$effekt.callcc(callback => read(${fd}, ${buffer}, ${offset}, callback))"
|
|
231
|
+
llvm """
|
|
232
|
+
call void @c_fs_read(%Int ${fd}, %Pos ${buffer}, %Int ${offset}, %Stack %stack)
|
|
233
|
+
ret void
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
extern async def write(fd: Int, buffer: Bytes, offset: Int): Int =
|
|
237
|
+
jsNode "$effekt.callcc(callback => write(${fd}, ${buffer}, ${offset}, callback))"
|
|
238
|
+
llvm """
|
|
239
|
+
call void @c_fs_write(%Int ${fd}, %Pos ${buffer}, %Int ${offset}, %Stack %stack)
|
|
240
|
+
ret void
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
extern async def close(fd: Int): Int =
|
|
244
|
+
jsNode "$effekt.callcc(callback => close(${fd}, callback))"
|
|
245
|
+
llvm """
|
|
246
|
+
call void @c_fs_close(%Int ${fd}, %Stack %stack)
|
|
247
|
+
ret void
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
def checkResult(result: Int): Int / Exception[IOError] =
|
|
251
|
+
if (result < 0) {
|
|
252
|
+
val ioError = fromNumber(internal::errorNumber(result));
|
|
253
|
+
do raise[IOError](ioError, message(ioError))
|
|
254
|
+
} else {
|
|
255
|
+
result
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
namespace examples {
|
|
260
|
+
|
|
261
|
+
// Example illustrating using abstract files interface
|
|
262
|
+
// and interpreting it into asynchronous IO
|
|
263
|
+
def copy(from: String, to: String): Unit / Files = {
|
|
264
|
+
val contents = do readFile(from)
|
|
265
|
+
do writeFile(to, contents)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
def main() = {
|
|
269
|
+
with on[IOError].panic;
|
|
270
|
+
with filesystem;
|
|
271
|
+
|
|
272
|
+
copy("README.md", "out/test.md")
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
module io/network
|
|
2
2
|
|
|
3
3
|
import bytes
|
|
4
|
-
import queue
|
|
5
4
|
import io
|
|
6
5
|
|
|
7
6
|
namespace js {
|
|
8
|
-
extern
|
|
7
|
+
extern jsNode """
|
|
9
8
|
const net = require('node:net');
|
|
10
9
|
|
|
11
10
|
function listen(server, port, host, listener) {
|
|
@@ -17,18 +16,18 @@ namespace js {
|
|
|
17
16
|
extern type JSServer // = net.Server
|
|
18
17
|
extern type JSSocket // = net.Socket
|
|
19
18
|
extern io def server(): JSServer =
|
|
20
|
-
|
|
21
|
-
extern io def listen(server: JSServer, port: Int, host: String, listener: JSSocket => Unit at {io, global}): Unit =
|
|
22
|
-
|
|
19
|
+
jsNode "net.createServer()"
|
|
20
|
+
extern io def listen(server: JSServer, port: Int, host: String, listener: JSSocket => Unit at {io, async, global}): Unit =
|
|
21
|
+
jsNode "listen(${server}, ${port}, ${host}, (socket) => (${listener})(socket).run())"
|
|
23
22
|
|
|
24
|
-
extern
|
|
25
|
-
|
|
23
|
+
extern async def send(socket: JSSocket, data: Bytes): Unit =
|
|
24
|
+
jsNode "$effekt.callcc(callback => ${socket}.write(${data}, callback))"
|
|
26
25
|
|
|
27
|
-
extern
|
|
28
|
-
|
|
26
|
+
extern async def receive(socket: JSSocket): Bytes =
|
|
27
|
+
jsNode "$effekt.callcc(callback => ${socket}.once('data', callback))"
|
|
29
28
|
|
|
30
|
-
extern
|
|
31
|
-
|
|
29
|
+
extern async def end(socket: JSSocket): Unit =
|
|
30
|
+
jsNode "$effekt.callcc(callback => ${socket}.end(callback))"
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
interface Socket {
|
|
@@ -37,44 +36,22 @@ interface Socket {
|
|
|
37
36
|
def end(): Unit
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
def server(host: String, port: Int, handler: () => Unit /
|
|
39
|
+
def server(host: String, port: Int, handler: () => Unit / Socket at {io, async, global}): Unit = {
|
|
41
40
|
val server = js::server();
|
|
42
|
-
def connections: Queue[js::JSSocket] = emptyQueue()
|
|
43
41
|
js::listen(server, port, host, box { socket =>
|
|
44
|
-
|
|
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
|
+
})
|
|
45
54
|
})
|
|
46
|
-
|
|
47
|
-
// TODO Instead of polling, we should use Channels
|
|
48
|
-
loop {
|
|
49
|
-
connections.popBack match {
|
|
50
|
-
case Some(socket) =>
|
|
51
|
-
println("New connection")
|
|
52
|
-
do promise(box {
|
|
53
|
-
try { handler() } with Socket {
|
|
54
|
-
def send(message) = {
|
|
55
|
-
do performIO[Unit](box { k =>
|
|
56
|
-
js::send(socket, message, box { k(()) })
|
|
57
|
-
});
|
|
58
|
-
resume(())
|
|
59
|
-
}
|
|
60
|
-
def receive() = {
|
|
61
|
-
resume(do performIO[Bytes](box { k =>
|
|
62
|
-
js::receive(socket, k)
|
|
63
|
-
}))
|
|
64
|
-
}
|
|
65
|
-
def end() = {
|
|
66
|
-
do performIO[Unit](box { k =>
|
|
67
|
-
js::end(socket, box { k(()) })
|
|
68
|
-
});
|
|
69
|
-
resume(())
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
()
|
|
74
|
-
case None() =>
|
|
75
|
-
do yield()
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
55
|
}
|
|
79
56
|
|
|
80
57
|
|
|
@@ -82,12 +59,12 @@ namespace examples {
|
|
|
82
59
|
def helloWorldApp(): Unit / Socket = {
|
|
83
60
|
val request = do receive();
|
|
84
61
|
|
|
85
|
-
println("Received a request: " ++ request.
|
|
62
|
+
println("Received a request: " ++ request.toUTF8)
|
|
86
63
|
|
|
87
|
-
if (request.
|
|
88
|
-
do send(
|
|
64
|
+
if (request.toUTF8.startsWith("GET /")) {
|
|
65
|
+
do send(fromUTF8("HTTP/1.1 200 OK\r\n\r\nHello from Effekt!"))
|
|
89
66
|
} else {
|
|
90
|
-
do send("HTTP/1.1 400 Bad Request\r\n\r\n".
|
|
67
|
+
do send("HTTP/1.1 400 Bad Request\r\n\r\n".fromUTF8)
|
|
91
68
|
}
|
|
92
69
|
do end()
|
|
93
70
|
}
|
|
@@ -96,10 +73,9 @@ namespace examples {
|
|
|
96
73
|
def main() = {
|
|
97
74
|
val port = 8080
|
|
98
75
|
println("Starting server on http://localhost:" ++ port.show)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
})
|
|
76
|
+
|
|
77
|
+
server("localhost", port, box {
|
|
78
|
+
helloWorldApp()
|
|
103
79
|
})
|
|
104
80
|
}
|
|
105
81
|
}
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
module io/time
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
extern llvm """
|
|
4
|
+
declare void @c_timer_start(%Int, %Stack)
|
|
5
|
+
"""
|
|
4
6
|
|
|
5
|
-
def wait(millis: Int): Unit
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
llvm """
|
|
12
|
-
call void @c_timer_wait(i64 ${millis}, %Neg ${onTimeout})
|
|
13
|
-
ret %Pos zeroinitializer
|
|
14
|
-
"""
|
|
15
|
-
}
|
|
7
|
+
extern async def wait(millis: Int): Unit =
|
|
8
|
+
js "$effekt.callcc(callback => setTimeout(() => callback($effekt.unit), ${millis}))"
|
|
9
|
+
llvm """
|
|
10
|
+
call void @c_timer_start(%Int ${millis}, %Stack %stack)
|
|
11
|
+
ret void
|
|
12
|
+
"""
|