@effekt-lang/effekt 0.6.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 +0 -0
- package/libraries/common/bytearray.effekt +71 -0
- package/libraries/common/effekt.effekt +8 -8
- package/libraries/common/io/filesystem.effekt +46 -62
- package/libraries/common/io/network.effekt +7 -7
- package/libraries/common/ref.effekt +1 -1
- package/libraries/common/string.effekt +3 -14
- package/libraries/llvm/bytearray.c +209 -0
- package/libraries/llvm/forward-declare-c.ll +23 -23
- package/libraries/llvm/io.c +13 -15
- package/libraries/llvm/main.c +3 -3
- package/libraries/llvm/rts.ll +267 -159
- package/libraries/llvm/types.c +3 -2
- package/licenses/eclipse public license, version 2.0 - epl-2.0.html +2 -2
- package/package.json +1 -1
- package/libraries/common/bytes.effekt +0 -115
- package/libraries/llvm/buffer.c +0 -286
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 @
|
|
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 @
|
|
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 @
|
|
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 @
|
|
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 @
|
|
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 @
|
|
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 @
|
|
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 @
|
|
242
|
+
%res = call %Pos @c_bytearray_equal(%Pos ${x}, %Pos ${y})
|
|
243
243
|
ret %Pos %res
|
|
244
244
|
"""
|
|
245
245
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module io/filesystem
|
|
2
2
|
|
|
3
|
-
import
|
|
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
|
|
38
|
-
with on[IOError].finalize { close(
|
|
37
|
+
val file = open(path, ReadOnly());
|
|
38
|
+
with on[IOError].finalize { close(file) }
|
|
39
39
|
|
|
40
|
-
val
|
|
41
|
-
var
|
|
42
|
-
var
|
|
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(
|
|
45
|
+
read(file, buffer, offset, chunkSize, -1) match {
|
|
47
46
|
case 0 =>
|
|
48
|
-
buffer.
|
|
49
|
-
case n and n < 0 => panic("Error!")
|
|
47
|
+
buffer.resize(offset).toUTF8
|
|
50
48
|
case n =>
|
|
51
49
|
offset = offset + n
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
71
|
-
with on[IOError].finalize { close(
|
|
62
|
+
val file = open(path, WriteOnly());
|
|
63
|
+
with on[IOError].finalize { close(file) }
|
|
72
64
|
|
|
73
|
-
val
|
|
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(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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(
|
|
118
|
-
internal::checkResult(internal::read(
|
|
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(
|
|
121
|
-
internal::checkResult(internal::write(
|
|
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(
|
|
124
|
-
internal::checkResult(internal::close(
|
|
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,
|
|
178
|
-
if (err) { callback(err.errno) } else { callback(
|
|
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(
|
|
183
|
-
let
|
|
184
|
-
fs.read(
|
|
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(
|
|
190
|
-
let
|
|
191
|
-
fs.write(
|
|
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(
|
|
197
|
-
fs.close(
|
|
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.capture(
|
|
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(
|
|
218
|
-
jsNode "$effekt.capture(
|
|
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 ${
|
|
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(
|
|
225
|
-
jsNode "$effekt.capture(
|
|
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 ${
|
|
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(
|
|
232
|
-
jsNode "$effekt.capture(
|
|
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 ${
|
|
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
|
|
3
|
+
import bytearray
|
|
4
4
|
import io
|
|
5
5
|
|
|
6
6
|
namespace js {
|
|
@@ -20,19 +20,19 @@ namespace js {
|
|
|
20
20
|
extern io def listen(server: JSServer, port: Int, host: String, listener: JSSocket => Unit at {io, async, global}): Unit =
|
|
21
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:
|
|
24
|
-
jsNode "$effekt.capture(
|
|
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):
|
|
27
|
-
jsNode "$effekt.capture(
|
|
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
30
|
jsNode "$effekt.capture(k => ${socket}.end(k))"
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
interface Socket {
|
|
34
|
-
def send(message:
|
|
35
|
-
def receive():
|
|
34
|
+
def send(message: ByteArray): Unit
|
|
35
|
+
def receive(): ByteArray
|
|
36
36
|
def end(): Unit
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -12,7 +12,7 @@ extern js """
|
|
|
12
12
|
/// Global, mutable references
|
|
13
13
|
extern type Ref[T]
|
|
14
14
|
|
|
15
|
-
/// Allocates a new reference
|
|
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
|
-
//
|
|
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 @
|
|
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 @
|
|
340
|
+
%x = call %Int @c_bytearray_character_at(%Pos ${str}, %Int ${n})
|
|
352
341
|
ret %Int %x
|
|
353
342
|
"""
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#ifndef EFFEKT_BYTEARRAY_C
|
|
2
|
+
#define EFFEKT_BYTEARRAY_C
|
|
3
|
+
|
|
4
|
+
#include <string.h> // For memcopy
|
|
5
|
+
|
|
6
|
+
/** We represent bytearrays like positive types.
|
|
7
|
+
*
|
|
8
|
+
* - The field `tag` contains the size
|
|
9
|
+
* - The field `obj` points to memory with the following layout:
|
|
10
|
+
*
|
|
11
|
+
* +--[ Header ]--+--------------+
|
|
12
|
+
* | Rc | Eraser | Contents ... |
|
|
13
|
+
* +--------------+--------------+
|
|
14
|
+
*
|
|
15
|
+
* The eraser does nothing.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
void c_bytearray_erase_noop(void *envPtr) { (void)envPtr; }
|
|
20
|
+
|
|
21
|
+
struct Pos c_bytearray_new(const Int size) {
|
|
22
|
+
void *objPtr = malloc(sizeof(struct Header) + size);
|
|
23
|
+
struct Header *headerPtr = objPtr;
|
|
24
|
+
*headerPtr = (struct Header) { .rc = 0, .eraser = c_bytearray_erase_noop, };
|
|
25
|
+
return (struct Pos) {
|
|
26
|
+
.tag = size,
|
|
27
|
+
.obj = objPtr,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Int c_bytearray_size(const struct Pos arr) {
|
|
32
|
+
return arr.tag;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
uint8_t* c_bytearray_data(const struct Pos arr) {
|
|
36
|
+
return arr.obj + sizeof(struct Header);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Byte c_bytearray_get(const struct Pos arr, const Int index) {
|
|
40
|
+
Byte *dataPtr = arr.obj + sizeof(struct Header);
|
|
41
|
+
Byte element = dataPtr[index];
|
|
42
|
+
return element;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
struct Pos c_bytearray_set(const struct Pos arr, const Int index, const Byte value) {
|
|
46
|
+
Byte *dataPtr = arr.obj + sizeof(struct Header);
|
|
47
|
+
dataPtr[index] = value;
|
|
48
|
+
return Unit;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
struct Pos c_bytearray_construct(const uint64_t n, const uint8_t *data) {
|
|
52
|
+
struct Pos arr = c_bytearray_new(n);
|
|
53
|
+
|
|
54
|
+
memcpy(c_bytearray_data(arr), data, n);
|
|
55
|
+
|
|
56
|
+
return arr;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Complex Operations
|
|
60
|
+
|
|
61
|
+
struct Pos c_bytearray_from_nullterminated_string(const char *data) {
|
|
62
|
+
uint64_t n = 0;
|
|
63
|
+
while (data[++n]);
|
|
64
|
+
|
|
65
|
+
return c_bytearray_construct(n, (uint8_t*)data);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
char* c_bytearray_into_nullterminated_string(const struct Pos arr) {
|
|
69
|
+
uint64_t size = c_bytearray_size(arr);
|
|
70
|
+
|
|
71
|
+
char* result = (char*)malloc(size + 1);
|
|
72
|
+
|
|
73
|
+
memcpy(result, c_bytearray_data(arr), size);
|
|
74
|
+
|
|
75
|
+
result[size] = '\0';
|
|
76
|
+
|
|
77
|
+
// TODO we should erase the input
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// TODO do this in Effekt
|
|
82
|
+
struct Pos c_bytearray_show_Int(const Int n) {
|
|
83
|
+
char str[24];
|
|
84
|
+
sprintf(str, "%" PRId64, n);
|
|
85
|
+
return c_bytearray_from_nullterminated_string(str);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// TODO do this in Effekt
|
|
89
|
+
struct Pos c_bytearray_show_Char(const uint64_t n) {
|
|
90
|
+
char str[5] = {0}; // Max 4 bytes for UTF-8 + 1 for null terminator
|
|
91
|
+
unsigned char *buf = (unsigned char *)str;
|
|
92
|
+
|
|
93
|
+
if (n < 0x80) { // 1-byte sequence
|
|
94
|
+
buf[0] = (unsigned char)n;
|
|
95
|
+
} else if (n < 0x800) { // 2-byte sequence
|
|
96
|
+
buf[0] = (unsigned char)(0xC0 | (n >> 6));
|
|
97
|
+
buf[1] = (unsigned char)(0x80 | (n & 0x3F));
|
|
98
|
+
} else if (n < 0x10000) { // 3-byte sequence
|
|
99
|
+
buf[0] = (unsigned char)(0xE0 | (n >> 12));
|
|
100
|
+
buf[1] = (unsigned char)(0x80 | ((n >> 6) & 0x3F));
|
|
101
|
+
buf[2] = (unsigned char)(0x80 | (n & 0x3F));
|
|
102
|
+
} else if (n < 0x110000) { // 4-byte sequence
|
|
103
|
+
buf[0] = (unsigned char)(0xF0 | (n >> 18));
|
|
104
|
+
buf[1] = (unsigned char)(0x80 | ((n >> 12) & 0x3F));
|
|
105
|
+
buf[2] = (unsigned char)(0x80 | ((n >> 6) & 0x3F));
|
|
106
|
+
buf[3] = (unsigned char)(0x80 | (n & 0x3F));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return c_bytearray_from_nullterminated_string(str);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// TODO do this in Effekt
|
|
113
|
+
struct Pos c_bytearray_show_Byte(const Byte n) {
|
|
114
|
+
char str[4]; // Byte values range from 0 to 255, 3 characters + null terminator
|
|
115
|
+
sprintf(str, "%" PRIu8, n);
|
|
116
|
+
return c_bytearray_from_nullterminated_string(str);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// TODO do this in Effekt
|
|
120
|
+
struct Pos c_bytearray_show_Double(const Double x) {
|
|
121
|
+
char str[64]; // TODO is this large enough? Possibly use snprintf first
|
|
122
|
+
sprintf(str, "%g", x);
|
|
123
|
+
return c_bytearray_from_nullterminated_string(str);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// TODO do this in Effekt
|
|
127
|
+
struct Pos c_bytearray_concatenate(const struct Pos left, const struct Pos right) {
|
|
128
|
+
const struct Pos concatenated = c_bytearray_new(c_bytearray_size(left) + c_bytearray_size(right));
|
|
129
|
+
for (int64_t j = 0; j < c_bytearray_size(concatenated); ++j)
|
|
130
|
+
c_bytearray_data(concatenated)[j]
|
|
131
|
+
= j < c_bytearray_size(left)
|
|
132
|
+
? c_bytearray_data(left)[j]
|
|
133
|
+
: c_bytearray_data(right)[j - c_bytearray_size(left)];
|
|
134
|
+
|
|
135
|
+
erasePositive(left);
|
|
136
|
+
erasePositive(right);
|
|
137
|
+
return concatenated;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// TODO do this in Effekt
|
|
141
|
+
struct Pos c_bytearray_equal(const struct Pos left, const struct Pos right) {
|
|
142
|
+
uint64_t left_size = c_bytearray_size(left);
|
|
143
|
+
uint64_t right_size = c_bytearray_size(right);
|
|
144
|
+
if (left_size != right_size) {
|
|
145
|
+
erasePositive(left);
|
|
146
|
+
erasePositive(right);
|
|
147
|
+
return BooleanFalse;
|
|
148
|
+
}
|
|
149
|
+
for (uint64_t j = 0; j < left_size; ++j) {
|
|
150
|
+
if (c_bytearray_data(left)[j] != c_bytearray_data(right)[j]) {
|
|
151
|
+
erasePositive(left);
|
|
152
|
+
erasePositive(right);
|
|
153
|
+
return BooleanFalse;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
erasePositive(left);
|
|
157
|
+
erasePositive(right);
|
|
158
|
+
return BooleanTrue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// TODO deprecate
|
|
162
|
+
struct Pos c_bytearray_substring(const struct Pos str, uint64_t start, uint64_t end) {
|
|
163
|
+
const struct Pos substr = c_bytearray_new(end - start);
|
|
164
|
+
for (int64_t j = 0; j < c_bytearray_size(substr); ++j) {
|
|
165
|
+
c_bytearray_data(substr)[j] = c_bytearray_data(str)[start+j];
|
|
166
|
+
}
|
|
167
|
+
erasePositive(str);
|
|
168
|
+
return substr;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// TODO deprecate
|
|
172
|
+
uint32_t c_bytearray_character_at(const struct Pos str, const uint64_t index) {
|
|
173
|
+
const uint8_t *bytes = c_bytearray_data(str);
|
|
174
|
+
uint8_t first_byte = bytes[index];
|
|
175
|
+
uint32_t character = 0;
|
|
176
|
+
|
|
177
|
+
uint32_t length = c_bytearray_size(str);
|
|
178
|
+
|
|
179
|
+
if (first_byte < 0x80) {
|
|
180
|
+
// Single-byte character (0xxxxxxx)
|
|
181
|
+
character = first_byte;
|
|
182
|
+
} else if ((first_byte & 0xE0) == 0xC0) {
|
|
183
|
+
// Two-byte character (110xxxxx 10xxxxxx)
|
|
184
|
+
if (index + 1 < length) {
|
|
185
|
+
character = ((first_byte & 0x1F) << 6) |
|
|
186
|
+
(bytes[index + 1] & 0x3F);
|
|
187
|
+
}
|
|
188
|
+
} else if ((first_byte & 0xF0) == 0xE0) {
|
|
189
|
+
// Three-byte character (1110xxxx 10xxxxxx 10xxxxxx)
|
|
190
|
+
if (index + 2 < length) {
|
|
191
|
+
character = ((first_byte & 0x0F) << 12) |
|
|
192
|
+
((bytes[index + 1] & 0x3F) << 6) |
|
|
193
|
+
(bytes[index + 2] & 0x3F);
|
|
194
|
+
}
|
|
195
|
+
} else if ((first_byte & 0xF8) == 0xF0) {
|
|
196
|
+
// Four-byte character (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
|
|
197
|
+
if (index + 3 < length) {
|
|
198
|
+
character = ((first_byte & 0x07) << 18) |
|
|
199
|
+
((bytes[index + 1] & 0x3F) << 12) |
|
|
200
|
+
((bytes[index + 2] & 0x3F) << 6) |
|
|
201
|
+
(bytes[index + 3] & 0x3F);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
erasePositive(str);
|
|
206
|
+
return character;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#endif
|