@effekt-lang/effekt 0.77.0 → 0.79.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
@@ -63,9 +63,10 @@ extern def unsafeAllocate[T](size: Int) at global: Array[T] =
63
63
 
64
64
  /// Fills a given array in-place.
65
65
  def fill[T](arr: Array[T], filler: T): Unit = {
66
- arr.foreachIndex { (idx, _) =>
67
- arr.set(idx, filler)
68
- }
66
+ val n = arr.size
67
+ def go(i: Int): Unit =
68
+ if (i < n) { arr.set(i, filler); go(i + 1) }
69
+ go(0)
69
70
  }
70
71
 
71
72
  /// Creates a new Array of size `size` filled with the value `init`
@@ -107,7 +108,7 @@ define void @assertInbounds(i64 %index, %Pos %arr) alwaysinline {
107
108
  %in_bounds = icmp ult i64 %index, %size
108
109
  br i1 %in_bounds, label %ok, label %out
109
110
  out:
110
- call void @exit(i64 1)
111
+ call void @exit(i32 1)
111
112
  unreachable
112
113
  ok:
113
114
  ret void
@@ -61,7 +61,7 @@ define void @assertInboundsByteArray(i64 %index, %Pos %arr) alwaysinline {
61
61
  %in_bounds = icmp ult i64 %index, %size
62
62
  br i1 %in_bounds, label %ok, label %out
63
63
  out:
64
- call void @exit(i64 1)
64
+ call void @exit(i32 1)
65
65
  unreachable
66
66
  ok:
67
67
  ret void
@@ -113,17 +113,35 @@ def build(size: Int) { index: Int => Byte }: ByteArray = {
113
113
  arr
114
114
  }
115
115
 
116
+ /// Copies `length`-many bytes from `from` to `to`
117
+ /// starting at `start` (in `from`) and `offset` (in `to`).
118
+ /// The ranges may overlap, even within the same bytearray: the source bytes are
119
+ /// read before any are written. Panics if out of bounds.
120
+ ///
121
+ /// O(N)
122
+ def copy(from: ByteArray, start: Int, to: ByteArray, offset: Int, length: Int): Unit =
123
+ if (length < 0 || start < 0 || offset < 0
124
+ || start + length > from.size || offset + length > to.size) {
125
+ <{ "bytearray::copy: range out of bounds" }>
126
+ } else {
127
+ unsafeCopy(from, start, to, offset, length)
128
+ }
129
+
130
+ /// `copy` without its bounds check.
131
+ /// Corrupts memory if misused; prefer `copy`.
132
+ extern def unsafeCopy(from: ByteArray, start: Int, to: ByteArray, offset: Int, length: Int) at global: Unit =
133
+ js "bytearray$copy(${from}, ${start}, ${to}, ${offset}, ${length})"
134
+ chez "(bytevector-copy! ${from} ${start} ${to} ${offset} ${length})"
135
+ llvm """
136
+ %z = call %Pos @c_bytearray_copy(%Pos ${from}, %Int ${start}, %Pos ${to}, %Int ${offset}, %Int ${length})
137
+ ret %Pos %z
138
+ """
139
+
116
140
  def resize(source: ByteArray, size: Int): ByteArray = {
117
141
  val target = allocate(size)
118
- val n = min(source.size, target.size)
119
- def go(i: Int): ByteArray =
120
- if (i < n) {
121
- target.set(i, source.get(i))
122
- go(i + 1)
123
- } else {
124
- target
125
- }
126
- go(0)
142
+ // safe: `min` establishes the bound for both ranges ~> no check is needed
143
+ unsafeCopy(source, 0, target, 0, min(source.size, size))
144
+ target
127
145
  }
128
146
 
129
147
  /// Sorts a given bytearray `arr` in-place using a counting sort,
@@ -190,6 +208,21 @@ extern def toString(arr: ByteArray) at {}: String =
190
208
  chez "(utf8->string ${arr})"
191
209
 
192
210
  extern js """
211
+ function bytearray$copy(src, srcOff, dst, dstOff, n) {
212
+ // for small `n`, it's faster to copy manually to avoid allocating `subarray`
213
+ if (n < 40) {
214
+ if (srcOff < dstOff) {
215
+ for (let i = n - 1; i >= 0; --i) dst[dstOff + i] = src[srcOff + i];
216
+ } else {
217
+ for (let i = 0; i < n; ++i) dst[dstOff + i] = src[srcOff + i];
218
+ }
219
+ } else {
220
+ // safe: `set` clones the source region first when both share a buffer
221
+ dst.set(src.subarray(srcOff, srcOff + n), dstOff);
222
+ }
223
+ return $effekt.unit;
224
+ }
225
+
193
226
  function bytearray$set(bytes, index, value) {
194
227
  bytes[index] = value;
195
228
  return $effekt.unit;
@@ -242,22 +275,8 @@ extern def compareByteArrayImpl(b1: ByteArray, b2: ByteArray) at {}: Int =
242
275
  """
243
276
  chez "(bytearray$compare ${b1} ${b2})"
244
277
 
245
- def compareByteArray(b1: ByteArray, b2: ByteArray): Ordering = {
246
- val ret = compareByteArrayImpl(b1, b2)
247
- if (ret == 0) {
248
- Equal()
249
- } else if (ret < 0) {
250
- Less()
251
- } else { // ret > 0
252
- Greater()
253
- }
254
- }
255
-
256
- def compareStringBytes(left: String, right: String): Ordering = {
257
- val l = left.fromString
258
- val r = right.fromString
259
- compareByteArray(l, r)
260
- }
278
+ def compareByteArray(b1: ByteArray, b2: ByteArray): Ordering =
279
+ compareByteArrayImpl(b1, b2).fromInt
261
280
 
262
281
 
263
282
  // Streaming
@@ -144,6 +144,10 @@ extern def infixGte(x: Char, y: Char) at {}: Bool =
144
144
  ret %Pos %adt_boolean
145
145
  """
146
146
 
147
+ def compareChar(x: Char, y: Char): Ordering =
148
+ if (x == y) Equal()
149
+ else if (x < y) Less()
150
+ else Greater()
147
151
 
148
152
  /**
149
153
  * Determines the number of bytes needed by a codepoint
@@ -155,18 +159,21 @@ def utf8ByteCount(codepoint: Char): Int = codepoint match {
155
159
  case c and c >= '\u0080' and c <= '\u07FF' => 2
156
160
  case c and c >= '\u0800' and c <= '\uFFFF' => 3
157
161
  case c and c >= '\u10000' and c <= '\u10FFFF' => 4
158
- case c => panic("Not a valid code point")
162
+ case c => <{ "Not a valid code point" }>
159
163
  }
160
164
 
161
165
  def utf16UnitCount(codepoint: Char): Int = codepoint match {
162
166
  case c and c >= '\u0000' and c <= '\uFFFF' => 1
163
- case c and c >= '\u10000' and c <= '\u10FFFF' => 4
164
- case c => panic("Not a valid code point")
167
+ case c and c >= '\u10000' and c <= '\u10FFFF' => 2
168
+ case c => <{ "Not a valid code point" }>
165
169
  }
166
170
 
171
+ /// How many index units `c` occupies in the unit that `length` and `unsafeCharAt`
172
+ /// use on this backend. Adding it to a code-point boundary gives the next character.
167
173
  extern def charWidth(c: Char) at {}: Int =
168
- // JavaScript strings are UTF-16 where every unicode character after 0xffff takes two units
169
- js "(${c} > 0xffff) ? 2 : 1"
174
+ js { utf16UnitCount(c) }
175
+ chez { 1 }
176
+ llvm { utf8ByteCount(c) }
170
177
 
171
178
 
172
179
  def decodeChar(): Char / {next[Byte], stop} = {
@@ -6,14 +6,40 @@ import bytearray
6
6
  import stream
7
7
 
8
8
  /// Internal trie node type.
9
+ ///
10
+ /// Nodes with at most four children are `Branch1` ... `Branch4`, holding their
11
+ /// children behind `Ref`s so that descending along an existing path can
12
+ /// update in place without rebuilding the spine. A fifth child promotes the
13
+ /// node to the dense representation (`Branch256` / `Node256`), a full 256-slot
14
+ /// array indexed by the byte, whose cells are mutable themselves.
15
+ ///
16
+ /// Invariant: in the sparse representations, `byte1 < byte2 < byte3 < byte4`
17
+ /// (bytes are unsigned), so iteration is in key order in every representation.
18
+ ///
19
+ /// `Leaf`, `Node1` ... `Node4` and `Node256` carry a value at this
20
+ /// position; `Branch1` ... `Branch4` and `Branch256` do not.
21
+ ///
22
+ /// No `Trie` value is ever shared between two live positions: insertion
23
+ /// mutates through `Ref`s and array cells, so aliasing a subtree would be
24
+ /// unsound.
9
25
  type Trie[A] {
10
26
  Empty()
11
27
  Leaf(value: A)
12
- Branch(children: Array[Trie[A]])
13
- Node(value: A, children: Array[Trie[A]])
28
+ Branch1(byte: Byte, child: Ref[Trie[A]])
29
+ Node1(value: A, byte: Byte, child: Ref[Trie[A]])
30
+ Branch2(byte1: Byte, child1: Ref[Trie[A]], byte2: Byte, child2: Ref[Trie[A]])
31
+ Node2(value: A, byte1: Byte, child1: Ref[Trie[A]], byte2: Byte, child2: Ref[Trie[A]])
32
+ Branch3(byte1: Byte, child1: Ref[Trie[A]], byte2: Byte, child2: Ref[Trie[A]], byte3: Byte, child3: Ref[Trie[A]])
33
+ Node3(value: A, byte1: Byte, child1: Ref[Trie[A]], byte2: Byte, child2: Ref[Trie[A]], byte3: Byte, child3: Ref[Trie[A]])
34
+ Branch4(byte1: Byte, child1: Ref[Trie[A]], byte2: Byte, child2: Ref[Trie[A]], byte3: Byte, child3: Ref[Trie[A]], byte4: Byte, child4: Ref[Trie[A]])
35
+ Node4(value: A, byte1: Byte, child1: Ref[Trie[A]], byte2: Byte, child2: Ref[Trie[A]], byte3: Byte, child3: Ref[Trie[A]], byte4: Byte, child4: Ref[Trie[A]])
36
+ Branch256(children: Array[Trie[A]])
37
+ Node256(value: A, children: Array[Trie[A]])
14
38
  }
15
39
 
16
40
  /// A mutable trie-based dictionary for efficient key-value storage with bytearray keys.
41
+ ///
42
+ /// Mutating the dictionary while iterating it (`each`, `eachTagged`) is undefined.
17
43
  type Dictionary[A] = Ref[Trie[A]]
18
44
 
19
45
  /// A record pairing a key with a value for use in streams.
@@ -27,34 +53,104 @@ def insert[A](dictionary: Dictionary[A], key: String, value: A): Unit =
27
53
 
28
54
  /// Insert a key-value pair into the dictionary with a ByteArray key.
29
55
  def insert[A](dictionary: Dictionary[A], key: ByteArray, value: A): Unit = {
56
+ val n = key.size
57
+
58
+ // The rest of the key from position `i` on, as a chain of `Branch1`s
59
+ // ending in a `Leaf`.
60
+ def chain(i: Int): Trie[A] =
61
+ if (i < n) Branch1(key.get(i), ref(chain(i + 1)))
62
+ else Leaf(value)
63
+
64
+ // The four children of a `Branch4`, plus the rest of the key at `b`,
65
+ // as a full 256-slot array.
66
+ def densified(byte1: Byte, child1: Ref[Trie[A]], byte2: Byte, child2: Ref[Trie[A]], byte3: Byte, child3: Ref[Trie[A]], byte4: Byte, child4: Ref[Trie[A]], b: Byte, i: Int): Array[Trie[A]] = {
67
+ val children = array[Trie[A]](256, Empty())
68
+ children.set(byte1.toInt, child1.get)
69
+ children.set(byte2.toInt, child2.get)
70
+ children.set(byte3.toInt, child3.get)
71
+ children.set(byte4.toInt, child4.get)
72
+ children.set(b.toInt, chain(i + 1))
73
+ children
74
+ }
75
+
30
76
  def go(trie: Trie[A], i: Int): Trie[A] =
31
- if (i < key.size) {
77
+ if (i < n) {
78
+ val b = key.get(i)
32
79
  trie match {
33
- case Empty() =>
34
- val child = go(Empty(), i + 1)
35
- val children = array(256, Empty())
36
- children.set(key.get(i).toInt, child)
37
- Branch(children)
38
- case Leaf(existing) =>
39
- val child = go(Empty(), i + 1)
40
- val children = array(256, Empty())
41
- children.set(key.get(i).toInt, child)
42
- Node(existing, children)
43
- case Branch(children) =>
44
- val child = go(children.get(key.get(i).toInt), i + 1)
45
- children.set(key.get(i).toInt, child)
46
- Branch(children)
47
- case Node(existing, children) =>
48
- val child = go(children.get(key.get(i).toInt), i + 1)
49
- children.set(key.get(i).toInt, child)
50
- Node(existing, children)
80
+ case Empty() => chain(i)
81
+ case Leaf(existing) => Node1(existing, b, ref(chain(i + 1)))
82
+
83
+ case Branch1(byte1, child1) =>
84
+ if (b == byte1) { child1.set(go(child1.get, i + 1)); trie }
85
+ else if (b.toInt < byte1.toInt) Branch2(b, ref(chain(i + 1)), byte1, child1)
86
+ else Branch2(byte1, child1, b, ref(chain(i + 1)))
87
+ case Node1(existing, byte1, child1) =>
88
+ if (b == byte1) { child1.set(go(child1.get, i + 1)); trie }
89
+ else if (b.toInt < byte1.toInt) Node2(existing, b, ref(chain(i + 1)), byte1, child1)
90
+ else Node2(existing, byte1, child1, b, ref(chain(i + 1)))
91
+
92
+ case Branch2(byte1, child1, byte2, child2) =>
93
+ if (b == byte1) { child1.set(go(child1.get, i + 1)); trie }
94
+ else if (b == byte2) { child2.set(go(child2.get, i + 1)); trie }
95
+ else if (b.toInt < byte1.toInt) Branch3(b, ref(chain(i + 1)), byte1, child1, byte2, child2)
96
+ else if (b.toInt < byte2.toInt) Branch3(byte1, child1, b, ref(chain(i + 1)), byte2, child2)
97
+ else Branch3(byte1, child1, byte2, child2, b, ref(chain(i + 1)))
98
+ case Node2(existing, byte1, child1, byte2, child2) =>
99
+ if (b == byte1) { child1.set(go(child1.get, i + 1)); trie }
100
+ else if (b == byte2) { child2.set(go(child2.get, i + 1)); trie }
101
+ else if (b.toInt < byte1.toInt) Node3(existing, b, ref(chain(i + 1)), byte1, child1, byte2, child2)
102
+ else if (b.toInt < byte2.toInt) Node3(existing, byte1, child1, b, ref(chain(i + 1)), byte2, child2)
103
+ else Node3(existing, byte1, child1, byte2, child2, b, ref(chain(i + 1)))
104
+
105
+ case Branch3(byte1, child1, byte2, child2, byte3, child3) =>
106
+ if (b == byte1) { child1.set(go(child1.get, i + 1)); trie }
107
+ else if (b == byte2) { child2.set(go(child2.get, i + 1)); trie }
108
+ else if (b == byte3) { child3.set(go(child3.get, i + 1)); trie }
109
+ else if (b.toInt < byte1.toInt) Branch4(b, ref(chain(i + 1)), byte1, child1, byte2, child2, byte3, child3)
110
+ else if (b.toInt < byte2.toInt) Branch4(byte1, child1, b, ref(chain(i + 1)), byte2, child2, byte3, child3)
111
+ else if (b.toInt < byte3.toInt) Branch4(byte1, child1, byte2, child2, b, ref(chain(i + 1)), byte3, child3)
112
+ else Branch4(byte1, child1, byte2, child2, byte3, child3, b, ref(chain(i + 1)))
113
+ case Node3(existing, byte1, child1, byte2, child2, byte3, child3) =>
114
+ if (b == byte1) { child1.set(go(child1.get, i + 1)); trie }
115
+ else if (b == byte2) { child2.set(go(child2.get, i + 1)); trie }
116
+ else if (b == byte3) { child3.set(go(child3.get, i + 1)); trie }
117
+ else if (b.toInt < byte1.toInt) Node4(existing, b, ref(chain(i + 1)), byte1, child1, byte2, child2, byte3, child3)
118
+ else if (b.toInt < byte2.toInt) Node4(existing, byte1, child1, b, ref(chain(i + 1)), byte2, child2, byte3, child3)
119
+ else if (b.toInt < byte3.toInt) Node4(existing, byte1, child1, byte2, child2, b, ref(chain(i + 1)), byte3, child3)
120
+ else Node4(existing, byte1, child1, byte2, child2, byte3, child3, b, ref(chain(i + 1)))
121
+
122
+ case Branch4(byte1, child1, byte2, child2, byte3, child3, byte4, child4) =>
123
+ if (b == byte1) { child1.set(go(child1.get, i + 1)); trie }
124
+ else if (b == byte2) { child2.set(go(child2.get, i + 1)); trie }
125
+ else if (b == byte3) { child3.set(go(child3.get, i + 1)); trie }
126
+ else if (b == byte4) { child4.set(go(child4.get, i + 1)); trie }
127
+ else Branch256(densified(byte1, child1, byte2, child2, byte3, child3, byte4, child4, b, i))
128
+ case Node4(existing, byte1, child1, byte2, child2, byte3, child3, byte4, child4) =>
129
+ if (b == byte1) { child1.set(go(child1.get, i + 1)); trie }
130
+ else if (b == byte2) { child2.set(go(child2.get, i + 1)); trie }
131
+ else if (b == byte3) { child3.set(go(child3.get, i + 1)); trie }
132
+ else if (b == byte4) { child4.set(go(child4.get, i + 1)); trie }
133
+ else Node256(existing, densified(byte1, child1, byte2, child2, byte3, child3, byte4, child4, b, i))
134
+
135
+ case Branch256(children) =>
136
+ children.set(b.toInt, go(children.get(b.toInt), i + 1)); trie
137
+ case Node256(_, children) =>
138
+ children.set(b.toInt, go(children.get(b.toInt), i + 1)); trie
51
139
  }
52
140
  } else {
53
141
  trie match {
54
142
  case Empty() => Leaf(value)
55
143
  case Leaf(_) => Leaf(value)
56
- case Node(_, children) => Node(value, children)
57
- case Branch(children) => Node(value, children)
144
+ case Branch1(byte1, child1) => Node1(value, byte1, child1)
145
+ case Node1(_, byte1, child1) => Node1(value, byte1, child1)
146
+ case Branch2(byte1, child1, byte2, child2) => Node2(value, byte1, child1, byte2, child2)
147
+ case Node2(_, byte1, child1, byte2, child2) => Node2(value, byte1, child1, byte2, child2)
148
+ case Branch3(byte1, child1, byte2, child2, byte3, child3) => Node3(value, byte1, child1, byte2, child2, byte3, child3)
149
+ case Node3(_, byte1, child1, byte2, child2, byte3, child3) => Node3(value, byte1, child1, byte2, child2, byte3, child3)
150
+ case Branch4(byte1, child1, byte2, child2, byte3, child3, byte4, child4) => Node4(value, byte1, child1, byte2, child2, byte3, child3, byte4, child4)
151
+ case Node4(_, byte1, child1, byte2, child2, byte3, child3, byte4, child4) => Node4(value, byte1, child1, byte2, child2, byte3, child3, byte4, child4)
152
+ case Branch256(children) => Node256(value, children)
153
+ case Node256(_, children) => Node256(value, children)
58
154
  }
59
155
  }
60
156
  dictionary.set(go(dictionary.get, 0))
@@ -68,18 +164,56 @@ def lookup[A](dictionary: Dictionary[A], key: String): Option[A] =
68
164
  def lookup[A](dictionary: Dictionary[A], key: ByteArray): Option[A] = {
69
165
  def go(trie: Trie[A], i: Int): Option[A] =
70
166
  if (i < key.size) {
167
+ val b = key.get(i)
71
168
  trie match {
72
169
  case Empty() => None()
73
170
  case Leaf(_) => None()
74
- case Branch(children) => go(children.get(key.get(i).toInt), i + 1)
75
- case Node(_, children) => go(children.get(key.get(i).toInt), i + 1)
171
+ case Branch1(byte1, child1) =>
172
+ if (b == byte1) go(child1.get, i + 1) else None()
173
+ case Node1(_, byte1, child1) =>
174
+ if (b == byte1) go(child1.get, i + 1) else None()
175
+ case Branch2(byte1, child1, byte2, child2) =>
176
+ if (b == byte1) go(child1.get, i + 1)
177
+ else if (b == byte2) go(child2.get, i + 1)
178
+ else None()
179
+ case Node2(_, byte1, child1, byte2, child2) =>
180
+ if (b == byte1) go(child1.get, i + 1)
181
+ else if (b == byte2) go(child2.get, i + 1)
182
+ else None()
183
+ case Branch3(byte1, child1, byte2, child2, byte3, child3) =>
184
+ if (b == byte1) go(child1.get, i + 1)
185
+ else if (b == byte2) go(child2.get, i + 1)
186
+ else if (b == byte3) go(child3.get, i + 1)
187
+ else None()
188
+ case Node3(_, byte1, child1, byte2, child2, byte3, child3) =>
189
+ if (b == byte1) go(child1.get, i + 1)
190
+ else if (b == byte2) go(child2.get, i + 1)
191
+ else if (b == byte3) go(child3.get, i + 1)
192
+ else None()
193
+ case Branch4(byte1, child1, byte2, child2, byte3, child3, byte4, child4) =>
194
+ if (b == byte1) go(child1.get, i + 1)
195
+ else if (b == byte2) go(child2.get, i + 1)
196
+ else if (b == byte3) go(child3.get, i + 1)
197
+ else if (b == byte4) go(child4.get, i + 1)
198
+ else None()
199
+ case Node4(_, byte1, child1, byte2, child2, byte3, child3, byte4, child4) =>
200
+ if (b == byte1) go(child1.get, i + 1)
201
+ else if (b == byte2) go(child2.get, i + 1)
202
+ else if (b == byte3) go(child3.get, i + 1)
203
+ else if (b == byte4) go(child4.get, i + 1)
204
+ else None()
205
+ case Branch256(children) => go(children.get(b.toInt), i + 1)
206
+ case Node256(_, children) => go(children.get(b.toInt), i + 1)
76
207
  }
77
208
  } else {
78
209
  trie match {
79
- case Empty() => None()
80
210
  case Leaf(value) => Some(value)
81
- case Branch(_) => None()
82
- case Node(value, _) => Some(value)
211
+ case Node1(value, _, _) => Some(value)
212
+ case Node2(value, _, _, _, _) => Some(value)
213
+ case Node3(value, _, _, _, _, _, _) => Some(value)
214
+ case Node4(value, _, _, _, _, _, _, _, _) => Some(value)
215
+ case Node256(value, _) => Some(value)
216
+ case _ => None()
83
217
  }
84
218
  }
85
219
  go(dictionary.get, 0)
@@ -87,48 +221,104 @@ def lookup[A](dictionary: Dictionary[A], key: ByteArray): Option[A] = {
87
221
 
88
222
  /// Collect key-value pairs from a stream of Tagged elements into a fresh Dictionary.
89
223
  def collect[A] { stream: () => Unit / emit[Tagged[A]] }: Dictionary[A] = {
90
- val dictionary = ref(Empty())
224
+ val dictionary = empty[A]()
91
225
  for[Tagged[A]] { stream() } { case Tagged(bytes, value) =>
92
226
  dictionary.insert(bytes, value)
93
227
  }
94
- return dictionary
228
+ dictionary
95
229
  }
96
230
 
97
- /// Emit all values stored in the dictionary as a push stream.
231
+ /// Emit all values stored in the dictionary as a push stream,
232
+ /// in lexicographic key order (a key before its extensions).
98
233
  def each[A](dictionary: Dictionary[A]): Unit / emit[A] = {
99
234
  def go(trie: Trie[A]): Unit / emit[A] =
100
235
  trie match {
101
236
  case Empty() => ()
102
237
  case Leaf(value) => do emit(value)
103
- case Branch(children) =>
104
- each(0, children.size) { i => go(children.get(i)) }
105
- case Node(value, children) =>
238
+ case Branch1(_, child1) => go(child1.get)
239
+ case Node1(value, _, child1) =>
240
+ do emit(value); go(child1.get)
241
+ case Branch2(_, child1, _, child2) =>
242
+ go(child1.get); go(child2.get)
243
+ case Node2(value, _, child1, _, child2) =>
244
+ do emit(value); go(child1.get); go(child2.get)
245
+ case Branch3(_, child1, _, child2, _, child3) =>
246
+ go(child1.get); go(child2.get); go(child3.get)
247
+ case Node3(value, _, child1, _, child2, _, child3) =>
248
+ do emit(value); go(child1.get); go(child2.get); go(child3.get)
249
+ case Branch4(_, child1, _, child2, _, child3, _, child4) =>
250
+ go(child1.get); go(child2.get); go(child3.get); go(child4.get)
251
+ case Node4(value, _, child1, _, child2, _, child3, _, child4) =>
252
+ do emit(value); go(child1.get); go(child2.get); go(child3.get); go(child4.get)
253
+ case Branch256(children) =>
254
+ each(0, 256) { i => go(children.get(i)) }
255
+ case Node256(value, children) =>
106
256
  do emit(value)
107
- each(0, children.size) { i => go(children.get(i)) }
257
+ each(0, 256) { i => go(children.get(i)) }
108
258
  }
109
259
  go(dictionary.get)
110
260
  }
111
261
 
112
- /// Emit all key-value pairs stored in the dictionary as a push stream of Tagged elements.
262
+ /// Emit all key-value pairs stored in the dictionary as a push stream of
263
+ /// Tagged elements, in lexicographic key order (a key before its extensions).
113
264
  def eachTagged[A](dictionary: Dictionary[A]): Unit / emit[Tagged[A]] = {
114
- def fromBytesReverse(bytes: List[Byte], n: Int): ByteArray = {
115
- val key = bytearray::allocate(n)
116
- foreachIndex(bytes) { (i, b) => key.set(n - 1 - i, b) }
117
- key
265
+ var buffer = bytearray::allocate(32)
266
+ // `depth` grows by exactly one per level, so `depth <= buffer.size` always
267
+ // holds and a single doubling suffices.
268
+ def push(depth: Int, b: Byte): Unit = {
269
+ if (depth >= buffer.size) {
270
+ buffer = buffer.resize(buffer.size * 2)
271
+ }
272
+ buffer.set(depth, b)
118
273
  }
119
- def go(trie: Trie[A], key: List[Byte], n: Int): Unit =
274
+ def emitAt(depth: Int, value: A): Unit = {
275
+ val key = bytearray::allocate(depth)
276
+ // safe: `key` has exactly `depth` bytes, and `depth` is within `buffer`'s capacity.
277
+ bytearray::unsafeCopy(buffer, 0, key, 0, depth)
278
+ do emit(Tagged(key, value))
279
+ }
280
+ def go(trie: Trie[A], depth: Int): Unit =
120
281
  trie match {
121
282
  case Empty() => ()
122
- case Leaf(value) => do emit(Tagged(fromBytesReverse(key, n), value))
123
- case Branch(children) =>
124
- each(0, children.size) { i =>
125
- go(children.get(i), Cons(i.toByte, key), n + 1)
126
- }
127
- case Node(value, children) =>
128
- do emit(Tagged(fromBytesReverse(key, n), value))
129
- each(0, children.size) { i =>
130
- go(children.get(i), Cons(i.toByte, key), n + 1)
131
- }
283
+ case Leaf(value) => emitAt(depth, value)
284
+ case Branch1(byte1, child1) =>
285
+ push(depth, byte1); go(child1.get, depth + 1)
286
+ case Node1(value, byte1, child1) =>
287
+ emitAt(depth, value)
288
+ push(depth, byte1); go(child1.get, depth + 1)
289
+ case Branch2(byte1, child1, byte2, child2) =>
290
+ push(depth, byte1); go(child1.get, depth + 1)
291
+ push(depth, byte2); go(child2.get, depth + 1)
292
+ case Node2(value, byte1, child1, byte2, child2) =>
293
+ emitAt(depth, value)
294
+ push(depth, byte1); go(child1.get, depth + 1)
295
+ push(depth, byte2); go(child2.get, depth + 1)
296
+ case Branch3(byte1, child1, byte2, child2, byte3, child3) =>
297
+ push(depth, byte1); go(child1.get, depth + 1)
298
+ push(depth, byte2); go(child2.get, depth + 1)
299
+ push(depth, byte3); go(child3.get, depth + 1)
300
+ case Node3(value, byte1, child1, byte2, child2, byte3, child3) =>
301
+ emitAt(depth, value)
302
+ push(depth, byte1); go(child1.get, depth + 1)
303
+ push(depth, byte2); go(child2.get, depth + 1)
304
+ push(depth, byte3); go(child3.get, depth + 1)
305
+ case Branch4(byte1, child1, byte2, child2, byte3, child3, byte4, child4) =>
306
+ push(depth, byte1); go(child1.get, depth + 1)
307
+ push(depth, byte2); go(child2.get, depth + 1)
308
+ push(depth, byte3); go(child3.get, depth + 1)
309
+ push(depth, byte4); go(child4.get, depth + 1)
310
+ case Node4(value, byte1, child1, byte2, child2, byte3, child3, byte4, child4) =>
311
+ emitAt(depth, value)
312
+ push(depth, byte1); go(child1.get, depth + 1)
313
+ push(depth, byte2); go(child2.get, depth + 1)
314
+ push(depth, byte3); go(child3.get, depth + 1)
315
+ push(depth, byte4); go(child4.get, depth + 1)
316
+ case Branch256(children) =>
317
+ each(0, 256) { i => push(depth, i.toByte); go(children.get(i), depth + 1) }
318
+ case Node256(value, children) =>
319
+ emitAt(depth, value)
320
+ each(0, 256) { i => push(depth, i.toByte); go(children.get(i), depth + 1) }
132
321
  }
133
- go(dictionary.get, Nil(), 0)
134
- }
322
+ go(dictionary.get, 0)
323
+ }
324
+
@@ -72,7 +72,7 @@ extern def readln() at async: String =
72
72
  jsNode "$effekt.capture(callback => $effekt.readln(callback))"
73
73
  llvm """
74
74
  %result = call %Pos @c_io_readln()
75
- tail call void @resume_Pos(%Stack %stack, %Pos %result)
75
+ notail call void @resume_Pos(%Stack %stack, %Pos %result)
76
76
  ret void
77
77
  """
78
78
 
@@ -191,20 +191,25 @@ extern def random() at io: Double =
191
191
  // extern io def freshGlobal[T](t: T): Ref[T] at global = js "$effekt.fresh(${t})"
192
192
 
193
193
 
194
- // Equality
194
+ // Comparison
195
195
  // ========
196
196
 
197
197
  /// Result of comparison between two objects according to some order:
198
198
  ///
199
199
  /// - `Less()` means that the first object is *before* the first in the order,
200
- /// - `Equal()` means that the two objects are the same in the order,
201
- /// - and `Greater()` means that the second object is *before* the first in the order.
200
+ /// - `Equal()` means that the two objects are the same in the order, and
201
+ /// - `Greater()` means that the second object is *before* the first in the order.
202
202
  type Ordering {
203
203
  Less();
204
204
  Equal();
205
205
  Greater()
206
206
  }
207
207
 
208
+ def ordering::fromInt(n: Int): Ordering =
209
+ if (n == 0) Equal()
210
+ else if (n < 0) Less()
211
+ else Greater()
212
+
208
213
  def compareInt(n: Int, m: Int) =
209
214
  if (n == m) Equal()
210
215
  else if (n < m) Less()
@@ -215,16 +220,8 @@ extern def genericCompareImpl[R](x: R, y: R) at {}: Int =
215
220
 
216
221
  /// Compares two values of the same type, returning an `Ordering`.
217
222
  /// Only available on the JavaScript backend.
218
- def genericCompare[R](x: R, y: R): Ordering = {
219
- val ret = genericCompareImpl(x, y)
220
- if (ret == 0) {
221
- Equal()
222
- } else if (ret < 0) {
223
- Less()
224
- } else { // ret > 0
225
- Greater()
226
- }
227
- }
223
+ def genericCompare[R](x: R, y: R): Ordering =
224
+ genericCompareImpl(x, y).fromInt
228
225
 
229
226
  def println(o: Ordering): Unit = println(o.show)
230
227
 
@@ -245,49 +245,49 @@ namespace internal {
245
245
  extern def openForReading(path: String) at async: Int =
246
246
  jsNode "$effekt.capture(callback => open(${path}, 'r', callback))"
247
247
  llvm """
248
- call void @c_fs_open_reading(%Pos ${path}, %Stack %stack)
248
+ notail call void @c_fs_open_reading(%Pos ${path}, %Stack %stack)
249
249
  ret void
250
250
  """
251
251
 
252
252
  extern def openForWriting(path: String) at async: Int =
253
253
  jsNode "$effekt.capture(callback => open(${path}, 'w', callback))"
254
254
  llvm """
255
- call void @c_fs_open_writing(%Pos ${path}, %Stack %stack)
255
+ notail call void @c_fs_open_writing(%Pos ${path}, %Stack %stack)
256
256
  ret void
257
257
  """
258
258
 
259
259
  extern def openForAppending(path: String) at async: Int =
260
260
  jsNode "$effekt.capture(callback => open(${path}, 'a', callback))"
261
261
  llvm """
262
- call void @c_fs_open_appending(%Pos ${path}, %Stack %stack)
262
+ notail call void @c_fs_open_appending(%Pos ${path}, %Stack %stack)
263
263
  ret void
264
264
  """
265
265
 
266
266
  extern def read(file: Int, buffer: ByteArray, offset: Int, size: Int, position: Int) at async: Int =
267
267
  jsNode "$effekt.capture(callback => read(${file}, ${buffer}, ${offset}, ${size}, ${position}, callback))"
268
268
  llvm """
269
- call void @c_fs_read(%Int ${file}, %Pos ${buffer}, %Int ${offset}, %Int ${size}, %Int ${position}, %Stack %stack)
269
+ notail call void @c_fs_read(%Int ${file}, %Pos ${buffer}, %Int ${offset}, %Int ${size}, %Int ${position}, %Stack %stack)
270
270
  ret void
271
271
  """
272
272
 
273
273
  extern def write(file: Int, buffer: ByteArray, offset: Int, size: Int, position: Int) at async: Int =
274
274
  jsNode "$effekt.capture(callback => write(${file}, ${buffer}, ${offset}, ${size}, ${position}, callback))"
275
275
  llvm """
276
- call void @c_fs_write(%Int ${file}, %Pos ${buffer}, %Int ${offset}, %Int ${size}, %Int ${position}, %Stack %stack)
276
+ notail call void @c_fs_write(%Int ${file}, %Pos ${buffer}, %Int ${offset}, %Int ${size}, %Int ${position}, %Stack %stack)
277
277
  ret void
278
278
  """
279
279
 
280
280
  extern def close(file: Int) at async: Int =
281
281
  jsNode "$effekt.capture(callback => close(${file}, callback))"
282
282
  llvm """
283
- call void @c_fs_close(%Int ${file}, %Stack %stack)
283
+ notail call void @c_fs_close(%Int ${file}, %Stack %stack)
284
284
  ret void
285
285
  """
286
286
 
287
287
  extern def mkdir(path: String) at async: Int =
288
288
  jsNode "$effekt.capture(callback => mkdir(${path}, callback))"
289
289
  llvm """
290
- call void @c_fs_mkdir(%Pos ${path}, %Stack %stack)
290
+ notail call void @c_fs_mkdir(%Pos ${path}, %Stack %stack)
291
291
  ret void
292
292
  """
293
293