@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 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
 
@@ -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.capture(k => open(${path}, ${mode}, k))"
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.capture(k => read(${fd}, ${buffer}, ${offset}, k))"
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.capture(k => write(${fd}, ${buffer}, ${offset}, k))"
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.capture(k => close(${fd}, k))"
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 {
@@ -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: Bytes): Unit =
24
- jsNode "$effekt.capture(k => ${socket}.write(${data}, k))"
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.capture(k => ${socket}.once('data', k))"
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: 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
 
@@ -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
  """
@@ -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