nomen-lang 0.3.0 → 0.5.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 (50) hide show
  1. package/NOMEN_AGENTS.md +7 -1
  2. package/core/System/Arena.nm +7 -7
  3. package/core/System/Awaitable.nm +21 -0
  4. package/core/System/BigInt.nm +5 -15
  5. package/core/System/Buffer.nm +140 -33
  6. package/core/System/Channel.nm +80 -29
  7. package/core/System/ClassBuffer.nm +89 -32
  8. package/core/System/Console.nm +4 -2
  9. package/core/System/Controls/Button.nm +4 -4
  10. package/core/System/Controls/CheckBox.nm +1 -1
  11. package/core/System/Controls/Text.nm +2 -2
  12. package/core/System/Controls/TextBox.nm +6 -6
  13. package/core/System/Controls/Window.nm +2 -2
  14. package/core/System/Fiber.nm +121 -0
  15. package/core/System/Graph.nm +4 -4
  16. package/core/System/LinkedList.nm +4 -4
  17. package/core/System/List.nm +6 -6
  18. package/core/System/Map.nm +30 -30
  19. package/core/System/Mutex.nm +57 -61
  20. package/core/System/Nursery.nm +10 -8
  21. package/core/System/Set.nm +24 -24
  22. package/core/System/Stream/Directory.nm +4 -4
  23. package/core/System/Stream/File.nm +19 -12
  24. package/core/System/Stream/Http.nm +83 -434
  25. package/core/System/Stream/Tcp.nm +441 -0
  26. package/core/System/String.nm +47 -18
  27. package/core/System/Task.nm +103 -4
  28. package/core/System/Text/JsonTree.nm +15 -15
  29. package/core/System/Thread.nm +83 -0
  30. package/core/System/bool.nm +2 -1
  31. package/core/System/char.nm +2 -1
  32. package/core/System/float.nm +2 -1
  33. package/core/System/float32.nm +2 -1
  34. package/core/System/float64.nm +2 -1
  35. package/core/System/int.nm +4 -2
  36. package/core/System/int16.nm +2 -1
  37. package/core/System/int32.nm +2 -1
  38. package/core/System/int64.nm +2 -1
  39. package/core/System/int8.nm +2 -1
  40. package/core/System/ufloat.nm +2 -1
  41. package/core/System/ufloat32.nm +2 -1
  42. package/core/System/ufloat64.nm +2 -1
  43. package/core/System/uint.nm +2 -1
  44. package/core/System/uint16.nm +2 -1
  45. package/core/System/uint32.nm +2 -1
  46. package/core/System/uint64.nm +2 -1
  47. package/core/System/uint8.nm +2 -1
  48. package/core/docs/System/BigInt.md +0 -1
  49. package/dist/index.mjs +20401 -13796
  50. package/package.json +1 -1
@@ -1,11 +1,13 @@
1
1
  // Channel — thread-safe single-direction queue for passing values between
2
2
  // tasks. Unbounded, FIFO, blocking receive.
3
3
  //
4
- // The queue node's payload is two words wide: every message carries a
5
- // (value, len) pair. `send`/`receive` move uint64 values (len stays 0);
6
- // `send_string`/`receive_string` move fat strings without truncating the len
7
- // half. A typed Channel<T> wrapper can come later — the storage story is
8
- // identical, only the call-site types change.
4
+ // The queue node's payload is three words wide: every message carries a
5
+ // (value, len, kind) triple. `send`/`receive` move uint64 values (len and
6
+ // kind stay 0); `send_string`/`receive_string` move fat strings (kind = 1)
7
+ // without truncating the len half. `#destroy` frees only string payloads —
8
+ // a raw uint64 payload is a value, not a pointer. A typed Channel<T>
9
+ // wrapper can come later — the storage story is identical, only the
10
+ // call-site types change.
9
11
  //
10
12
  // String payloads are copied in and moved out: `send_string` duplicates the
11
13
  // value into the node (the sender keeps its own string), and
@@ -25,17 +27,20 @@ pub class Channel: Sendable {
25
27
  pub var uint64 not_empty_cv = 0
26
28
  pub var uint64 head = 0
27
29
  pub var uint64 tail = 0
30
+ // Head of the parked-fiber wait list (guarded by mu). Send wakes it.
31
+ pub var uint64 waiters = 0
28
32
 
29
33
  pub func #init = (self) {
30
34
  ```
31
35
  #arch: c
32
36
  #scope: file
33
37
  #include <pthread.h>
34
- struct nomen_channel_node {
35
- unsigned long long value;
36
- unsigned long long len;
37
- struct nomen_channel_node *next;
38
- };
38
+ struct nomen_channel_node {
39
+ unsigned long long value;
40
+ unsigned long long len;
41
+ unsigned long long kind; // 0 = uint64 payload, 1 = owned string
42
+ struct nomen_channel_node *next;
43
+ };
39
44
  ```
40
45
  ```
41
46
  #arch: c
@@ -45,6 +50,7 @@ pub class Channel: Sendable {
45
50
  pthread_cond_init((pthread_cond_t *)self->not_empty_cv, NULL);
46
51
  self->head = 0;
47
52
  self->tail = 0;
53
+ self->waiters = 0;
48
54
  ```
49
55
  ```
50
56
  #arch: aarch64
@@ -60,6 +66,7 @@ pub class Channel: Sendable {
60
66
  bl _pthread_cond_init
61
67
  str xzr, [x19, #24]
62
68
  str xzr, [x19, #32]
69
+ str xzr, [x19, #40]
63
70
  ```
64
71
  }
65
72
 
@@ -69,6 +76,7 @@ pub class Channel: Sendable {
69
76
  struct nomen_channel_node *n = (struct nomen_channel_node *)malloc(sizeof(struct nomen_channel_node));
70
77
  n->value = value;
71
78
  n->len = 0;
79
+ n->kind = 0;
72
80
  n->next = NULL;
73
81
  pthread_mutex_lock((pthread_mutex_t *)self->mu);
74
82
  if (self->tail) {
@@ -78,25 +86,27 @@ pub class Channel: Sendable {
78
86
  }
79
87
  self->tail = (unsigned long long)n;
80
88
  pthread_cond_signal((pthread_cond_t *)self->not_empty_cv);
89
+ __nomen_fiber_waitq_wake((struct nomen_fiber **)&self->waiters);
81
90
  pthread_mutex_unlock((pthread_mutex_t *)self->mu);
82
91
  ```
83
92
  ```
84
93
  #arch: aarch64
85
94
  // x19 = self (set by prologue), x1 = value
86
95
  str x1, [sp, #-16]! // save value on stack
87
- mov x0, #24 // node = { value, len, next }
96
+ mov x0, #32 // node = { value, len, kind, next }
88
97
  bl _malloc // x0 = node
89
98
  ldr x1, [sp], #16 // restore value
90
99
  str x1, [x0, #0] // node->value = value
91
100
  str xzr, [x0, #8] // node->len = 0
92
- str xzr, [x0, #16] // node->next = NULL
101
+ str xzr, [x0, #16] // node->kind = 0 (uint64 payload)
102
+ str xzr, [x0, #24] // node->next = NULL
93
103
  str x0, [sp, #-16]! // save node on stack
94
104
  ldr x0, [x19, #8] // self->mu
95
105
  bl _pthread_mutex_lock
96
106
  ldr x2, [sp], #16 // restore node
97
107
  ldr x1, [x19, #32] // tail
98
108
  cbz x1, .Lchannel_send_empty
99
- str x2, [x1, #16] // tail->next = node
109
+ str x2, [x1, #24] // tail->next = node
100
110
  b .Lchannel_send_enqueued
101
111
  .Lchannel_send_empty:
102
112
  str x2, [x19, #24] // head = node
@@ -104,6 +114,8 @@ pub class Channel: Sendable {
104
114
  str x2, [x19, #32] // tail = node
105
115
  ldr x0, [x19, #16] // not_empty_cv
106
116
  bl _pthread_cond_signal
117
+ add x0, x19, #40 // &self->waiters
118
+ bl ___nomen_fiber_waitq_wake
107
119
  ldr x0, [x19, #8] // self->mu
108
120
  bl _pthread_mutex_unlock
109
121
  ```
@@ -120,6 +132,7 @@ pub class Channel: Sendable {
120
132
  nomen_string _v = value.ptr ? nomen_str_dup(value) : (nomen_string){0, 0};
121
133
  n->value = (unsigned long long)_v.ptr;
122
134
  n->len = (unsigned long long)_v.len;
135
+ n->kind = 1;
123
136
  n->next = NULL;
124
137
  pthread_mutex_lock((pthread_mutex_t *)self->mu);
125
138
  if (self->tail) {
@@ -129,13 +142,14 @@ pub class Channel: Sendable {
129
142
  }
130
143
  self->tail = (unsigned long long)n;
131
144
  pthread_cond_signal((pthread_cond_t *)self->not_empty_cv);
145
+ __nomen_fiber_waitq_wake((struct nomen_fiber **)&self->waiters);
132
146
  pthread_mutex_unlock((pthread_mutex_t *)self->mu);
133
147
  ```
134
148
  ```
135
149
  #arch: aarch64
136
150
  // x19 = self (set by prologue), x1 = value.ptr, x2 = value.len
137
151
  stp x1, x2, [sp, #-32]! // [sp]=ptr [sp+8]=len [sp+16]=node scratch
138
- mov x0, #24 // node = { value, len, next }
152
+ mov x0, #32 // node = { value, len, kind, next }
139
153
  bl _malloc // x0 = node
140
154
  str x0, [sp, #16] // save node across the strdup call
141
155
  ldr x1, [sp, #0] // value.ptr
@@ -150,13 +164,15 @@ pub class Channel: Sendable {
150
164
  str x0, [x2, #0] // node->value = copied ptr
151
165
  ldr x1, [sp, #8] // value.len
152
166
  str x1, [x2, #8] // node->len = len
153
- str xzr, [x2, #16] // node->next = NULL
167
+ mov x1, #1
168
+ str x1, [x2, #16] // node->kind = 1 (owned string payload)
169
+ str xzr, [x2, #24] // node->next = NULL
154
170
  ldr x0, [x19, #8] // self->mu
155
171
  bl _pthread_mutex_lock
156
172
  ldr x2, [sp, #16] // restore node
157
173
  ldr x1, [x19, #32] // tail
158
174
  cbz x1, .Lchannel_send_str_empty
159
- str x2, [x1, #16] // tail->next = node
175
+ str x2, [x1, #24] // tail->next = node
160
176
  b .Lchannel_send_str_enqueued
161
177
  .Lchannel_send_str_empty:
162
178
  str x2, [x19, #24] // head = node
@@ -175,7 +191,13 @@ pub class Channel: Sendable {
175
191
  #arch: c
176
192
  pthread_mutex_lock((pthread_mutex_t *)self->mu);
177
193
  while (!self->head) {
178
- pthread_cond_wait((pthread_cond_t *)self->not_empty_cv, (pthread_mutex_t *)self->mu);
194
+ // Fibers park instead of blocking; a cancelled fiber returns the
195
+ // zero value rather than waiting forever (cooperative cancel).
196
+ if (!__nomen_fiber_waitq_park(
197
+ (struct nomen_fiber **)&self->waiters, (void *)self->mu, (void *)self->not_empty_cv)) {
198
+ pthread_mutex_unlock((pthread_mutex_t *)self->mu);
199
+ return 0;
200
+ }
179
201
  }
180
202
  struct nomen_channel_node *n = (struct nomen_channel_node *)self->head;
181
203
  self->head = (unsigned long long)n->next;
@@ -193,13 +215,21 @@ pub class Channel: Sendable {
193
215
  .Lchannel_receive_wait:
194
216
  ldr x1, [x19, #24] // head
195
217
  cbnz x1, .Lchannel_receive_got
196
- ldr x0, [x19, #16] // not_empty_cv
218
+ add x0, x19, #40 // &self->waiters
197
219
  ldr x1, [x19, #8] // mu
198
- bl _pthread_cond_wait
220
+ ldr x2, [x19, #16] // not_empty_cv
221
+ bl ___nomen_fiber_waitq_park
222
+ cbz x0, .Lchannel_receive_cancelled
199
223
  b .Lchannel_receive_wait
224
+ .Lchannel_receive_cancelled:
225
+ // Cancelled while waiting: return the zero value.
226
+ ldr x0, [x19, #8] // self->mu
227
+ bl _pthread_mutex_unlock
228
+ mov x0, #0
229
+ b .Lchannel_receive_end
200
230
  .Lchannel_receive_got:
201
231
  // x1 = node
202
- ldr x2, [x1, #16] // node->next
232
+ ldr x2, [x1, #24] // node->next
203
233
  str x2, [x19, #24] // head = node->next
204
234
  cbnz x2, .Lchannel_receive_not_empty
205
235
  str xzr, [x19, #32] // tail = 0
@@ -211,6 +241,7 @@ pub class Channel: Sendable {
211
241
  ldr x0, [x19, #8] // self->mu
212
242
  bl _pthread_mutex_unlock
213
243
  ldr x0, [sp], #16 // pop value → x0 (return)
244
+ .Lchannel_receive_end:
214
245
  ```
215
246
  }
216
247
 
@@ -223,7 +254,11 @@ pub class Channel: Sendable {
223
254
  #arch: c
224
255
  pthread_mutex_lock((pthread_mutex_t *)self->mu);
225
256
  while (!self->head) {
226
- pthread_cond_wait((pthread_cond_t *)self->not_empty_cv, (pthread_mutex_t *)self->mu);
257
+ if (!__nomen_fiber_waitq_park(
258
+ (struct nomen_fiber **)&self->waiters, (void *)self->mu, (void *)self->not_empty_cv)) {
259
+ pthread_mutex_unlock((pthread_mutex_t *)self->mu);
260
+ return (nomen_string){ 0, 0 };
261
+ }
227
262
  }
228
263
  struct nomen_channel_node *n = (struct nomen_channel_node *)self->head;
229
264
  self->head = (unsigned long long)n->next;
@@ -243,13 +278,23 @@ pub class Channel: Sendable {
243
278
  .Lchannel_receive_str_wait:
244
279
  ldr x1, [x19, #24] // head
245
280
  cbnz x1, .Lchannel_receive_str_got
246
- ldr x0, [x19, #16] // not_empty_cv
281
+ add x0, x19, #40 // &self->waiters
247
282
  ldr x1, [x19, #8] // mu
248
- bl _pthread_cond_wait
283
+ ldr x2, [x19, #16] // not_empty_cv
284
+ bl ___nomen_fiber_waitq_park
285
+ cbz x0, .Lchannel_receive_str_cancelled
249
286
  b .Lchannel_receive_str_wait
287
+ .Lchannel_receive_str_cancelled:
288
+ // Cancelled while waiting: return the empty fat string (x0 = ptr,
289
+ // x1 = len).
290
+ ldr x0, [x19, #8] // self->mu
291
+ bl _pthread_mutex_unlock
292
+ mov x0, #0
293
+ mov x1, #0
294
+ b .Lchannel_receive_str_end
250
295
  .Lchannel_receive_str_got:
251
296
  // x1 = node
252
- ldr x2, [x1, #16] // node->next
297
+ ldr x2, [x1, #24] // node->next
253
298
  str x2, [x19, #24] // head = node->next
254
299
  cbnz x2, .Lchannel_receive_str_not_empty
255
300
  str xzr, [x19, #32] // tail = 0
@@ -264,6 +309,7 @@ pub class Channel: Sendable {
264
309
  ldr x0, [x19, #8] // self->mu
265
310
  bl _pthread_mutex_unlock
266
311
  ldp x0, x1, [sp], #16 // pop pair → x0/x1 (return)
312
+ .Lchannel_receive_str_end:
267
313
  ```
268
314
  }
269
315
 
@@ -271,12 +317,13 @@ pub class Channel: Sendable {
271
317
  ```
272
318
  #arch: c
273
319
  if (self->mu) {
274
- // Drain any pending nodes. A string payload that was never
275
- // received still owns its buffer — free it with the node.
320
+ // Drain any pending nodes. A string payload (kind = 1) that was
321
+ // never received still owns its buffer — free it with the node.
322
+ // A uint64 payload (kind = 0) is a raw value, never a pointer.
276
323
  struct nomen_channel_node *n = (struct nomen_channel_node *)self->head;
277
324
  while (n) {
278
325
  struct nomen_channel_node *next = n->next;
279
- if (n->value) free((void *)n->value);
326
+ if (n->kind == 1 && n->value) free((void *)n->value);
280
327
  free(n);
281
328
  n = next;
282
329
  }
@@ -295,14 +342,18 @@ pub class Channel: Sendable {
295
342
  // x19 = self (set by prologue)
296
343
  ldr x0, [x19, #8] // self->mu
297
344
  cbz x0, .Lchannel_destroy_done
298
- // Drain nodes (freeing any unconsumed string payload)
345
+ // Drain nodes (freeing only owned-string payloads; a uint64
346
+ // payload is a raw value, never a pointer)
299
347
  ldr x1, [x19, #24] // head
300
348
  .Lchannel_destroy_drain:
301
349
  cbz x1, .Lchannel_destroy_drained
302
- ldr x2, [x1, #16] // next (before calls clobber x1)
350
+ ldr x2, [x1, #24] // next (before calls clobber x1)
303
351
  stp x1, x2, [sp, #-16]! // save node + next
352
+ ldr x3, [x1, #16] // payload kind (1 = owned string)
304
353
  ldr x0, [x1, #0] // payload ptr
305
354
  cbz x0, .Lchannel_destroy_no_payload
355
+ cmp x3, #1
356
+ b.ne .Lchannel_destroy_no_payload
306
357
  bl _free
307
358
  .Lchannel_destroy_no_payload:
308
359
  ldr x0, [sp, #0] // node
@@ -8,27 +8,28 @@
8
8
  * A flat buffer of owned class pointers of type T — the backing store for collections of classes
9
9
  **/
10
10
  pub struct ClassBuffer<T> {
11
- var uint64 data = 0
12
- var cap = 0
11
+ internal var uint64 data = 0
12
+ // Publicly readable; grown only by ClassBuffer's own methods (`readonly`).
13
+ readonly cap = 0
13
14
 
14
- func alloc_int = (ref self, int size, out int: out >= size) {
15
+ internal func alloc_int = (ref self, int size, out int: out >= size) {
15
16
  self.cap = size
16
17
  self.data = calloc(size, 8)
17
18
  return self.cap
18
19
  }
19
20
 
20
- // Semantic parity with Buffer<T>.alloc_T: generic code (e.g. Map#init's
21
- // `self.values.alloc_T(N)`) forwards to either Buffer — where alloc_T is
21
+ // Semantic parity with Buffer<T>.alloc: generic code (e.g. Map#init's
22
+ // `self.values.alloc(N)`) forwards to either Buffer — where alloc is
22
23
  // T_SIZE-aware (string slots are 16 bytes) — or this ClassBuffer, whose
23
24
  // slots are always one pointer wide. The compiler routes value-struct
24
25
  // element types to Buffer and class/trait elements to ClassBuffer
25
26
  // (monomorphize's field rewrite), so a call arriving HERE is always
26
27
  // pointer-sized: delegate to alloc_int.
27
- func alloc_T = (ref self, int size, out int: out >= size) {
28
+ func alloc = (ref self, int size, out int: out >= size) {
28
29
  return self.alloc_int(size)
29
30
  }
30
31
 
31
- func grow_int = (ref self, int needed, out int: out >= needed) {
32
+ internal func grow_int = (ref self, int needed, out int: out >= needed) {
32
33
  if self.cap >= needed {
33
34
  return self.cap
34
35
  }
@@ -43,7 +44,7 @@ pub struct ClassBuffer<T> {
43
44
  return self.cap
44
45
  }
45
46
 
46
- inline func load_int = (self, int i: i >= 0 && i < self.cap, out int) {
47
+ internal inline func load_int = (self, int i: i >= 0 && i < self.cap, out int) {
47
48
  ```
48
49
  #arch: c
49
50
  return ((long*)(unsigned long long)self->data)[i];
@@ -57,14 +58,14 @@ pub struct ClassBuffer<T> {
57
58
  ```
58
59
  }
59
60
 
60
- // T_SIZE-aware aliases of the _int primitives. ClassBuffer only ever holds
61
- // 8-byte class pointers (T_SIZE is 8), so these are behaviorally identical
62
- // to their _int counterparts — they exist so List<T> can call a single
63
- // uniform `_T` API whether T is a value struct (Buffer) or a class
64
- // (ClassBuffer, after the Buffer<T> field is rewritten). Bodies use 8-byte
65
- // slots, matching _int.
61
+ // Generic (T-typed) counterparts of the _int primitives. ClassBuffer only
62
+ // ever holds 8-byte class pointers (T_SIZE is 8), so these are
63
+ // behaviorally identical to their _int counterparts — they exist so
64
+ // List<T> can call a single uniform API whether T is a value struct
65
+ // (Buffer) or a class (ClassBuffer, after the Buffer<T> field is
66
+ // rewritten). Bodies use 8-byte slots, matching _int.
66
67
 
67
- func grow_T = (ref self, int needed, out int: out >= needed) {
68
+ func grow = (ref self, int needed, out int: out >= needed) {
68
69
  if self.cap >= needed {
69
70
  return self.cap
70
71
  }
@@ -79,7 +80,7 @@ pub struct ClassBuffer<T> {
79
80
  return self.cap
80
81
  }
81
82
 
82
- inline func load_T = (self, int i: i >= 0 && i < self.cap, out T) {
83
+ inline func load = (self, int i: i >= 0 && i < self.cap, out T) {
83
84
  ```
84
85
  #arch: c
85
86
  return (void*)((long*)(unsigned long long)self->data)[i];
@@ -94,11 +95,15 @@ pub struct ClassBuffer<T> {
94
95
  }
95
96
 
96
97
  // Store the owned class pointer `val` into slot `i`, which MUST be fresh
97
- // (calloc'd, or zeroed by move_T/move_int). This is a plain pointer
98
- // store — it does NOT destroy/free the slot's previous occupant, so a
99
- // second `store_T` at an occupied index leaks that instance. Use
100
- // replace_T (or replace_int) to overwrite a live slot.
101
- inline func store_T = (ref self, int i: i >= 0 && i < self.cap, T val: val != 0) {
98
+ // (calloc'd, or zeroed by move/move_int). Takes OWNERSHIP of `val`
99
+ // (`move`): the slot's #destroy reclaims the instance, so the caller must
100
+ // not free its copy — a fresh constructor result transfers here directly,
101
+ // and a local must be moved (its own cleanup is suppressed at the call
102
+ // site). This is a plain pointer store — it does NOT destroy/free the
103
+ // slot's previous occupant, so a second `store` at an occupied index
104
+ // leaks that instance. Use replace (or replace_int) to overwrite a live
105
+ // slot.
106
+ internal inline func store = (ref self, int i: i >= 0 && i < self.cap, move T val: val != 0) {
102
107
  ```
103
108
  #arch: c
104
109
  ((long*)(unsigned long long)self->data)[i] = (long)val;
@@ -116,11 +121,11 @@ pub struct ClassBuffer<T> {
116
121
  // buffer no longer references it. Declared `move out T` so the borrow
117
122
  // checker treats the result as OWNED — the slot is relinquished, so the
118
123
  // caller is the sole remaining owner. This is the owning-extract primitive
119
- // (parallel to List.pop); use it (not load_T) when transferring ownership
124
+ // (parallel to List.pop); use it (not load) when transferring ownership
120
125
  // out of a ClassBuffer slot, or moving a value into a fresh owning
121
- // container — load_T returns a BORROW (the slot is unchanged) and storing
126
+ // container — load returns a BORROW (the slot is unchanged) and storing
122
127
  // it elsewhere would create shared ownership (double-free at destroy).
123
- inline func move_T = (ref self, int i: i >= 0 && i < self.cap, move out T) {
128
+ inline func move = (ref self, int i: i >= 0 && i < self.cap, move out T) {
124
129
  ```
125
130
  #arch: c
126
131
  long *slots = (long*)(unsigned long long)self->data;
@@ -138,7 +143,57 @@ pub struct ClassBuffer<T> {
138
143
  ```
139
144
  }
140
145
 
141
- inline func store_int = (ref self, int i: i >= 0 && i < self.cap, int val: val != 0) {
146
+ // Apply `f` to slot `i` in place — the owning-counterpart of Buffer's
147
+ // modify for class elements. `f` receives the current instance pointer
148
+ // as a borrow and returns the replacement instance (fresh, or the same
149
+ // pointer); the primitive frees the displaced instance (destroy + free)
150
+ // unless `f` returned it. Storing the returned instance raw is sound:
151
+ // the slot takes sole ownership and `f`'s return is not retained
152
+ // elsewhere. NOT `inline`: the body calls through the fn pointer.
153
+ func modify = (ref self, int i: i >= 0 && i < self.cap, func (T, out T) f) {
154
+ ```
155
+ #arch: c
156
+ long *slots = (long*)(unsigned long long)self->data;
157
+ void *old = (void*)slots[i];
158
+ void *new_value = (void*)((void *(*)(void *, void *))f->code)(f->env, old);
159
+ slots[i] = (long)new_value;
160
+ if (old != new_value) {
161
+ T_destroy(old);
162
+ free(old);
163
+ }
164
+ ```
165
+ ```
166
+ #arch: aarch64
167
+ // x19 = self, x1 = i, x2 = f (a closure descriptor — env goes in x0,
168
+ // the old slot value shifts to x1). Class slots are one pointer wide.
169
+ stp x20, x21, [sp, #-16]!
170
+ stp x22, x23, [sp, #-16]!
171
+ stp x24, x25, [sp, #-16]!
172
+ mov x22, x2
173
+ ldr x9, [x19, #8]
174
+ lsl x3, x1, #3
175
+ add x20, x9, x3
176
+ ldr x24, [x22]
177
+ ldr x0, [x22, #8]
178
+ ldr x1, [x20]
179
+ blr x24
180
+ mov x23, x0
181
+ ldr x9, [x20]
182
+ str x23, [x20]
183
+ cmp x9, x23
184
+ b.eq .Lcb_modify_T_done
185
+ mov x0, x9
186
+ bl T_destroy
187
+ mov x0, x9
188
+ bl _free
189
+ .Lcb_modify_T_done:
190
+ ldp x24, x25, [sp], #16
191
+ ldp x22, x23, [sp], #16
192
+ ldp x20, x21, [sp], #16
193
+ ```
194
+ }
195
+
196
+ internal inline func store_int = (ref self, int i: i >= 0 && i < self.cap, int val: val != 0) {
142
197
  ```
143
198
  #arch: c
144
199
  ((long*)(unsigned long long)self->data)[i] = val;
@@ -152,7 +207,7 @@ pub struct ClassBuffer<T> {
152
207
  ```
153
208
  }
154
209
 
155
- inline func move_int = (ref self, int i: i >= 0 && i < self.cap, out int) {
210
+ internal inline func move_int = (ref self, int i: i >= 0 && i < self.cap, out int) {
156
211
  ```
157
212
  #arch: c
158
213
  long *slots = (long*)(unsigned long long)self->data;
@@ -172,7 +227,7 @@ pub struct ClassBuffer<T> {
172
227
 
173
228
  // Store into a slot, freeing the previous value (it is an owned class
174
229
  // pointer): run its destroy then free it before overwriting.
175
- func replace_int = (ref self, int i: i >= 0 && i < self.cap, int val: val != 0) {
230
+ internal func replace_int = (ref self, int i: i >= 0 && i < self.cap, int val: val != 0) {
176
231
  ```
177
232
  #arch: c
178
233
  long *slots = (long*)(unsigned long long)self->data;
@@ -204,9 +259,11 @@ pub struct ClassBuffer<T> {
204
259
  }
205
260
 
206
261
  // T-typed alias of replace_int: free the outgoing class pointer (destroy +
207
- // free) then store the new one. Used by List.set so the uniform `_T` API
262
+ // free) then store the new one. Used by List.set so the uniform API
208
263
  // preserves ownership for class elements.
209
- func replace_T = (ref self, int i: i >= 0 && i < self.cap, T val: val != 0) {
264
+ // Overwrite a live slot: destroy + free the displaced instance, then take
265
+ // OWNERSHIP of `val` (`move`) — same transfer rule as store.
266
+ func replace = (ref self, int i: i >= 0 && i < self.cap, move T val: val != 0) {
210
267
  ```
211
268
  #arch: c
212
269
  long *slots = (long*)(unsigned long long)self->data;
@@ -239,10 +296,10 @@ pub struct ClassBuffer<T> {
239
296
 
240
297
  // Move slot src into slot dst: reclaim dst's previous instance (destroy +
241
298
  // free), take over src's pointer, and zero src — exactly one slot owns the
242
- // instance afterwards. The class-buffer counterpart of Buffer.shift_T,
299
+ // instance afterwards. The class-buffer counterpart of Buffer.shift,
243
300
  // used by Map/Set.remove's backward-shift so shifting entries neither
244
301
  // duplicates nor orphans owned instances.
245
- func shift_T = (ref self, int dst: dst >= 0 && dst < self.cap, int src: src >= 0 && src < self.cap) {
302
+ func shift = (ref self, int dst: dst >= 0 && dst < self.cap, int src: src >= 0 && src < self.cap) {
246
303
  ```
247
304
  #arch: c
248
305
  long *slots = (long*)(unsigned long long)self->data;
@@ -286,7 +343,7 @@ pub struct ClassBuffer<T> {
286
343
  // pointers, returned as a `view T` (T is a class, so each element is an
287
344
  // 8-byte pointer). Borrows from self — invalidated on reassignment. Mirrors
288
345
  // Buffer.slice; ClassBuffer stores 8-byte slots, so the element width is 8.
289
- func slice = (self, int start: start >= 0, int end: end >= start, out view T) {
346
+ func slice = (self, int start: start >= 0, int end: end >= start && end <= self.cap, out view T) {
290
347
  ```
291
348
  #arch: c
292
349
  nomen_view _r;
@@ -5,7 +5,9 @@ pub struct Console {
5
5
  pub func write = (string line) {
6
6
  ```
7
7
  #arch: c
8
- printf("%s", line);
8
+ // Fat-string ABI: strings pass their ptr half. printf %s stops at
9
+ // the first embedded NUL on both backends (known display limit).
10
+ printf("%s", line.ptr);
9
11
  ```
10
12
  ```
11
13
  #arch: aarch64
@@ -25,7 +27,7 @@ pub struct Console {
25
27
  pub func write_line = (string line) {
26
28
  ```
27
29
  #arch: c
28
- printf("%s\n", line);
30
+ printf("%s\n", line.ptr);
29
31
  ```
30
32
  ```
31
33
  #arch: aarch64
@@ -17,7 +17,7 @@ pub class Button : Control {
17
17
  ((void(*)(id, SEL, id))objc_msgSend)(
18
18
  btn, sel_registerName("setTitle:"),
19
19
  ((id(*)(id, SEL, const char*))objc_msgSend)(
20
- (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title));
20
+ (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title.ptr));
21
21
  id contentView = ((id(*)(id, SEL))objc_msgSend)(
22
22
  (id)window->handle, sel_registerName("contentView"));
23
23
  ((void(*)(id, SEL, id))objc_msgSend)(
@@ -53,7 +53,7 @@ pub class Button : Control {
53
53
  ((void(*)(id, SEL, unsigned long, id, id))objc_msgSend)(
54
54
  btn, sel_registerName("setTitle:forState:"),
55
55
  ((id(*)(id, SEL, const char*))objc_msgSend)(
56
- (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title),
56
+ (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title.ptr),
57
57
  0); // UIControlStateNormal = 0
58
58
  id parentView = ((id(*)(id, SEL))objc_msgSend)(
59
59
  ((id(*)(id, SEL))objc_msgSend)(
@@ -123,7 +123,7 @@ pub class Button : Control {
123
123
  ((void(*)(id, SEL, id))objc_msgSend)(
124
124
  (id)self->handle, sel_registerName("setTitle:"),
125
125
  ((id(*)(id, SEL, const char*))objc_msgSend)(
126
- (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title));
126
+ (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title.ptr));
127
127
  ```
128
128
  ```
129
129
  #arch: aarch64_use_c
@@ -139,7 +139,7 @@ pub class Button : Control {
139
139
  ((void(*)(id, SEL, unsigned long, id, id))objc_msgSend)(
140
140
  (id)self->handle, sel_registerName("setTitle:forState:"),
141
141
  ((id(*)(id, SEL, const char*))objc_msgSend)(
142
- (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title),
142
+ (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title.ptr),
143
143
  0); // UIControlStateNormal = 0
144
144
  ```
145
145
  ```
@@ -109,7 +109,7 @@ pub class CheckBox : Control {
109
109
  ((void(*)(id, SEL, id))objc_msgSend)(
110
110
  (id)self->handle, sel_registerName("setTitle:"),
111
111
  ((id(*)(id, SEL, const char*))objc_msgSend)(
112
- (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title));
112
+ (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title.ptr));
113
113
  ```
114
114
  ```
115
115
  #arch: aarch64_use_c
@@ -100,7 +100,7 @@ pub class Text: Control {
100
100
  ((void(*)(id, SEL, id))objc_msgSend)(
101
101
  (id)self->handle, sel_registerName("setStringValue:"),
102
102
  ((id(*)(id, SEL, const char*))objc_msgSend)(
103
- (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), text));
103
+ (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), text.ptr));
104
104
  ```
105
105
  ```
106
106
  #arch: aarch64_use_c
@@ -116,7 +116,7 @@ pub class Text: Control {
116
116
  ((void(*)(id, SEL, id))objc_msgSend)(
117
117
  (id)self->handle, sel_registerName("setText:"),
118
118
  ((id(*)(id, SEL, const char*))objc_msgSend)(
119
- (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), text));
119
+ (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), text.ptr));
120
120
  ```
121
121
  ```
122
122
  #arch: aarch64_use_c
@@ -133,7 +133,7 @@ pub class TextBox : Control {
133
133
  ((void(*)(id, SEL, id))objc_msgSend)(
134
134
  (id)self->handle, sel_registerName("setStringValue:"),
135
135
  ((id(*)(id, SEL, const char*))objc_msgSend)(
136
- (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), text));
136
+ (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), text.ptr));
137
137
  ```
138
138
  ```
139
139
  #arch: aarch64_use_c
@@ -149,7 +149,7 @@ pub class TextBox : Control {
149
149
  ((void(*)(id, SEL, id))objc_msgSend)(
150
150
  (id)self->handle, sel_registerName("setText:"),
151
151
  ((id(*)(id, SEL, const char*))objc_msgSend)(
152
- (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), text));
152
+ (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), text.ptr));
153
153
  ```
154
154
  ```
155
155
  #arch: aarch64_use_c
@@ -169,7 +169,7 @@ pub class TextBox : Control {
169
169
  (id)self->handle, sel_registerName("stringValue"));
170
170
  const char *cstr = ((const char*(*)(id, SEL))objc_msgSend)(
171
171
  str, sel_registerName("UTF8String"));
172
- return strdup(cstr);
172
+ return (nomen_string){ strdup(cstr), (long)strlen(cstr) };
173
173
  ```
174
174
  ```
175
175
  #arch: aarch64_use_c
@@ -178,7 +178,7 @@ pub class TextBox : Control {
178
178
  (id)self->handle, sel_registerName("stringValue"));
179
179
  const char *cstr = ((const char*(*)(id, SEL))objc_msgSend)(
180
180
  str, sel_registerName("UTF8String"));
181
- return strdup(cstr);
181
+ return (nomen_string){ strdup(cstr), (long)strlen(cstr) };
182
182
  ```
183
183
  ```
184
184
  #arch: c
@@ -187,7 +187,7 @@ pub class TextBox : Control {
187
187
  (id)self->handle, sel_registerName("text"));
188
188
  const char *cstr = ((const char*(*)(id, SEL))objc_msgSend)(
189
189
  str, sel_registerName("UTF8String"));
190
- return strdup(cstr);
190
+ return (nomen_string){ strdup(cstr), (long)strlen(cstr) };
191
191
  ```
192
192
  ```
193
193
  #arch: aarch64_use_c
@@ -196,7 +196,7 @@ pub class TextBox : Control {
196
196
  (id)self->handle, sel_registerName("text.ptr"));
197
197
  const char *cstr = ((const char*(*)(id, SEL))objc_msgSend)(
198
198
  str, sel_registerName("UTF8String"));
199
- return strdup(cstr);
199
+ return (nomen_string){ strdup(cstr), (long)strlen(cstr) };
200
200
  ```
201
201
  }
202
202
 
@@ -22,7 +22,7 @@ pub class Window: Control {
22
22
  ((id(*)(id, SEL, id))objc_msgSend)(
23
23
  win, sel_registerName("setTitle:"),
24
24
  ((id(*)(id, SEL, const char*))objc_msgSend)(
25
- (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title));
25
+ (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title.ptr));
26
26
  ((void(*)(id, SEL, double, double))objc_msgSend)(
27
27
  win, sel_registerName("setFrameTopLeftPoint:"), 100.0, 100.0);
28
28
  ((void(*)(id, SEL, id))objc_msgSend)(win, sel_registerName("makeKeyAndOrderFront:"), (id)0);
@@ -115,7 +115,7 @@ pub class Window: Control {
115
115
  ((id(*)(id, SEL, id))objc_msgSend)(
116
116
  (id)self->handle, sel_registerName("setTitle:"),
117
117
  ((id(*)(id, SEL, const char*))objc_msgSend)(
118
- (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title));
118
+ (id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title.ptr));
119
119
  ```
120
120
  ```
121
121
  #arch: aarch64_use_c