@effekt-lang/effekt 0.5.0 → 0.7.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 CHANGED
Binary file
@@ -0,0 +1,71 @@
1
+ module bytearray
2
+
3
+ /**
4
+ * A memory managed, mutable, fixed-length array of bytes.
5
+ */
6
+ extern type ByteArray
7
+ // = llvm "%Pos"
8
+ // = js "Uint8Array"
9
+
10
+
11
+ /// Allocates a new bytearray with the given `size`, its values are undefined.
12
+ extern global def allocate(size: Int): ByteArray =
13
+ js "(new Uint8Array(${size}))"
14
+ llvm """
15
+ %arr = call %Pos @c_bytearray_new(%Int ${size})
16
+ ret %Pos %arr
17
+ """
18
+
19
+ extern pure def size(arr: ByteArray): Int =
20
+ js "${arr}.length"
21
+ llvm """
22
+ %size = call %Int @c_bytearray_size(%Pos ${arr})
23
+ ret %Int %size
24
+ """
25
+
26
+ extern global def unsafeGet(arr: ByteArray, index: Int): Byte =
27
+ js "(${arr})[${index}]"
28
+ llvm """
29
+ %byte = call %Byte @c_bytearray_get(%Pos ${arr}, %Int ${index})
30
+ ret %Byte %byte
31
+ """
32
+
33
+ extern global def unsafeSet(arr: ByteArray, index: Int, value: Byte): Unit =
34
+ js "bytearray$set(${arr}, ${index}, ${value})"
35
+ llvm """
36
+ %z = call %Pos @c_bytearray_set(%Pos ${arr}, %Int ${index}, %Byte ${value})
37
+ ret %Pos %z
38
+ """
39
+
40
+ def resize(source: ByteArray, size: Int): ByteArray = {
41
+ val target = allocate(size)
42
+ val n = min(source.size, target.size)
43
+ def go(i: Int): ByteArray =
44
+ if (i < n) {
45
+ target.unsafeSet(i, source.unsafeGet(i))
46
+ go(i + 1)
47
+ } else {
48
+ target
49
+ }
50
+ go(0)
51
+ }
52
+
53
+ extern pure def fromUTF8(str: String): ByteArray =
54
+ js "(new TextEncoder().encode(${str}))"
55
+ llvm """
56
+ ret %Pos ${str}
57
+ """
58
+
59
+ extern pure def toUTF8(arr: ByteArray): String =
60
+ js "(new TextDecoder('utf-8').decode(${arr}))"
61
+ // assuming the buffer is already in UTF-8
62
+ llvm """
63
+ ret %Pos ${arr}
64
+ """
65
+
66
+ extern js """
67
+ function bytearray$set(bytes, index, value) {
68
+ bytes[index] = value;
69
+ return $effekt.unit;
70
+ }
71
+ """
@@ -56,7 +56,7 @@ extern pure def show(value: Int): String =
56
56
  js "'' + ${value}"
57
57
  chez "(show-number ${value})"
58
58
  llvm """
59
- %z = call %Pos @c_buffer_show_Int(%Int ${value})
59
+ %z = call %Pos @c_bytearray_show_Int(%Int ${value})
60
60
  ret %Pos %z
61
61
  """
62
62
 
@@ -66,7 +66,7 @@ extern pure def show(value: Double): String =
66
66
  js "'' + ${value}"
67
67
  chez "(show-number ${value})"
68
68
  llvm """
69
- %z = call %Pos @c_buffer_show_Double(%Double ${value})
69
+ %z = call %Pos @c_bytearray_show_Double(%Double ${value})
70
70
  ret %Pos %z
71
71
  """
72
72
 
@@ -80,13 +80,13 @@ extern pure def show(value: Char): String =
80
80
  js "String.fromCodePoint(${value})"
81
81
  chez "(string (integer->char ${value}))"
82
82
  llvm """
83
- %z = call %Pos @c_buffer_show_Char(%Int ${value})
83
+ %z = call %Pos @c_bytearray_show_Char(%Int ${value})
84
84
  ret %Pos %z
85
85
  """
86
86
 
87
87
  extern pure def show(value: Byte): String =
88
88
  llvm """
89
- %z = call %Pos @c_buffer_show_Byte(i8 ${value})
89
+ %z = call %Pos @c_bytearray_show_Byte(i8 ${value})
90
90
  ret %Pos %z
91
91
  """
92
92
 
@@ -105,7 +105,7 @@ extern pure def infixConcat(s1: String, s2: String): String =
105
105
  js "((${s1}) + (${s2}))"
106
106
  chez "(string-append ${s1} ${s2})"
107
107
  llvm """
108
- %spz = call %Pos @c_buffer_concatenate(%Pos ${s1}, %Pos ${s2})
108
+ %spz = call %Pos @c_bytearray_concatenate(%Pos ${s1}, %Pos ${s2})
109
109
  ret %Pos %spz
110
110
  """
111
111
 
@@ -113,7 +113,7 @@ extern pure def length(str: String): Int =
113
113
  js "${str}.length"
114
114
  chez "(string-length ${str})"
115
115
  llvm """
116
- %x = call %Int @c_buffer_length(%Pos ${str})
116
+ %x = call %Int @c_bytearray_size(%Pos ${str})
117
117
  call void @erasePositive(%Pos ${str})
118
118
  ret %Int %x
119
119
  """
@@ -122,7 +122,7 @@ extern pure def unsafeSubstring(str: String, from: Int, to: Int): String =
122
122
  js "${str}.substring(${from}, ${to})"
123
123
  chez "(substring ${str} ${from} ${to})" // potentially raises: "Exception in substring: ..."
124
124
  llvm """
125
- %x = call %Pos @c_buffer_substring(%Pos ${str}, i64 ${from}, i64 ${to})
125
+ %x = call %Pos @c_bytearray_substring(%Pos ${str}, i64 ${from}, i64 ${to})
126
126
  ret %Pos %x
127
127
  """
128
128
 
@@ -239,7 +239,7 @@ extern pure def infixEq(x: String, y: String): Bool =
239
239
  js "${x} === ${y}"
240
240
  chez "(equal? ${x} ${y})"
241
241
  llvm """
242
- %res = call %Pos @c_buffer_eq(%Pos ${x}, %Pos ${y})
242
+ %res = call %Pos @c_bytearray_equal(%Pos ${x}, %Pos ${y})
243
243
  ret %Pos %res
244
244
  """
245
245
 
@@ -38,8 +38,7 @@ namespace js {
38
38
  jsNode "${console}.close()"
39
39
 
40
40
  extern async def readLine(console: JSConsole): String =
41
- jsNode "$effekt.callcc(callback => ${console}.once('line', callback))"
42
-
41
+ jsNode "$effekt.capture(k => ${console}.once('line', k))"
43
42
  extern io def writeLine(console: JSConsole, message: String): Unit =
44
43
  jsNode "${console}.output.write(${message} + '\\n')"
45
44
  }
@@ -1,6 +1,6 @@
1
1
  module io/filesystem
2
2
 
3
- import bytes
3
+ import bytearray
4
4
 
5
5
  import io
6
6
  import io/error
@@ -34,29 +34,21 @@ type File = Int
34
34
 
35
35
  /// Reads a file at given path as utf8 encoded string.
36
36
  def readFile(path: String): String / Exception[IOError] = {
37
- val fd = open(path, ReadOnly());
38
- with on[IOError].finalize { close(fd) }
37
+ val file = open(path, ReadOnly());
38
+ with on[IOError].finalize { close(file) }
39
39
 
40
- val readSize = 1048576 // 1MB
41
- var size = readSize
42
- var buffer = bytes(size)
43
- var offset = 0;
40
+ val chunkSize = 1048576 // 1MB
41
+ var buffer = bytearray::allocate(chunkSize)
42
+ var offset = 0
44
43
 
45
44
  def go(): String = {
46
- read(fd, buffer.slice(offset, min(readSize, size - offset)), -1) match {
45
+ read(file, buffer, offset, chunkSize, -1) match {
47
46
  case 0 =>
48
- buffer.slice(0, offset).toUTF8
49
- case n and n < 0 => panic("Error!")
47
+ buffer.resize(offset).toUTF8
50
48
  case n =>
51
49
  offset = offset + n
52
-
53
- // we are at maximum capacity
54
- if (n == readSize && (offset + readSize) > size) {
55
- val newSize = size * 2
56
- val newBuffer = bytes(newSize)
57
- copy(buffer, newBuffer, 0, 0, size)
58
- buffer = newBuffer
59
- size = newSize
50
+ if (offset + chunkSize > buffer.size) {
51
+ buffer = buffer.resize(2 * buffer.size)
60
52
  }
61
53
  go()
62
54
  }
@@ -67,26 +59,18 @@ def readFile(path: String): String / Exception[IOError] = {
67
59
 
68
60
  /// Writes the (utf8 encoded) string `contents` into the specified file.
69
61
  def writeFile(path: String, contents: String): Unit / Exception[IOError] = {
70
- val fd = open(path, WriteOnly());
71
- with on[IOError].finalize { close(fd) }
62
+ val file = open(path, WriteOnly());
63
+ with on[IOError].finalize { close(file) }
72
64
 
73
- val writeSize = 1048576 // 1MB
74
-
75
- // this induces a memcpy that is not strictly necessary, since we use the buffer read-only
65
+ val chunkSize = 1048576 // 1MB
76
66
  val buffer = contents.fromUTF8
77
- val size = buffer.size
78
-
79
67
  var offset = 0;
80
- def remaining() = size - offset
81
68
 
82
- def go(): Unit =
83
- write(fd, buffer.slice(offset, min(writeSize, remaining())), -1) match {
84
- case 0 => ()
85
- case n and n < 0 => panic("Error!")
86
- case n =>
87
- offset = offset + n;
88
- if (remaining() > 0) go()
89
- }
69
+ def go(): Unit = {
70
+ val n = write(file, buffer, offset, min(buffer.size - offset, chunkSize), -1)
71
+ offset = offset + n
72
+ if (offset < buffer.size) { go() }
73
+ }
90
74
 
91
75
  go()
92
76
  }
@@ -114,14 +98,14 @@ def filesystem[R] { program: => R / Files }: R / Exception[IOError] = // TODO mo
114
98
  def open(path: String, mode: Mode): File / Exception[IOError] =
115
99
  internal::checkResult(internal::open(path, mode))
116
100
 
117
- def read(fd: File, buffer: Bytes, offset: Int): Int / Exception[IOError] =
118
- internal::checkResult(internal::read(fd, buffer, offset))
101
+ def read(file: File, buffer: ByteArray, offset: Int, size: Int, position: Int): Int / Exception[IOError] =
102
+ internal::checkResult(internal::read(file, buffer, offset, size, position))
119
103
 
120
- def write(fd: File, buffer: Bytes, offset: Int): Int / Exception[IOError] =
121
- internal::checkResult(internal::write(fd, buffer, offset))
104
+ def write(file: File, buffer: ByteArray, offset: Int, size: Int, position: Int): Int / Exception[IOError] =
105
+ internal::checkResult(internal::write(file, buffer, offset, size, position))
122
106
 
123
- def close(fd: File): Unit / Exception[IOError] = {
124
- internal::checkResult(internal::close(fd)); ()
107
+ def close(file: File): Unit / Exception[IOError] = {
108
+ internal::checkResult(internal::close(file)); ()
125
109
  }
126
110
 
127
111
  namespace internal {
@@ -174,27 +158,27 @@ namespace internal {
174
158
  const fs = require("fs");
175
159
 
176
160
  function open(path, mode, callback) {
177
- fs.open(path, modeName(mode), (err, fd) => {
178
- if (err) { callback(err.errno) } else { callback(fd) }
161
+ fs.open(path, modeName(mode), (err, file) => {
162
+ if (err) { callback(err.errno) } else { callback(file) }
179
163
  })
180
164
  }
181
165
 
182
- function read(fd, buffer, offset, callback) {
183
- let position = offset === -1 ? null : offset;
184
- fs.read(fd, toBuffer(buffer), 0, buffer.length, position, (err, bytesRead) => {
166
+ function read(file, buffer, offset, size, position, callback) {
167
+ let positionOrNull = position === -1 ? null : position;
168
+ fs.read(file, toBuffer(buffer), offset, size, positionOrNull, (err, bytesRead) => {
185
169
  if (err) { callback(err.errno) } else { callback(bytesRead) }
186
170
  })
187
171
  }
188
172
 
189
- function write(fd, buffer, offset, callback) {
190
- let position = offset === -1 ? null : offset;
191
- fs.write(fd, toBuffer(buffer), 0, buffer.length, position, (err, bytesWritten) => {
173
+ function write(file, buffer, offset, size, position, callback) {
174
+ let positionOrNull = position === -1 ? null : position;
175
+ fs.write(file, toBuffer(buffer), offset, size, positionOrNull, (err, bytesWritten) => {
192
176
  if (err) { callback(err.errno) } else { callback(bytesWritten) }
193
177
  })
194
178
  }
195
179
 
196
- function close(fd, callback) {
197
- fs.close(fd, (err) => {
180
+ function close(file, callback) {
181
+ fs.close(file, (err) => {
198
182
  if (err) { callback(err.errno) } else { callback(0) }
199
183
  })
200
184
  }
@@ -202,36 +186,36 @@ namespace internal {
202
186
 
203
187
  extern llvm """
204
188
  declare void @c_fs_open(%Pos, %Pos, %Stack)
205
- declare void @c_fs_read(%Int, %Pos, %Int, %Stack)
206
- declare void @c_fs_write(%Int, %Pos, %Int, %Stack)
189
+ declare void @c_fs_read(%Int, %Pos, %Int, %Int, %Int, %Stack)
190
+ declare void @c_fs_write(%Int, %Pos, %Int, %Int, %Int, %Stack)
207
191
  declare void @c_fs_close(%Int, %Stack)
208
192
  """
209
193
 
210
194
  extern async def open(path: String, mode: Mode): Int =
211
- jsNode "$effekt.callcc(callback => open(${path}, ${mode}, callback))"
195
+ jsNode "$effekt.capture(callback => open(${path}, ${mode}, callback))"
212
196
  llvm """
213
197
  call void @c_fs_open(%Pos ${path}, %Pos ${mode}, %Stack %stack)
214
198
  ret void
215
199
  """
216
200
 
217
- extern async def read(fd: Int, buffer: Bytes, offset: Int): Int =
218
- jsNode "$effekt.callcc(callback => read(${fd}, ${buffer}, ${offset}, callback))"
201
+ extern async def read(file: Int, buffer: ByteArray, offset: Int, size: Int, position: Int): Int =
202
+ jsNode "$effekt.capture(callback => read(${file}, ${buffer}, ${offset}, ${size}, ${position}, callback))"
219
203
  llvm """
220
- call void @c_fs_read(%Int ${fd}, %Pos ${buffer}, %Int ${offset}, %Stack %stack)
204
+ call void @c_fs_read(%Int ${file}, %Pos ${buffer}, %Int ${offset}, %Int ${size}, %Int ${position}, %Stack %stack)
221
205
  ret void
222
206
  """
223
207
 
224
- extern async def write(fd: Int, buffer: Bytes, offset: Int): Int =
225
- jsNode "$effekt.callcc(callback => write(${fd}, ${buffer}, ${offset}, callback))"
208
+ extern async def write(file: Int, buffer: ByteArray, offset: Int, size: Int, position: Int): Int =
209
+ jsNode "$effekt.capture(callback => write(${file}, ${buffer}, ${offset}, ${size}, ${position}, callback))"
226
210
  llvm """
227
- call void @c_fs_write(%Int ${fd}, %Pos ${buffer}, %Int ${offset}, %Stack %stack)
211
+ call void @c_fs_write(%Int ${file}, %Pos ${buffer}, %Int ${offset}, %Int ${size}, %Int ${position}, %Stack %stack)
228
212
  ret void
229
213
  """
230
214
 
231
- extern async def close(fd: Int): Int =
232
- jsNode "$effekt.callcc(callback => close(${fd}, callback))"
215
+ extern async def close(file: Int): Int =
216
+ jsNode "$effekt.capture(callback => close(${file}, callback))"
233
217
  llvm """
234
- call void @c_fs_close(%Int ${fd}, %Stack %stack)
218
+ call void @c_fs_close(%Int ${file}, %Stack %stack)
235
219
  ret void
236
220
  """
237
221
 
@@ -1,6 +1,6 @@
1
1
  module io/network
2
2
 
3
- import bytes
3
+ import bytearray
4
4
  import io
5
5
 
6
6
  namespace js {
@@ -18,21 +18,21 @@ namespace js {
18
18
  extern io def server(): JSServer =
19
19
  jsNode "net.createServer()"
20
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())"
21
+ jsNode "listen(${server}, ${port}, ${host}, (socket) => $effekt.runToplevel((ks, k) => (${listener})(socket, ks, k)))"
22
22
 
23
- extern async def send(socket: JSSocket, data: Bytes): Unit =
24
- jsNode "$effekt.callcc(callback => ${socket}.write(${data}, callback))"
23
+ extern async def send(socket: JSSocket, data: ByteArray): Unit =
24
+ jsNode "$effekt.capture(callback => ${socket}.write(${data}, callback))"
25
25
 
26
- extern async def receive(socket: JSSocket): Bytes =
27
- jsNode "$effekt.callcc(callback => ${socket}.once('data', callback))"
26
+ extern async def receive(socket: JSSocket): ByteArray =
27
+ jsNode "$effekt.capture(callback => ${socket}.once('data', callback))"
28
28
 
29
29
  extern async def end(socket: JSSocket): Unit =
30
- jsNode "$effekt.callcc(callback => ${socket}.end(callback))"
30
+ jsNode "$effekt.capture(k => ${socket}.end(k))"
31
31
  }
32
32
 
33
33
  interface Socket {
34
- def send(message: Bytes): Unit
35
- def receive(): Bytes
34
+ def send(message: ByteArray): Unit
35
+ def receive(): ByteArray
36
36
  def end(): Unit
37
37
  }
38
38
 
@@ -5,7 +5,7 @@ extern llvm """
5
5
  """
6
6
 
7
7
  extern async def wait(millis: Int): Unit =
8
- js "$effekt.callcc(callback => setTimeout(() => callback($effekt.unit), ${millis}))"
8
+ js "$effekt.capture(k => setTimeout(() => k($effekt.unit), ${millis}))"
9
9
  llvm """
10
10
  call void @c_timer_start(%Int ${millis}, %Stack %stack)
11
11
  ret void
@@ -13,7 +13,7 @@ extern llvm """
13
13
  """
14
14
 
15
15
  extern async def spawn(task: Task[Unit]): Unit =
16
- js "$effekt.callcc(callback => { setTimeout(() => callback($effekt.unit), 0); ${task}().run()})"
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
19
  call void @run(%Neg ${task})
@@ -21,14 +21,14 @@ extern async def spawn(task: Task[Unit]): Unit =
21
21
  """
22
22
 
23
23
  extern async def yield(): Unit =
24
- js "$effekt.callcc(callback => setTimeout(() => callback($effekt.unit), 0))"
24
+ js "$effekt.capture(k => setTimeout(() => k($effekt.unit), 0))"
25
25
  llvm """
26
26
  call void @c_yield(%Stack %stack)
27
27
  ret void
28
28
  """
29
29
 
30
30
  extern async def abort(): Nothing =
31
- js "$effekt.callcc(callback => { return })"
31
+ js "$effekt.capture(k => undefined)"
32
32
  llvm """
33
33
  call void @eraseStack(%Stack %stack)
34
34
  ret void
@@ -55,14 +55,14 @@ extern llvm """
55
55
  """
56
56
 
57
57
  extern async def await[T](promise: Promise[T]): T =
58
- js "$effekt.callcc(callback => ${promise}.promise.then(res => callback(res)))"
58
+ js "$effekt.capture(k => ${promise}.promise.then(k))"
59
59
  llvm """
60
60
  call void @c_promise_await(%Pos ${promise}, %Stack %stack)
61
61
  ret void
62
62
  """
63
63
 
64
64
  extern async def resolve[T](promise: Promise[T], value: T): Unit =
65
- js "$effekt.callcc(callback => { ${promise}.resolve(${value}); callback($effekt.unit)})"
65
+ js "$effekt.capture(k => { ${promise}.resolve(${value}); return k($effekt.unit) })"
66
66
  llvm """
67
67
  call void @c_promise_resolve(%Pos ${promise}, %Pos ${value}, %Stack %stack)
68
68
  ret void
@@ -12,7 +12,7 @@ extern js """
12
12
  /// Global, mutable references
13
13
  extern type Ref[T]
14
14
 
15
- /// Allocates a new reference of size `size`, keeping its value _undefined_.
15
+ /// Allocates a new reference, keeping its value _undefined_.
16
16
  /// Prefer using `ref` constructor instead to ensure that the value is defined.
17
17
  extern global def allocate[T](): Ref[T] =
18
18
  js "{ value: undefined }"
@@ -14,7 +14,6 @@ import result
14
14
  /**
15
15
  * Strings
16
16
  * - JS: Strings are represented as UTF-16 code units where some characters take 2 slots (surrogate pairs)
17
- * - ML: Strings are sequences of 8-bit characters (https://smlfamily.github.io/Basis/string.html)
18
17
  * - Chez: Strings are sequences of unicode characters (?)
19
18
  * - LLVM: UTF-8 (characters can take from 1-4 bytes).
20
19
  */
@@ -237,14 +236,14 @@ def printing[T] { prog: => T / Stream }: T = <>
237
236
  // ----------
238
237
  //
239
238
  // JS: Int (Unicode codepoints)
240
- // CS: ?
239
+ // Chez: ?
241
240
  // LLVM: i64 representing utf-8 (varying length 1-4 bytes)
242
241
 
243
242
  extern pure def toString(ch: Char): String =
244
243
  js "String.fromCodePoint(${ch})"
245
244
  chez "(string (integer->char ${ch}))"
246
245
  llvm """
247
- %z = call %Pos @c_buffer_show_Char(%Int ${ch})
246
+ %z = call %Pos @c_bytearray_show_Char(%Int ${ch})
248
247
  ret %Pos %z
249
248
  """
250
249
 
@@ -324,16 +323,6 @@ def utf16UnitCount(codepoint: Char): Int = codepoint match {
324
323
  case c => panic("Not a valid code point")
325
324
  }
326
325
 
327
- // TODO this is copied from the LLVM-backend stdlib. Do we need this?
328
- // def showQuoted(s: String): String =
329
- // "\x22" ++ s.map { c =>
330
- // if(c == "\x22") {
331
- // "\\\x22"
332
- // } else if (c == "\\") {
333
- // "\\\\"
334
- // } else c
335
- // } ++ "\x22"
336
-
337
326
  extern pure def charWidth(c: Char): Int =
338
327
  // JavaScript strings are UTF-16 where every unicode character after 0xffff takes two units
339
328
  js "(${c} > 0xffff) ? 2 : 1"
@@ -348,6 +337,6 @@ extern pure def unsafeCharAt(str: String, n: Int): Char =
348
337
  js "${str}.codePointAt(${n})"
349
338
  chez "(char->integer (string-ref ${str} ${n}))"
350
339
  llvm """
351
- %x = call %Int @c_buffer_character_at(%Pos ${str}, i64 ${n})
340
+ %x = call %Int @c_bytearray_character_at(%Pos ${str}, %Int ${n})
352
341
  ret %Int %x
353
342
  """
@@ -54,4 +54,8 @@ $effekt.println = function println$impl(str) {
54
54
  console.log(str); return $effekt.unit;
55
55
  }
56
56
 
57
- $effekt.unit = { __unit: true }
57
+ $effekt.unit = { __unit: true }
58
+
59
+ $effekt.emptyMatch = function() { throw "empty match" }
60
+
61
+ $effekt.hole = function() { throw "not implemented, yet" }