@effekt-lang/effekt 0.2.1 → 0.3.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/LICENSE +21 -0
- package/README.md +36 -0
- package/bin/effekt +0 -0
- package/bin/effekt.sh +2 -0
- package/libraries/chez/THIRD-PARTY.txt +11 -0
- package/libraries/chez/callcc/datatype.ss +188 -0
- package/libraries/chez/callcc/effekt.ss +31 -0
- package/libraries/chez/callcc/seq.ss +44 -0
- package/libraries/chez/callcc/seq0.ss +69 -0
- package/libraries/chez/callcc/tail.ss +76 -0
- package/libraries/chez/callcc/tail0.ss +89 -0
- package/libraries/chez/common/effekt_primitives.ss +105 -0
- package/libraries/chez/common/immutable/cslist.effekt +37 -0
- package/libraries/chez/common/mutable/dict.effekt +28 -0
- package/libraries/chez/common/text/pregexp.scm +772 -0
- package/libraries/chez/common/text/regex.effekt +31 -0
- package/libraries/chez/lift/effekt.ss +118 -0
- package/libraries/chez/monadic/effekt.ss +84 -0
- package/libraries/chez/monadic/seq0.ss +76 -0
- package/libraries/common/args.effekt +95 -0
- package/libraries/common/array.effekt +225 -0
- package/libraries/common/bench.effekt +60 -0
- package/libraries/common/buffer.effekt +123 -0
- package/libraries/common/bytes.effekt +125 -0
- package/libraries/common/dequeue.effekt +92 -0
- package/libraries/common/effekt.effekt +764 -0
- package/libraries/common/exception.effekt +122 -0
- package/libraries/common/io/console.effekt +66 -0
- package/libraries/common/io/error.effekt +365 -0
- package/libraries/common/io/files.effekt +282 -0
- package/libraries/common/io/network.effekt +105 -0
- package/libraries/common/io/time.effekt +15 -0
- package/libraries/common/io.effekt +158 -0
- package/libraries/common/list.effekt +687 -0
- package/libraries/common/option.effekt +72 -0
- package/libraries/common/process.effekt +10 -0
- package/libraries/common/queue.effekt +140 -0
- package/libraries/common/ref.effekt +63 -0
- package/libraries/common/result.effekt +45 -0
- package/libraries/common/seq.effekt +914 -0
- package/libraries/common/string.effekt +395 -0
- package/libraries/common/test.effekt +98 -0
- package/libraries/js/effekt_builtins.js +57 -0
- package/libraries/js/effekt_runtime.js +385 -0
- package/libraries/js/io.js +143 -0
- package/libraries/js/mutable/map.effekt +31 -0
- package/libraries/js/text/regex.effekt +33 -0
- package/libraries/js/unsafe/cont.effekt +11 -0
- package/libraries/js/web/dom.effekt +45 -0
- package/libraries/llvm/array.c +53 -0
- package/libraries/llvm/buffer.c +286 -0
- package/libraries/llvm/forward-declare-c.ll +43 -0
- package/libraries/llvm/hole.c +11 -0
- package/libraries/llvm/io.c +566 -0
- package/libraries/llvm/io.ll +19 -0
- package/libraries/llvm/main.c +41 -0
- package/libraries/llvm/ref.c +45 -0
- package/libraries/llvm/rts.ll +761 -0
- package/libraries/llvm/sanity.c +16 -0
- package/libraries/llvm/types.c +43 -0
- package/libraries/ml/effekt.sml +65 -0
- package/libraries/ml/internal/mllist.effekt +9 -0
- package/libraries/ml/internal/mloption.effekt +18 -0
- package/libraries/ml/random.effekt +85 -0
- package/libraries/ml/text/regex.effekt +102 -0
- package/licenses/THIRD-PARTY.txt +21 -0
- package/licenses/apache-2.0 - license-2.0.txt +202 -0
- package/licenses/eclipse public license, version 2.0 - epl-2.0.html +61 -0
- package/licenses/kiama-license.txt +373 -0
- package/licenses/kiama-readme.txt +186 -0
- package/licenses/mit license - mit-license.html +1239 -0
- package/licenses/the apache software license, version 2.0 - license-2.0.txt +202 -0
- package/licenses/the bsd license - bsd-license.html +1237 -0
- package/licenses/the mit license - mit.html +1239 -0
- package/package.json +26 -18
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
module io/files
|
|
2
|
+
|
|
3
|
+
import bytes
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
import io/error
|
|
7
|
+
|
|
8
|
+
extern js """
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
extern type FileDescriptor
|
|
13
|
+
// = llvm: 32bit integer extended to 64bit and stored into the "tag" of "Pos"
|
|
14
|
+
// = js: FileHandle (https://nodejs.org/api/fs.html#class-filehandle)
|
|
15
|
+
|
|
16
|
+
extern pure def show(fd: FileDescriptor): String =
|
|
17
|
+
llvm """
|
|
18
|
+
%res = call %Pos @c_filedescriptor_show(%Pos %fd)
|
|
19
|
+
ret %Pos %res
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* An abstract interface applications can program against.
|
|
25
|
+
*
|
|
26
|
+
* Can be interpreted into AsyncIO with the `filesystem`
|
|
27
|
+
* handler, or virtualized etc.
|
|
28
|
+
*
|
|
29
|
+
* See example below.
|
|
30
|
+
*/
|
|
31
|
+
interface Files {
|
|
32
|
+
def readFile(path: String): String
|
|
33
|
+
def writeFile(path: String, contents: String): Unit
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Represents the file opening modes with their respective flags
|
|
38
|
+
*/
|
|
39
|
+
type Mode {
|
|
40
|
+
// Most common modes
|
|
41
|
+
Append() // 'a' created if does not exist
|
|
42
|
+
ReadAppend() // 'a+' created if does not exist
|
|
43
|
+
ReadOnly() // 'r' exception if does not exist
|
|
44
|
+
ReadWrite() // 'w+' created if does not exist, truncated if exists
|
|
45
|
+
Write() // 'w' created if does not exist, truncated if exists
|
|
46
|
+
|
|
47
|
+
// Other modes
|
|
48
|
+
AppendExclusive() // 'ax' created if does not exist, fails if exists
|
|
49
|
+
ReadAppendExclusive() // 'ax+' created if does not exist, fails if exists
|
|
50
|
+
AppendSync() // 'as' created if does not exist, append in synchronous mode
|
|
51
|
+
ReadAppendSync() // 'as+' created if does not exist, append in synchronous mode
|
|
52
|
+
ReadSync() // 'rs' exception if does not exist, read in synchronous mode
|
|
53
|
+
ReadWriteSync() // 'rs+' exception if does not exist, read/write in synchronous mode
|
|
54
|
+
WriteExclusive() // 'wx' created if does not exist, truncated if exists, fails if exists
|
|
55
|
+
ReadWriteExclusive() // 'wx+' created if does not exist, truncated if exists, fails if exists
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def modeName(modes: Mode): String = modes match {
|
|
59
|
+
case Append() => "a"
|
|
60
|
+
case ReadAppend() => "a+"
|
|
61
|
+
case ReadOnly() => "r"
|
|
62
|
+
case ReadWrite() => "w+"
|
|
63
|
+
case Write() => "w"
|
|
64
|
+
case AppendExclusive() => "ax"
|
|
65
|
+
case ReadAppendExclusive() => "ax+"
|
|
66
|
+
case AppendSync() => "as"
|
|
67
|
+
case ReadAppendSync() => "as+"
|
|
68
|
+
case ReadSync() => "rs"
|
|
69
|
+
case ReadWriteSync() => "rs+"
|
|
70
|
+
case WriteExclusive() => "wx"
|
|
71
|
+
case ReadWriteExclusive() => "wx+"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Using the file system via node.js
|
|
77
|
+
*/
|
|
78
|
+
def filesystem[R] { program: => R / Files }: R / { AsyncIO, Exception[IOError] } = // TODO move Exception to be bidirectional
|
|
79
|
+
try { program() }
|
|
80
|
+
with Files {
|
|
81
|
+
def readFile(path) = resume(readFile(path))
|
|
82
|
+
def writeFile(path, contents) = resume(writeFile(path, contents))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
// AsyncIO-based API (default)
|
|
87
|
+
// ---------------------------
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Reads a file at given path as utf8 encoded string.
|
|
91
|
+
*/
|
|
92
|
+
def readFile(path: String): String / { AsyncIO, Exception[IOError] } = {
|
|
93
|
+
val fd = openFile(path, ReadOnly());
|
|
94
|
+
with on[IOError].finalize { closeFile(fd) }
|
|
95
|
+
|
|
96
|
+
val readSize = 1048576 // 1MB
|
|
97
|
+
var size = readSize
|
|
98
|
+
var buffer = bytes(size)
|
|
99
|
+
var offset = 0;
|
|
100
|
+
|
|
101
|
+
def go(): String = {
|
|
102
|
+
readFile(fd, buffer.slice(offset, min(readSize, size - offset)), -1) match {
|
|
103
|
+
case 0 =>
|
|
104
|
+
buffer.slice(0, offset).toUTF8
|
|
105
|
+
case n and n < 0 => panic("Error!")
|
|
106
|
+
case n =>
|
|
107
|
+
offset = offset + n
|
|
108
|
+
|
|
109
|
+
// we are at maximum capacity
|
|
110
|
+
if (n == readSize && (offset + readSize) > size) {
|
|
111
|
+
val newSize = size * 2
|
|
112
|
+
val newBuffer = bytes(newSize)
|
|
113
|
+
copy(buffer, newBuffer, 0, 0, size)
|
|
114
|
+
buffer = newBuffer
|
|
115
|
+
size = newSize
|
|
116
|
+
}
|
|
117
|
+
go()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
go()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Writes the (utf8 encoded) string `contents` into the specified file.
|
|
126
|
+
*/
|
|
127
|
+
def writeFile(path: String, contents: String): Unit / { AsyncIO, Exception[IOError] } = {
|
|
128
|
+
val fd = openFile(path, Write());
|
|
129
|
+
with on[IOError].finalize { closeFile(fd) }
|
|
130
|
+
|
|
131
|
+
val writeSize = 1048576 // 1MB
|
|
132
|
+
|
|
133
|
+
// this induces a memcpy that is not strictly necessary, since we use the buffer read-only
|
|
134
|
+
val buffer = contents.fromUTF8
|
|
135
|
+
val size = buffer.size
|
|
136
|
+
|
|
137
|
+
var offset = 0;
|
|
138
|
+
def remaining() = size - offset
|
|
139
|
+
|
|
140
|
+
def go(): Unit =
|
|
141
|
+
writeFile(fd, buffer.slice(offset, min(writeSize, remaining())), -1) match {
|
|
142
|
+
case 0 => ()
|
|
143
|
+
case n and n < 0 => panic("Error!")
|
|
144
|
+
case n =>
|
|
145
|
+
offset = offset + n;
|
|
146
|
+
if (remaining() > 0) go()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
go()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Opens the file at given path using the specified mode (read, write, etc.)
|
|
154
|
+
*/
|
|
155
|
+
def openFile(path: String, flags: Mode): FileDescriptor / { AsyncIO, Exception[IOError] } =
|
|
156
|
+
do performIO(box { k =>
|
|
157
|
+
val res = callback::openFile(path, flags,
|
|
158
|
+
box { fd => k(Success(fd)) },
|
|
159
|
+
box { error => k(Error(error, "Error opening file: " ++ error.message)) });
|
|
160
|
+
()
|
|
161
|
+
}).value
|
|
162
|
+
|
|
163
|
+
def readFile(fd: FileDescriptor, buffer: Bytes, offset: Int): Int / { AsyncIO, Exception[IOError] } =
|
|
164
|
+
do performIO(box { k =>
|
|
165
|
+
callback::readFile(fd, buffer, offset,
|
|
166
|
+
box { n => k(Success(n)) },
|
|
167
|
+
box { error => k(Error(error, "Error reading from file: " ++ error.message)) })
|
|
168
|
+
}).value
|
|
169
|
+
|
|
170
|
+
def writeFile(fd: FileDescriptor, buffer: Bytes, offset: Int): Int / { AsyncIO, Exception[IOError] } =
|
|
171
|
+
do performIO(box { k =>
|
|
172
|
+
callback::writeFile(fd, buffer, offset,
|
|
173
|
+
box { n => k(Success(n)) },
|
|
174
|
+
box { n =>
|
|
175
|
+
val error = n.fromCode
|
|
176
|
+
k(Error(error, "Error writing to file: " ++ error.message))
|
|
177
|
+
})
|
|
178
|
+
}).value
|
|
179
|
+
|
|
180
|
+
extern io def closeFile(fd: FileDescriptor): Unit =
|
|
181
|
+
js "fs.close(${fd}, () => $effekt.unit)"
|
|
182
|
+
llvm """
|
|
183
|
+
; extract the filedescriptor from the tag of the Pos
|
|
184
|
+
%tag = extractvalue %Pos ${fd}, 0
|
|
185
|
+
%fd_int = trunc i64 %tag to i32
|
|
186
|
+
|
|
187
|
+
call void @c_file_close(i32 %fd_int)
|
|
188
|
+
ret %Pos zeroinitializer
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
// Callback-based API
|
|
193
|
+
// ------------------
|
|
194
|
+
// mostly used as an implementation detail.
|
|
195
|
+
namespace callback {
|
|
196
|
+
|
|
197
|
+
def openFile(path: String, mode: Mode,
|
|
198
|
+
onSuccess: FileDescriptor => Unit at {io, global},
|
|
199
|
+
onFailure: IOError => Unit at {io, global}): Unit =
|
|
200
|
+
openFile(path, modeName(mode), onSuccess, box { code => onFailure(fromCode(code)) })
|
|
201
|
+
|
|
202
|
+
def readFile(fd: FileDescriptor, buffer: Bytes, offset: Int,
|
|
203
|
+
onSuccess: (Int) => Unit at {io, global},
|
|
204
|
+
onFailure: IOError => Unit at {io, global}): Unit =
|
|
205
|
+
readFile(fd, buffer, offset, onSuccess, box { code => onFailure(fromCode(code)) })
|
|
206
|
+
|
|
207
|
+
// The following are supposed to be internal
|
|
208
|
+
|
|
209
|
+
extern io def openFile(path: String, mode: String,
|
|
210
|
+
onSuccess: (FileDescriptor) => Unit at {io, global},
|
|
211
|
+
onFailure: (Int) => Unit at {io, global}): Unit =
|
|
212
|
+
js "openFile(${path}, ${mode}, ${onSuccess}, ${onFailure})"
|
|
213
|
+
llvm """
|
|
214
|
+
; stack allocate
|
|
215
|
+
%onSuccess_ptr = alloca %Neg
|
|
216
|
+
store %Neg %onSuccess, %Neg* %onSuccess_ptr
|
|
217
|
+
|
|
218
|
+
%onFailure_ptr = alloca %Neg
|
|
219
|
+
store %Neg %onFailure, %Neg* %onFailure_ptr
|
|
220
|
+
|
|
221
|
+
call void @c_file_open(%Pos ${path}, %Pos ${mode}, ptr %onSuccess_ptr, ptr %onFailure_ptr) #0
|
|
222
|
+
ret %Pos zeroinitializer
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
extern io def readFile(fd: FileDescriptor, buffer: Bytes, offset: Int,
|
|
226
|
+
onSuccess: (Int) => Unit at {io, global},
|
|
227
|
+
onFailure: (Int) => Unit at {io, global}): Unit =
|
|
228
|
+
js "readFile(${fd}, ${buffer}, ${offset}, ${onSuccess}, ${onFailure})"
|
|
229
|
+
llvm """
|
|
230
|
+
; extract the filedescriptor from the tag of the Pos
|
|
231
|
+
%tag = extractvalue %Pos %${fd}, 0
|
|
232
|
+
%fd_int = trunc i64 %tag to i32
|
|
233
|
+
|
|
234
|
+
; stack allocate
|
|
235
|
+
%onSuccess_ptr = alloca %Neg
|
|
236
|
+
store %Neg ${onSuccess}, %Neg* %onSuccess_ptr
|
|
237
|
+
|
|
238
|
+
%onFailure_ptr = alloca %Neg
|
|
239
|
+
store %Neg ${onFailure}, %Neg* %onFailure_ptr
|
|
240
|
+
|
|
241
|
+
call void @c_file_read(i32 %fd_int, %Pos ${buffer}, i64 noundef ${offset}, ptr %onSuccess_ptr, ptr %onFailure_ptr) #0
|
|
242
|
+
ret %Pos zeroinitializer
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
extern io def writeFile(fd: FileDescriptor, buffer: Bytes, offset: Int,
|
|
246
|
+
onSuccess: (Int) => Unit at {io, global},
|
|
247
|
+
onFailure: (Int) => Unit at {io, global}): Unit =
|
|
248
|
+
js "writeFile(${fd}, ${buffer}, ${offset}, ${onSuccess}, ${onFailure})"
|
|
249
|
+
llvm """
|
|
250
|
+
; extract the filedescriptor from the tag of the Pos
|
|
251
|
+
%tag = extractvalue %Pos ${fd}, 0
|
|
252
|
+
%fd_int = trunc i64 %tag to i32
|
|
253
|
+
|
|
254
|
+
; stack allocate
|
|
255
|
+
%onSuccess_ptr = alloca %Neg
|
|
256
|
+
store %Neg ${onSuccess}, %Neg* %onSuccess_ptr
|
|
257
|
+
|
|
258
|
+
%onFailure_ptr = alloca %Neg
|
|
259
|
+
store %Neg ${onFailure}, %Neg* %onFailure_ptr
|
|
260
|
+
|
|
261
|
+
call void @c_file_write(i32 %fd_int, %Pos ${buffer}, i64 noundef ${offset}, ptr %onSuccess_ptr, ptr %onFailure_ptr) #0
|
|
262
|
+
ret %Pos zeroinitializer
|
|
263
|
+
"""
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
namespace examples {
|
|
268
|
+
|
|
269
|
+
// Example illustrating using abstract files interface
|
|
270
|
+
// and interpreting it into asynchronous IO
|
|
271
|
+
def copy(from: String, to: String): Unit / Files = {
|
|
272
|
+
val contents = do readFile(from)
|
|
273
|
+
do writeFile(to, contents)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
def main() = eventloop(box {
|
|
277
|
+
with on[IOError].panic;
|
|
278
|
+
with filesystem;
|
|
279
|
+
|
|
280
|
+
copy("README.md", "out/test.md")
|
|
281
|
+
})
|
|
282
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
module io/network
|
|
2
|
+
|
|
3
|
+
import bytes
|
|
4
|
+
import queue
|
|
5
|
+
import io
|
|
6
|
+
|
|
7
|
+
namespace js {
|
|
8
|
+
extern js """
|
|
9
|
+
const net = require('node:net');
|
|
10
|
+
|
|
11
|
+
function listen(server, port, host, listener) {
|
|
12
|
+
server.listen(port, host);
|
|
13
|
+
server.on('connection', listener);
|
|
14
|
+
}
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
extern type JSServer // = net.Server
|
|
18
|
+
extern type JSSocket // = net.Socket
|
|
19
|
+
extern io def server(): JSServer =
|
|
20
|
+
js "net.createServer()"
|
|
21
|
+
extern io def listen(server: JSServer, port: Int, host: String, listener: JSSocket => Unit at {io, global}): Unit =
|
|
22
|
+
js "listen(${server}, ${port}, ${host}, (socket) => (${listener})(socket).run())"
|
|
23
|
+
|
|
24
|
+
extern io def send(socket: JSSocket, data: Bytes, callback: () => Unit at {io, global}): Unit =
|
|
25
|
+
js "${socket}.write(${data}, () => (${callback})().run())"
|
|
26
|
+
|
|
27
|
+
extern io def receive(socket: JSSocket, callback: Bytes => Unit at {io, global}): Unit =
|
|
28
|
+
js "${socket}.once('data', (data) => (${callback})(data).run())"
|
|
29
|
+
|
|
30
|
+
extern io def end(socket: JSSocket, callback: () => Unit at {io, global}): Unit =
|
|
31
|
+
js "${socket}.end(() => (${callback})().run())"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Socket {
|
|
35
|
+
def send(message: Bytes): Unit
|
|
36
|
+
def receive(): Bytes
|
|
37
|
+
def end(): Unit
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
def server(host: String, port: Int, handler: () => Unit / { Socket, IO } at {io, global}): Unit / IO = {
|
|
41
|
+
val server = js::server();
|
|
42
|
+
def connections: Queue[js::JSSocket] = emptyQueue()
|
|
43
|
+
js::listen(server, port, host, box { socket =>
|
|
44
|
+
connections.pushFront(socket)
|
|
45
|
+
})
|
|
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
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
namespace examples {
|
|
82
|
+
def helloWorldApp(): Unit / Socket = {
|
|
83
|
+
val request = do receive();
|
|
84
|
+
|
|
85
|
+
println("Received a request: " ++ request.show)
|
|
86
|
+
|
|
87
|
+
if (request.show.startsWith("GET /")) {
|
|
88
|
+
do send(toBytes("HTTP/1.1 200 OK\r\n\r\nHello from Effekt!"))
|
|
89
|
+
} else {
|
|
90
|
+
do send("HTTP/1.1 400 Bad Request\r\n\r\n".toBytes)
|
|
91
|
+
}
|
|
92
|
+
do end()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// A server that just shows "Hello from Effekt!" on localhost:8080
|
|
96
|
+
def main() = {
|
|
97
|
+
val port = 8080
|
|
98
|
+
println("Starting server on http://localhost:" ++ port.show)
|
|
99
|
+
eventloop(box {
|
|
100
|
+
server("localhost", port, box {
|
|
101
|
+
helloWorldApp()
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module io/time
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
|
|
5
|
+
def wait(millis: Int): Unit / AsyncIO =
|
|
6
|
+
do performIO(box { k => callback::wait(millis, box { () => k(()) }) })
|
|
7
|
+
|
|
8
|
+
namespace callback {
|
|
9
|
+
extern io def wait(millis: Int, onTimeout: () => Unit at {io, global}): Unit =
|
|
10
|
+
js "setTimeout(() => (${onTimeout})().run(), ${millis})"
|
|
11
|
+
llvm """
|
|
12
|
+
call void @c_timer_wait(i64 ${millis}, %Neg ${onTimeout})
|
|
13
|
+
ret %Pos zeroinitializer
|
|
14
|
+
"""
|
|
15
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
module io
|
|
2
|
+
|
|
3
|
+
import ref
|
|
4
|
+
import queue
|
|
5
|
+
|
|
6
|
+
extern include llvm "../llvm/io.ll"
|
|
7
|
+
extern include js "../js/io.js"
|
|
8
|
+
|
|
9
|
+
// Asynchronous I/O
|
|
10
|
+
// ----------------
|
|
11
|
+
|
|
12
|
+
interface AsyncIO {
|
|
13
|
+
/**
|
|
14
|
+
* Runs the provided computation and conceptually waits until it completes by
|
|
15
|
+
* calling the writer-continuation.
|
|
16
|
+
*
|
|
17
|
+
* The provided writer-continuation should only ever be called ONCE.
|
|
18
|
+
* The computation is passed as a thunk in order to prevent accidental usage of
|
|
19
|
+
* other control effects that would violate the contract of calling the writer
|
|
20
|
+
* once.
|
|
21
|
+
*/
|
|
22
|
+
def performIO[T](computation: (T => Unit at {io, global}) => Unit at {io, global}): T
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Run an IO computation (that uses callbacks) here.
|
|
27
|
+
*/
|
|
28
|
+
def blocking(program: () => Unit / AsyncIO at {io, global}): Unit =
|
|
29
|
+
try { program() }
|
|
30
|
+
with AsyncIO {
|
|
31
|
+
def performIO[T](computation) = computation(resume)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
interface Concurrent {
|
|
36
|
+
def yield(): Unit
|
|
37
|
+
def abort(): Nothing
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Promises
|
|
41
|
+
// --------
|
|
42
|
+
|
|
43
|
+
extern type Promise[T]
|
|
44
|
+
// = js "{promise: Promise, resolve: ƒ}"
|
|
45
|
+
// = llvm "Promise"
|
|
46
|
+
|
|
47
|
+
interface Promises {
|
|
48
|
+
def promise[T](p: Task[T]): Promise[T]
|
|
49
|
+
def await[T](p: Promise[T]): T
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
namespace promise {
|
|
53
|
+
extern js """
|
|
54
|
+
function promise$make() {
|
|
55
|
+
let resolve;
|
|
56
|
+
const promise = new Promise((res, rej) => {
|
|
57
|
+
resolve = res;
|
|
58
|
+
});
|
|
59
|
+
return { resolve: resolve, promise: promise };
|
|
60
|
+
}
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
extern io def make[T](): Promise[T] =
|
|
64
|
+
js "promise$make()"
|
|
65
|
+
llvm """
|
|
66
|
+
; Get the default loop and run it.
|
|
67
|
+
%promise = call %Pos @c_promise_make()
|
|
68
|
+
ret %Pos %promise
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
extern io def resolve[T](p: Promise[T], value: T): Unit =
|
|
72
|
+
js "${p}.resolve(${value})"
|
|
73
|
+
llvm """
|
|
74
|
+
; Get the default loop and run it.
|
|
75
|
+
call void @c_promise_resolve(%Pos ${p}, %Pos ${value})
|
|
76
|
+
ret %Pos zeroinitializer
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
extern io def await[T](p: Promise[T], callback: T => Unit at {io, global}): Unit =
|
|
80
|
+
js "${p}.promise.then(res => (${callback})(res).run())"
|
|
81
|
+
llvm """
|
|
82
|
+
; Get the default loop and run it.
|
|
83
|
+
call void @c_promise_await(%Pos ${p}, %Neg ${callback})
|
|
84
|
+
ret %Pos zeroinitializer
|
|
85
|
+
"""
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
// TODO we cannot add effects Files, Console, etc. to Task since modules cannot be recursive, at the moment.
|
|
90
|
+
effect IO = { AsyncIO, Concurrent, Promises }
|
|
91
|
+
|
|
92
|
+
type Task[T] = () => T / IO at {io, global}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
// Event Loop
|
|
96
|
+
// ----------
|
|
97
|
+
|
|
98
|
+
def eventloop(prog: Task[Unit]) = {
|
|
99
|
+
|
|
100
|
+
def handle(p: Task[Unit]): Unit =
|
|
101
|
+
try { p() }
|
|
102
|
+
with Concurrent {
|
|
103
|
+
def yield() = eventloop::schedule(box { resume(()) })
|
|
104
|
+
def abort() = ()
|
|
105
|
+
} with AsyncIO {
|
|
106
|
+
// TODO there is a bug in our LLVM backend where eta-reducing the following will break
|
|
107
|
+
def performIO[T](computation) = computation(box { t =>
|
|
108
|
+
eventloop::schedule(box { resume(t) })
|
|
109
|
+
})
|
|
110
|
+
} with Promises {
|
|
111
|
+
def promise[T](task: Task[T]) = {
|
|
112
|
+
val p = promise::make[T]();
|
|
113
|
+
handle(box {
|
|
114
|
+
val t = task()
|
|
115
|
+
promise::resolve(p, t)
|
|
116
|
+
})
|
|
117
|
+
eventloop::schedule( box { resume(p) })
|
|
118
|
+
}
|
|
119
|
+
def await[T](p: Promise[T]) = promise::await(p, box { t => resume(t) })
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
handle(prog)
|
|
123
|
+
|
|
124
|
+
eventloop::start()
|
|
125
|
+
eventloop::stop()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
namespace eventloop {
|
|
129
|
+
extern io def schedule(program: () => Unit at {io, global}): Unit =
|
|
130
|
+
js "setTimeout(() => (${program})().run(), 0)"
|
|
131
|
+
llvm """
|
|
132
|
+
call void @c_timer_wait(i64 0, %Neg ${program})
|
|
133
|
+
ret %Pos zeroinitializer
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
extern io def start(): Unit =
|
|
137
|
+
js "$effekt.unit"
|
|
138
|
+
llvm """
|
|
139
|
+
; Get the default loop and run it.
|
|
140
|
+
%loop = call ptr @uv_default_loop()
|
|
141
|
+
%run_result = call i32 @uv_run(ptr %loop, i32 0)
|
|
142
|
+
|
|
143
|
+
; return Unit
|
|
144
|
+
ret %Pos zeroinitializer
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
extern io def stop(): Unit =
|
|
148
|
+
js "$effekt.unit"
|
|
149
|
+
llvm """
|
|
150
|
+
; Get the default loop, stop and close it.
|
|
151
|
+
%loop = call ptr @uv_default_loop()
|
|
152
|
+
call void @uv_stop(ptr %loop)
|
|
153
|
+
call void @uv_loop_close(ptr %loop)
|
|
154
|
+
|
|
155
|
+
; return Unit
|
|
156
|
+
ret %Pos zeroinitializer
|
|
157
|
+
"""
|
|
158
|
+
}
|