nomen-lang 0.7.1 → 0.9.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.
Files changed (80) hide show
  1. package/audit_runtime.c +12 -2
  2. package/core/System/Arena.nm +8 -1
  3. package/core/System/Buffer.nm +397 -8
  4. package/core/System/ClassBuffer.nm +156 -0
  5. package/core/System/Controls/Alert.nm +326 -0
  6. package/core/System/Controls/App.nm +147 -0
  7. package/core/System/Controls/Button.nm +119 -1
  8. package/core/System/Controls/CheckBox.nm +25 -1
  9. package/core/System/Controls/ColorPicker.nm +307 -0
  10. package/core/System/Controls/ComboBox.nm +430 -0
  11. package/core/System/Controls/Container.nm +843 -9
  12. package/core/System/Controls/Control.nm +443 -0
  13. package/core/System/Controls/DatePicker.nm +288 -0
  14. package/core/System/Controls/Form.nm +70 -0
  15. package/core/System/Controls/GroupBox.nm +267 -0
  16. package/core/System/Controls/ImageView.nm +311 -0
  17. package/core/System/Controls/LevelIndicator.nm +305 -0
  18. package/core/System/Controls/ListView.nm +633 -0
  19. package/core/System/Controls/Menu.nm +428 -0
  20. package/core/System/Controls/NumberInput.nm +56 -0
  21. package/core/System/Controls/OtpInput.nm +50 -0
  22. package/core/System/Controls/OutlineView.nm +598 -0
  23. package/core/System/Controls/Pagination.nm +142 -0
  24. package/core/System/Controls/Popover.nm +296 -0
  25. package/core/System/Controls/Progress.nm +348 -0
  26. package/core/System/Controls/Radio.nm +292 -0
  27. package/core/System/Controls/Scrollable.nm +584 -0
  28. package/core/System/Controls/SearchField.nm +326 -0
  29. package/core/System/Controls/SecureField.nm +344 -0
  30. package/core/System/Controls/Segmented.nm +380 -0
  31. package/core/System/Controls/Select.nm +340 -0
  32. package/core/System/Controls/Separator.nm +198 -0
  33. package/core/System/Controls/Sheet.nm +329 -0
  34. package/core/System/Controls/Slider.nm +315 -0
  35. package/core/System/Controls/Spinner.nm +287 -0
  36. package/core/System/Controls/SplitView.nm +509 -0
  37. package/core/System/Controls/Stepper.nm +349 -0
  38. package/core/System/Controls/Switch.nm +259 -0
  39. package/core/System/Controls/Table.nm +833 -0
  40. package/core/System/Controls/Tabs.nm +500 -0
  41. package/core/System/Controls/Text.nm +108 -11
  42. package/core/System/Controls/TextBox.nm +113 -2
  43. package/core/System/Controls/TextView.nm +329 -0
  44. package/core/System/Controls/Toggle.nm +346 -0
  45. package/core/System/Controls/Toolbar.nm +501 -0
  46. package/core/System/Controls/Tooltip.nm +76 -0
  47. package/core/System/Controls/Window.nm +1548 -91
  48. package/core/System/LinkedList.nm +8 -1
  49. package/core/System/List.nm +97 -7
  50. package/core/System/Map.nm +191 -18
  51. package/core/System/Mutex.nm +4 -4
  52. package/core/System/Random.nm +3 -4
  53. package/core/System/Set.nm +101 -13
  54. package/core/System/StableList.nm +371 -0
  55. package/core/System/Stream/Directory.nm +2 -2
  56. package/core/System/Stream/File.nm +2 -2
  57. package/core/System/Stream/Http.nm +2 -2
  58. package/core/System/String.nm +10 -5
  59. package/core/System/StringBuilder.nm +213 -2
  60. package/core/System/Text/CharIndex.nm +9 -9
  61. package/core/System/Text/Json.nm +60 -46
  62. package/core/System/Text/JsonTree.nm +120 -92
  63. package/core/System/Text/Regex.nm +23 -18
  64. package/core/System/Text/Utf8.nm +5 -5
  65. package/core/System/float.nm +36 -22
  66. package/core/System/int.nm +80 -28
  67. package/core/System/int16.nm +11 -0
  68. package/core/System/int32.nm +11 -0
  69. package/core/System/int64.nm +11 -0
  70. package/core/System/int8.nm +11 -0
  71. package/core/System/uint.nm +11 -0
  72. package/core/System/uint16.nm +11 -0
  73. package/core/System/uint32.nm +11 -0
  74. package/core/System/uint64.nm +11 -0
  75. package/core/System/uint8.nm +11 -0
  76. package/core/docs/System/StringBuilder.md +6 -0
  77. package/core/docs/System/Text/JsonTree.md +3 -2
  78. package/dist/index.mjs +38332 -30604
  79. package/package.json +1 -1
  80. package/src/bench_loop.nm +9 -9
@@ -6,26 +6,46 @@
6
6
  * The capacity is always a power of two (initial 8, doubling on rehash), so
7
7
  * slot indexing is a mask (`hash & (cap - 1)`) — correct for negative hashes
8
8
  * too (two's-complement `&` already lands in `[0, cap)`).
9
+ *
10
+ * Each slot carries its value's hash (truncated to 32 bits) in a parallel
11
+ * `hashes` buffer. Probes compare the stored hash before the value itself
12
+ * (the Swiss-table short-circuit — the `==` only runs on a hash match, and a
13
+ * false match still falls through to `==`, so truncation costs nothing);
14
+ * rehash re-inserts hash-free, reading the stored word instead of calling
15
+ * `value.hash()` again.
9
16
  **/
10
17
  pub mv struct Set<T: Hashable + Equatable> {
11
18
  var length = 0
12
19
  var slots = Buffer<T>()
13
20
  var used = Buffer<int>()
21
+ var hashes = Buffer<int>()
14
22
 
15
23
  pub func add = (ref self, mv T value) {
16
24
  var int cap = self.slots.cap
17
25
  if cap == 0 {
18
26
  cap = self.slots.alloc(8)
19
27
  self.used.alloc_u32(8)
28
+ self.hashes.alloc_u32(8)
20
29
  }
21
30
  if self.length * 4 >= cap * 3 {
22
31
  self.rehash(cap * 2)
23
32
  cap = self.slots.cap
24
33
  }
25
- var int idx = self.find_slot(value, cap)
34
+ var uint32 h = value.hash() as uint32
35
+ var int idx = self.find_slot(value, h, cap)
26
36
  if self.used.load_u32(idx) == 0 {
27
37
  self.used.store_u32(idx, 1)
28
- self.slots.store(idx, value)
38
+ // The value arrives owned (`mv T value`): a value-struct element
39
+ // moves into the fresh slot (store_take — the caller's storage is
40
+ // zeroed, no deep copy); a string's by-value pair keeps the
41
+ // deep-copying store (the caller still owns its buffer), and a
42
+ // class/trait handle is a pointer store either way.
43
+ if T_IS_VALUE_STRUCT {
44
+ self.slots.store_take(idx, value)
45
+ } else {
46
+ self.slots.store(idx, value)
47
+ }
48
+ self.hashes.store_u32(idx, h)
29
49
  self.length += 1
30
50
  }
31
51
  }
@@ -35,7 +55,7 @@ pub mv struct Set<T: Hashable + Equatable> {
35
55
  if cap == 0 {
36
56
  return false
37
57
  }
38
- var int idx = self.find_slot(value, cap)
58
+ var int idx = self.find_slot(value, value.hash() as uint32, cap)
39
59
  return self.used.load_u32(idx) != 0
40
60
  }
41
61
 
@@ -44,7 +64,7 @@ pub mv struct Set<T: Hashable + Equatable> {
44
64
  if cap == 0 {
45
65
  return 0
46
66
  }
47
- var int idx = self.find_slot(key, cap)
67
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
48
68
  if self.used.load_u32(idx) == 0 {
49
69
  return 0
50
70
  }
@@ -58,7 +78,7 @@ pub mv struct Set<T: Hashable + Equatable> {
58
78
  if cap == 0 {
59
79
  return fallback
60
80
  }
61
- var int idx = self.find_slot(key, cap)
81
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
62
82
  if self.used.load_u32(idx) == 0 {
63
83
  return fallback
64
84
  }
@@ -71,7 +91,7 @@ pub mv struct Set<T: Hashable + Equatable> {
71
91
  if cap == 0 {
72
92
  panic("value not found in set")
73
93
  }
74
- var int idx = self.find_slot(key, cap)
94
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
75
95
  if self.used.load_u32(idx) == 0 {
76
96
  panic("value not found in set")
77
97
  }
@@ -83,7 +103,7 @@ pub mv struct Set<T: Hashable + Equatable> {
83
103
  if cap == 0 {
84
104
  return
85
105
  }
86
- var int idx = self.find_slot(value, cap)
106
+ var int idx = self.find_slot(value, value.hash() as uint32, cap)
87
107
  if self.used.load_u32(idx) == 0 {
88
108
  return
89
109
  }
@@ -96,7 +116,9 @@ pub mv struct Set<T: Hashable + Equatable> {
96
116
  // so entries past a non-moveable one are not stranded behind an empty
97
117
  // slot (which would make find_slot miss them). No tombstones, so
98
118
  // find_slot/get/has/add are unchanged, and the cost is the local cluster
99
- // length — not a full O(cap) rehash on every delete.
119
+ // length — not a full O(cap) rehash on every delete. `home` reads the
120
+ // stored hash word (no re-hash per shifted entry), and the word moves
121
+ // with the entry — find_slot's hash short-circuit trusts it.
100
122
  var int gap = idx
101
123
  var int k = idx
102
124
  while true {
@@ -107,7 +129,7 @@ pub mv struct Set<T: Hashable + Equatable> {
107
129
  if self.used.load_u32(k) == 0 {
108
130
  break
109
131
  }
110
- var int home = (self.slots.load(k).hash() as int) & (cap - 1)
132
+ var int home = (self.hashes.load_u32(k) as int) & (cap - 1)
111
133
  var int dgap = gap - home
112
134
  if dgap < 0 {
113
135
  dgap = dgap + cap
@@ -123,20 +145,68 @@ pub mv struct Set<T: Hashable + Equatable> {
123
145
  // pointer at the vacated slot (leaked once used.store marks it
124
146
  // empty; the buffer destroy skips unused slots).
125
147
  self.slots.shift(gap, k)
148
+ self.hashes.store_u32(gap, self.hashes.load_u32(k))
126
149
  self.used.store_u32(gap, 1)
127
150
  gap = k
128
151
  }
129
152
  }
153
+ if T_NEEDS_STRDUP {
154
+ if gap == idx {
155
+ // No entry shifted: the removed entry still owns its strdup'd
156
+ // bytes in its slot (a shift's first move would have
157
+ // reclaimed them — see shift_T above). destroy_at frees the
158
+ // buffer and zeroes the slot: a later add() that lands in
159
+ // this hole stores over the bytes assuming a fresh slot, and
160
+ // a stranded-then-overwritten entry leaks.
161
+ self.slots.destroy_at(idx)
162
+ }
163
+ }
164
+ if T_IS_VALUE_STRUCT {
165
+ if gap == idx {
166
+ // Same hole for a value-struct element: reclaim the slot's
167
+ // owned fields (destroy_at frees and zeroes; scalars-only
168
+ // structs reclaim nothing and the call is a no-op).
169
+ self.slots.destroy_at(idx)
170
+ }
171
+ }
130
172
  self.used.store_u32(gap, 0)
131
173
  self.length = self.length - 1
132
174
  }
133
175
 
134
- func find_slot = (self, T value, int cap, out int: out >= 0 && out < cap) {
176
+ // An independent copy: a fresh Set whose member slabs are fresh (`slots`
177
+ // deep-copied per element, `used`/`hashes` copied word by word), with the
178
+ // same length and entries. The u32-word buffers are copied through their
179
+ // own primitives — `Buffer.copy` is T_SIZE-aware (8-byte loads for
180
+ // `int`), which would read past a 4-byte-per-slot `alloc_u32` slab. Not
181
+ // available for class/trait elements (the member routes to ClassBuffer,
182
+ // which has no copy — the checker rejects the call; the shape is also
183
+ // unreachable today since `T: Hashable + Equatable` cannot be a trait
184
+ // without trait-conformance syntax).
185
+ pub func copy = (self, out Set<T>) {
186
+ var dst = Set<T>()
187
+ dst.slots = self.slots.copy()
188
+ dst.used.alloc_u32(self.slots.cap)
189
+ dst.hashes.alloc_u32(self.slots.cap)
190
+ var i = 0
191
+ while i < self.slots.cap; i += 1 {
192
+ dst.used.store_u32(i, self.used.load_u32(i))
193
+ dst.hashes.store_u32(i, self.hashes.load_u32(i))
194
+ }
195
+ dst.length = self.length
196
+ return dst
197
+ }
198
+
199
+ func find_slot = (self, T value, uint32 h, int cap, out int: out >= 0 && out < cap) {
135
200
  // cap is a power of two: `hash mod cap` via a mask, non-negative
136
201
  // for any hash — no adjustment needed.
137
- var int idx = (value.hash() as int) & (cap - 1)
202
+ var int idx = (h as int) & (cap - 1)
138
203
  while self.used.load_u32(idx) != 0 {
139
- if self.slots.load(idx) == value {
204
+ // The stored-hash word short-circuits the value compare: a probed
205
+ // slot whose hash differs (every step of a collision walk past
206
+ // the home slot) skips the value load and `==`. A hash match
207
+ // still falls through to `==`, so the 32-bit truncation costs
208
+ // nothing.
209
+ if self.hashes.load_u32(idx) == h && self.slots.load(idx) == value {
140
210
  return idx
141
211
  }
142
212
  idx += 1
@@ -147,13 +217,20 @@ pub mv struct Set<T: Hashable + Equatable> {
147
217
  return idx
148
218
  }
149
219
 
220
+ // Rehash hash-free: the stored per-slot hash words move with the entries,
221
+ // so no `value.hash()` runs here and the insertion probe needs no value
222
+ // compares (values arrive unique — they came from a valid set). The old
223
+ // form re-inserted via `self.add(mv v)`, re-hashing every value and
224
+ // re-comparing values on every probed slot.
150
225
  func rehash = (ref self, int new_cap) {
151
226
  var Buffer<T> old_slots = mv self.slots swap Buffer<T>()
152
227
  var Buffer<int> old_used = mv self.used swap Buffer<int>()
228
+ var Buffer<int> old_hashes = mv self.hashes swap Buffer<int>()
153
229
  var int old_cap = old_slots.cap
154
230
 
155
231
  self.slots.alloc(new_cap)
156
232
  self.used.alloc_u32(new_cap)
233
+ self.hashes.alloc_u32(new_cap)
157
234
  self.length = 0
158
235
 
159
236
  var i = 0
@@ -168,7 +245,18 @@ pub mv struct Set<T: Hashable + Equatable> {
168
245
  // once the new buffer has made its own copy — passing it inline
169
246
  // would orphan the move_T result.
170
247
  var T v = old_slots.move(i)
171
- self.add(mv v)
248
+ var uint32 h = old_hashes.load_u32(i)
249
+ var int idx = (h as int) & (new_cap - 1)
250
+ while self.used.load_u32(idx) != 0 {
251
+ idx += 1
252
+ if idx >= new_cap {
253
+ idx = 0
254
+ }
255
+ }
256
+ self.used.store_u32(idx, 1)
257
+ self.slots.store(idx, v)
258
+ self.hashes.store_u32(idx, h)
259
+ self.length += 1
172
260
  }
173
261
  }
174
262
  }
@@ -0,0 +1,371 @@
1
+ /**
2
+ * A grow-only, ordered list whose element borrows survive `push` — including
3
+ * interior slot ADDRESSES (stable grow-only arrays; v2).
4
+ *
5
+ * Storage is CHUNKED so that no slot address ever moves: chunk 0 is the
6
+ * `items` Buffer, allocated once at exactly 256 slots and never resized;
7
+ * chunks 1.. live in the `chunks` directory as raw slab pointers (each
8
+ * `calloc`'d once at 256 slots). The directory itself may realloc — it holds
9
+ * slab ADDRESSES, which survive the move. That is what lets `slot_ptr`
10
+ * exist: `ref T x = sl.at(i)` binds the slot itself, the checker's `push`
11
+ * exemption keeps the alias alive across pushes, and writes through it land
12
+ * in the list. The list is still append-only — no `pop`/`set`/`remove_at` —
13
+ * because displacing an element would invalidate its borrows.
14
+ *
15
+ * The tail pieces (store/load/destroy for directory chunks) delegate to
16
+ * Buffer's mono'd methods through a stack `Buffer_T` view — `load_from` is a
17
+ * bitwise slot load (the borrow form; never the strdup-copy the
18
+ * owning-specialized `load` would materialize), the tail store is the typed
19
+ * `store` against a `Buffer.init_view` view (deep-copy semantics into the
20
+ * chunk slot, no staging relay), and `#destroy` destroys each directory chunk
21
+ * through a view whose `cap` is the chunk's FILL count, so no uninitialized
22
+ * slot is ever walked. Chunk 0 (`items`) and the directory's own slab are
23
+ * reclaimed by the ordinary field destroy: the specialized per-element
24
+ * destroy guards zeroed slots and frees its slab.
25
+ **/
26
+ pub mv struct StableList<T> {
27
+ var length = 0
28
+ // Chunk 0: an exact-cap 256-slot slab, allocated on first push, never
29
+ // grown — a realloc would move its slots and invalidate `slot_ptr` aliases.
30
+ var items = Buffer<T>()
31
+ // Directory of chunk-1.. slab addresses (uint64 words, `calloc`'d slabs of
32
+ // 256 T_SIZE bytes). The directory's slot array may realloc; the slabs it
33
+ // points at never move.
34
+ var chunks = Buffer<uint64>()
35
+ // A transient VIEW carrier for tail pushes: `push` points it at the
36
+ // target chunk slab, stores the value through it (deep-copy semantics via
37
+ // Buffer's specialized `store` — no staging relay, no byte copy), then
38
+ // clears it. A field (not a local) because an owning Buffer local is
39
+ // auto-destroyed at scope exit and would free the borrowed slab. Its own
40
+ // slab is always null, so its field destroy reclaims nothing.
41
+ var stage = Buffer<T>()
42
+
43
+ pub func push = (ref self, mv T value) {
44
+ if self.items.cap == 0 {
45
+ self.items.alloc(256)
46
+ }
47
+ if self.length < self.items.cap {
48
+ self.items.store(self.length, value)
49
+ } else {
50
+ var int dir_index = self.length / 256 - 1
51
+ if dir_index >= self.chunks.cap {
52
+ self.chunks.grow(dir_index + 1)
53
+ }
54
+ self.ensure_chunk(dir_index)
55
+ // The typed store lands the value straight into the chunk slot
56
+ // through the view — deep-copy semantics via Buffer's specialized
57
+ // `store`, no staging slot, no byte relay. Every T-value crossing
58
+ // stays inside Nomen's typed machinery.
59
+ self.stage.store_in_slab(
60
+ self.chunks.load(dir_index),
61
+ 256,
62
+ self.length % 256,
63
+ value
64
+ )
65
+ }
66
+ self.length += 1
67
+ }
68
+
69
+ // Capacity for `extra` more appends: allocates chunk 0, grows the
70
+ // directory, and pre-callocs the target slab (the raw `ensure_chunk`'s
71
+ // zero-check keeps push's own calloc idempotent). Splitting it
72
+ // out of `push` is what makes the compiler's in-place push lowering
73
+ // possible: the destination slot for a `push(parse(x))` argument must
74
+ // exist before the argument call runs, so the backend reserves first
75
+ // and constructs the callee's result directly into the append slot
76
+ // (the in-place push contract). Byte-identical policy for extra=1: the same
77
+ // alloc/dir-grow/first-touch sequence push's inline branches performed.
78
+ internal func reserve = (ref self, int extra: extra >= 0, out int) {
79
+ if self.items.cap == 0 {
80
+ self.items.alloc(256)
81
+ }
82
+ if self.length + extra > self.items.cap {
83
+ var int last = self.length + extra - 1
84
+ var int dir_index = last / 256 - 1
85
+ if dir_index >= self.chunks.cap {
86
+ self.chunks.grow(dir_index + 1)
87
+ }
88
+ self.ensure_chunk(dir_index)
89
+ }
90
+ return self.items.cap
91
+ }
92
+
93
+ // Calloc the directory chunk's slab on first touch (zero words mark
94
+ // empty chunks) — push's tail store and the in-place append both rely on
95
+ // the slab existing before a slot address is taken from it.
96
+ internal func ensure_chunk = (ref self, int dir_index) {
97
+ ```
98
+ #arch: c
99
+ if (((unsigned long long*)self->chunks.data)[dir_index] == 0) {
100
+ ((unsigned long long*)self->chunks.data)[dir_index] =
101
+ (unsigned long long)calloc(256, T_SIZE);
102
+ }
103
+ ```
104
+ ```
105
+ #arch: aarch64
106
+ // x0 = self, x1 = dir_index. Stack-less raw functions get no
107
+ // `mov x19, x0` prologue, so load self explicitly and keep it in
108
+ // x19 (the prologue saved the caller's) with dir_index in x20
109
+ // (saved here — x20 is not covered by the prologue) across the
110
+ // calloc call.
111
+ stp x19, x20, [sp, #-16]!
112
+ mov x19, x0
113
+ mov x20, x1
114
+ ldr x9, [x19, #48]
115
+ ldr x10, [x9, x20, lsl #3]
116
+ cmp x10, #0
117
+ b.ne .Lsl_ec_have
118
+ mov x0, #256
119
+ mov x1, #T_SIZE
120
+ bl _calloc
121
+ ldr x9, [x19, #48]
122
+ str x0, [x9, x20, lsl #3]
123
+ .Lsl_ec_have:
124
+ ldp x19, x20, [sp], #16
125
+ ```
126
+ }
127
+
128
+ // Address of the APPEND slot (element `length`) — reserve must have run,
129
+ // so the slot exists. The in-place push lowering constructs the argument
130
+ // call's result straight through this address (sole ownership transfers
131
+ // into the slot — the chunk slab is calloc'd on first touch by
132
+ // `reserve`'s `ensure_chunk` before the slot address is taken).
133
+ internal func append_slot_ptr = (ref self, out ptr T) {
134
+ ```
135
+ #arch: c
136
+ unsigned long long idx = (unsigned long long)self->length;
137
+ if (idx < 256) {
138
+ return (T*)(unsigned long long)self->items.data + idx;
139
+ }
140
+ unsigned long long chunk = ((unsigned long long*)self->chunks.data)[idx / 256 - 1];
141
+ return (T*)chunk + (idx % 256);
142
+ ```
143
+ ```
144
+ #arch: aarch64
145
+ // x0 = self (stack-less raw: x19 is NOT loaded — read the fields
146
+ // from x0 and return the slot address in x0; the body calls
147
+ // nothing, so x9-x13 scratch is safe). Field offsets mirror
148
+ // push's: length @8, items.data @24, chunks.data @48 (the
149
+ // Buffer mono is {_vt, data, cap} = 24 bytes).
150
+ ldr x9, [x0, #8]
151
+ ldr x10, [x0, #24]
152
+ cmp x9, #256
153
+ b.lt .Lsl_as_c0
154
+ and x11, x9, #255
155
+ lsr x12, x9, #8
156
+ sub x12, x12, #1
157
+ ldr x13, [x0, #48]
158
+ ldr x10, [x13, x12, lsl #3]
159
+ b .Lsl_as_have
160
+ .Lsl_as_c0:
161
+ mov x11, x9
162
+ .Lsl_as_have:
163
+ mov x12, #T_SIZE
164
+ madd x0, x11, x12, x10
165
+ ```
166
+ }
167
+
168
+ pub inline func at = (self, int i: i >= 0 && i < self.length, out T) {
169
+ if i < self.items.cap {
170
+ return self.items.load(i)
171
+ }
172
+ return self.load_tail(i)
173
+ }
174
+
175
+ // The directory-chunk half of `at`: a slot borrow exactly like `load`,
176
+ // routed through the chunk directory, delegating to the mono'd
177
+ // `load_from` through a stack view over the foreign slab. Named in the
178
+ // borrow-accessor family (string_return_analysis.ts) so a two-branch
179
+ // `at` classifies as a borrow on both branches.
180
+ internal func load_tail = (self, int i, out T) {
181
+ ```
182
+ #arch: c
183
+ long dir_index = i / 256L - 1L;
184
+ long slot = i % 256L;
185
+ T_buffer _tmp;
186
+ _tmp._vt = 0;
187
+ _tmp.data = ((unsigned long long*)self->chunks.data)[dir_index];
188
+ _tmp.cap = 256;
189
+ return T_load_from(&_tmp, slot);
190
+ ```
191
+ ```
192
+ #arch: aarch64
193
+ // x19 = self, x1 = i. The result forwards: x0 for T_SIZE <= 16, or
194
+ // the x8 sret slot — which is this function's OWN sret slot, passed
195
+ // through untouched, so the callee writes the caller's destination
196
+ // directly.
197
+ sub sp, sp, #32
198
+ mov x9, #256
199
+ sdiv x10, x1, x9
200
+ msub x1, x10, x9, x1
201
+ sub x10, x10, #1
202
+ ldr x11, [x19, #48]
203
+ ldr x12, [x11, x10, lsl #3]
204
+ str xzr, [sp]
205
+ str x12, [sp, #8]
206
+ mov x13, #256
207
+ str x13, [sp, #16]
208
+ mov x0, sp
209
+ bl T_load_from
210
+ add sp, sp, #32
211
+ ```
212
+ }
213
+
214
+ pub func at_or = (self, int i, T fallback, out T) {
215
+ if i >= 0 && i < self.length {
216
+ return self.at(i)
217
+ }
218
+ return fallback
219
+ }
220
+
221
+ // Bounds-checked access that traps when i is outside [0, self.length) —
222
+ // for indices that can't be proven at compile time and out-of-range is a
223
+ // bug, not a case.
224
+ pub func at_or_panic = (self, int i, out T) {
225
+ if i >= 0 && i < self.length {
226
+ return self.at(i)
227
+ }
228
+ panic("index out of range")
229
+ }
230
+
231
+ // Address of element `i` (INTERNAL — the ELEMENT BORROW primitive):
232
+ // `ref T x = sl.at(i)` lowers to this, binding the alias to the slot
233
+ // itself. The
234
+ // address is stable across any number of pushes (slabs are allocated
235
+ // once and never relocate), and the checker exempts `push` from borrow
236
+ // invalidation for this type, so the alias stays live and writes through
237
+ // it land in the list. The length constraint is the caller's proof
238
+ // obligation, exactly like `.at`.
239
+ internal func slot_ptr = (ref self, int i: i >= 0 && i < self.length, out ptr T) {
240
+ if i < self.items.cap {
241
+ return self.items.slot_ptr(i)
242
+ }
243
+ return self.slot_ptr_tail(i)
244
+ }
245
+
246
+ // The slot address in a directory chunk (pure address arithmetic — the
247
+ // slab never moves, so the pointer stays valid for the list's lifetime).
248
+ internal func slot_ptr_tail = (self, int i, out ptr T) {
249
+ ```
250
+ #arch: c
251
+ long dir_index = i / 256L - 1L;
252
+ long slot = i % 256L;
253
+ return (T*)(unsigned long long)((unsigned long long*)self->chunks.data)[dir_index] + slot;
254
+ ```
255
+ ```
256
+ #arch: aarch64
257
+ // x19 = self, x1 = i — the slot address, returned in x0.
258
+ mov x9, #256
259
+ sdiv x10, x1, x9
260
+ msub x1, x10, x9, x1
261
+ sub x10, x10, #1
262
+ ldr x11, [x19, #48]
263
+ ldr x12, [x11, x10, lsl #3]
264
+ mov x13, #T_SIZE
265
+ madd x0, x1, x13, x12
266
+ ```
267
+ }
268
+
269
+ // A deep copy: a fresh StableList<T> with an independent copy of every
270
+ // element. For mv element types (strings, mv value structs) each slot in
271
+ // the copy owns its own heap data (Buffer deep-copies on store), so the
272
+ // source and the copy share nothing. Not available for class/trait
273
+ // element types (the copy would share the stored instances — the checker
274
+ // rejects it). The element is bound to a local before push — the
275
+ // monomorphized body is never re-checked, so it can't rely on check-time
276
+ // call-arg hoisting for struct element types.
277
+ pub func copy = (self, out StableList<T>) {
278
+ var dst = StableList<T>()
279
+ var i = 0
280
+ while i < self.length {
281
+ var T v = self.at(i)
282
+ dst.push(v)
283
+ i += 1
284
+ }
285
+ return dst
286
+ }
287
+
288
+ // Chunk 0 first: `move` hands each element out (the binding's scope exit
289
+ // destroys it — exactly once), then `alloc(0)` retires the empty slab so
290
+ // the field destroy walks zero slots. Directory chunks: raw loop, each
291
+ // destroyed through a view whose cap is the chunk's FILL count (never
292
+ // its 256 capacity — uninitialized slots hold zeroes, and a user
293
+ // `#destroy` over zeroed elements is garbage). The directory's own slot
294
+ // array is reclaimed by the ordinary field destroy (plain free — pointer
295
+ // words own nothing).
296
+ // Only the directory slabs (chunks 1..) need explicit teardown — they are
297
+ // raw pointer words the field destroy cannot see — so each is destroyed
298
+ // through a cap=filled view (per-element destroy + slab free). Chunk 0
299
+ // (`items`) is reclaimed by the ordinary field destroy: the
300
+ // specialized per-element destroy guards zeroed slots (a calloc'd slot
301
+ // skips the element destroy instead of firing the user `#destroy` on
302
+ // unconstructed storage) and frees its own slab; pointer words own
303
+ // nothing.
304
+ // (The old `stage` relay field is gone — push's tail path stores into
305
+ // the chunk through a `Buffer.init_view` view.)
306
+ func #destroy = (ref self) {
307
+ self.destroy_tails()
308
+ }
309
+
310
+ // Raw: destroy directory chunks 1.. — per chunk, `filled` slots + the
311
+ // slab, via `T_buffer_destroy` on a cap=filled view.
312
+ internal func destroy_tails = (ref self) {
313
+ ```
314
+ #arch: c
315
+ long n_dir = (self->length - 1L) / 256L;
316
+ for (long c = 0; c < n_dir; c++) {
317
+ unsigned long long slab = ((unsigned long long*)self->chunks.data)[c];
318
+ if (slab == 0) {
319
+ continue;
320
+ }
321
+ long filled = (c == n_dir - 1L) ? (self->length - n_dir * 256L) : 256L;
322
+ T_buffer _tmp;
323
+ _tmp._vt = 0;
324
+ _tmp.data = slab;
325
+ _tmp.cap = filled;
326
+ T_buffer_destroy(&_tmp);
327
+ }
328
+ ```
329
+ ```
330
+ #arch: aarch64
331
+ // x19 = self. n_dir = (length - 1) / 256; per chunk, a cap=filled
332
+ // view on the stack and `bl T_buffer_destroy`.
333
+ sub sp, sp, #48
334
+ ldr x20, [x19, #8]
335
+ sub x20, x20, #1
336
+ mov x9, #256
337
+ sdiv x20, x20, x9
338
+ str xzr, [sp, #24]
339
+ .Lsl_dt_loop:
340
+ ldr x21, [sp, #24]
341
+ cmp x21, x20
342
+ b.ge .Lsl_dt_end
343
+ lsl x22, x21, #3
344
+ ldr x9, [x19, #48]
345
+ ldr x10, [x9, x22]
346
+ cmp x10, #0
347
+ b.eq .Lsl_dt_next
348
+ mov x11, #256
349
+ cmp x21, x20
350
+ b.ne .Lsl_dt_full
351
+ ldr x11, [x19, #8]
352
+ msub x11, x20, x9, x11
353
+ b .Lsl_dt_have
354
+ .Lsl_dt_full:
355
+ mov x11, #256
356
+ .Lsl_dt_have:
357
+ str xzr, [sp]
358
+ str x10, [sp, #8]
359
+ str x11, [sp, #16]
360
+ mov x0, sp
361
+ bl T_buffer_destroy
362
+ .Lsl_dt_next:
363
+ ldr x21, [sp, #24]
364
+ add x21, x21, #1
365
+ str x21, [sp, #24]
366
+ b .Lsl_dt_loop
367
+ .Lsl_dt_end:
368
+ add sp, sp, #48
369
+ ```
370
+ }
371
+ }
@@ -29,8 +29,8 @@ pub struct Directory {
29
29
  return DirectoryError.not_empty
30
30
  }
31
31
  } else {
32
- return DirectoryError.other
33
- }
32
+ return DirectoryError.other
33
+ }
34
34
  }
35
35
 
36
36
  // (static) Create a directory at `path`; returns errno (0 on success).
@@ -57,8 +57,8 @@ pub struct File {
57
57
  return FileError.already_exists
58
58
  }
59
59
  } else {
60
- return FileError.other
61
- }
60
+ return FileError.other
61
+ }
62
62
  }
63
63
 
64
64
  // Open the file at `path` with a C mode string ("r", "w", "a", …).
@@ -49,8 +49,8 @@ pub struct Http {
49
49
  return HttpError.connect
50
50
  }
51
51
  } else {
52
- return HttpError.other
53
- }
52
+ return HttpError.other
53
+ }
54
54
  }
55
55
 
56
56
  // Shared socket round-trip used by get/post. `method` is the request
@@ -276,21 +276,26 @@ pub mv struct string: Stringable, Hashable, Equatable {
276
276
  }
277
277
 
278
278
  // Replace all non-overlapping occurrences of needle with replacement,
279
- // left to right. Same edge behavior as replace_first.
279
+ // left to right. Same edge behavior as replace_first. Literal bytes
280
+ // between matches are copied in bulk runs (one `append_string_view` per
281
+ // run) — the old loop appended per byte.
280
282
  pub func replace_all = (self, view string needle, view string replacement, out string) {
281
283
  var sb = StringBuilder()
282
284
  var i = 0
283
285
  var int n = self.length
284
286
  var int nlen = needle.length
285
- while i < n {
286
- if nlen > 0 && i + nlen <= n && match_at(self, needle, i) {
287
+ var run_start = 0
288
+ while nlen > 0 && i + nlen <= n {
289
+ if match_at(self, needle, i) {
290
+ sb.append_string_view(self.slice(run_start, i))
287
291
  sb.append_string_view(replacement)
288
292
  i = i + nlen
293
+ run_start = i
289
294
  } else {
290
- sb.append_char(self.at(i))
291
- i = i + 1
295
+ i += 1
292
296
  }
293
297
  }
298
+ sb.append_string_view(self.slice(run_start, n))
294
299
  return sb.to_string()
295
300
  }
296
301