nomen-lang 0.0.19 → 0.2.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 (62) hide show
  1. package/NOMEN_AGENTS.md +57 -12
  2. package/core/System/Array.nm +11 -11
  3. package/core/System/Buffer.nm +2 -2
  4. package/core/System/Channel.nm +146 -12
  5. package/core/System/ClassBuffer.nm +2 -2
  6. package/core/System/Console.nm +28 -17
  7. package/core/System/Controls/Button.nm +96 -8
  8. package/core/System/Controls/CheckBox.nm +117 -12
  9. package/core/System/Controls/Container.nm +114 -8
  10. package/core/System/Controls/Text.nm +98 -8
  11. package/core/System/Controls/TextBox.nm +120 -10
  12. package/core/System/Controls/Window.nm +231 -23
  13. package/core/System/Graph.nm +1 -1
  14. package/core/System/Init.nm +4 -8
  15. package/core/System/LinkedList.nm +1 -1
  16. package/core/System/List.nm +3 -3
  17. package/core/System/Map.nm +5 -5
  18. package/core/System/Math.nm +10 -32
  19. package/core/System/Result.nm +1 -1
  20. package/core/System/Set.nm +4 -4
  21. package/core/System/Stream/Directory.nm +189 -63
  22. package/core/System/Stream/DirectoryError.nm +21 -0
  23. package/core/System/Stream/File.nm +339 -141
  24. package/core/System/Stream/FileError.nm +21 -0
  25. package/core/System/Stream/Http.nm +109 -44
  26. package/core/System/Stream/HttpError.nm +20 -0
  27. package/core/System/String.nm +83 -69
  28. package/core/System/StringBuilder.nm +11 -13
  29. package/core/System/Task.nm +46 -12
  30. package/core/System/Test.nm +1 -1
  31. package/core/System/Text/Json.nm +5 -3
  32. package/core/System/Text/JsonTree.nm +23 -14
  33. package/core/System/Text/Regex.nm +124 -64
  34. package/core/System/Tree.nm +1 -1
  35. package/core/System/bool.nm +4 -9
  36. package/core/System/char.nm +3 -9
  37. package/core/System/float.nm +2 -1
  38. package/core/System/float32.nm +44 -0
  39. package/core/System/float64.nm +44 -0
  40. package/core/System/int.nm +3 -9
  41. package/core/System/int16.nm +47 -0
  42. package/core/System/int32.nm +47 -0
  43. package/core/System/int64.nm +3 -9
  44. package/core/System/int8.nm +3 -9
  45. package/core/System/ufloat.nm +44 -0
  46. package/core/System/ufloat32.nm +44 -0
  47. package/core/System/ufloat64.nm +44 -0
  48. package/core/System/uint.nm +3 -9
  49. package/core/System/uint16.nm +47 -0
  50. package/core/System/uint32.nm +47 -0
  51. package/core/System/uint64.nm +3 -9
  52. package/core/System/uint8.nm +3 -9
  53. package/core/docs/System/Channel.md +2 -0
  54. package/core/docs/System/Result.md +5 -1
  55. package/core/docs/System/Task.md +1 -1
  56. package/dist/index.mjs +38794 -21299
  57. package/package.json +38 -34
  58. package/src/args.ts +11 -0
  59. package/src/index.ts +21 -3
  60. package/src/init.ts +1 -1
  61. package/src/test.ts +48 -4
  62. package/src/types/Config.ts +2 -0
package/NOMEN_AGENTS.md CHANGED
@@ -36,22 +36,50 @@ The rules below trip up newcomers and LLMs. Follow them.
36
36
 
37
37
  Nomen infers types from initializers and return values. Don't spell them out
38
38
  unless the inference would be wrong or the annotation materially aids a public
39
- API.
39
+ API. This includes generic instantiations — an initializer like
40
+ `split_lines(text)` already says `List<Line>`.
40
41
 
41
42
  ```nomen
42
- const x = 5 // good — inferred int
43
- const int x = 5 // avoid
44
- const msg = "hi" // good
45
- var items = [1, 2, 3] // good — int[]
43
+ const x = 5 // good — inferred int
44
+ const int x = 5 // avoid
45
+ const lines = split_lines(text) // good — inferred List<Line>
46
+ const List<Line> lines = split_lines(text) // avoid
47
+ const msg = "hi" // good
48
+ var items = [1, 2, 3] // good — int[]
46
49
  ```
47
50
 
48
51
  Annotate when you need a wider type (`int64` rather than `int`), when there is
49
52
  no initializer, or on a `pub` API where the type is the contract.
50
53
 
51
- ### No `else if` use `switch` or separate `if`s
54
+ ### Loop updates go in the header
52
55
 
53
- Nomen forbids `else if` and `else { if { } }` chains. Use a `switch` for
54
- related conditions, or pull each condition into its own top-level `if`.
56
+ `for` and `while` take a post-statement after the condition, C-`for` style.
57
+ Don't bury the update as the last statement of the body.
58
+
59
+ ```nomen
60
+ while i < n; i += 1 { ... } // good — update in the header
61
+ while i < n { ...; i += 1 } // avoid
62
+ ```
63
+
64
+ Only hoist an update that runs on every iteration — if a `continue` (or early
65
+ `return` that isn't meant to advance) must skip it, keep it in the body.
66
+
67
+ ### No `else if` — use `switch`
68
+
69
+ Nomen forbids `else if` and `else { if { … } }` chains. Consecutive sibling
70
+ `if`s on mutually exclusive conditions are the same smell (and a `did_x` flag
71
+ set only to gate the next `if` is a giveaway): fold them into a `switch`,
72
+ which evaluates cases in order and runs the first true one.
73
+
74
+ ```nomen
75
+ switch {
76
+ case moved { ... }
77
+ case is_ins { ... }
78
+ else { ... }
79
+ }
80
+ ```
81
+
82
+ Independent conditions that can all fire stay as separate `if`s.
55
83
 
56
84
  ### Constraints are compile-time assertions
57
85
 
@@ -72,7 +100,7 @@ func safe = (string[] s, int i: i >= 0 && i < s.length, out string) {
72
100
  | -------- | ---------------------------------------------- |
73
101
  | `ref T` | mutable borrow; caller must also write `ref` |
74
102
  | `view T` | read-only borrow |
75
- | `mov T` | transfer ownership; caller writes `mov` |
103
+ | `move T` | transfer ownership; caller writes `move` |
76
104
  | `out T` | output parameter, assigned inside the function |
77
105
 
78
106
  `const` values cannot be passed to `ref` — rebind the caller as `var` first.
@@ -106,15 +134,27 @@ const y = x + 1 // Error: 'x' is null
106
134
 
107
135
  ```nomen
108
136
  import System // standard library
109
- import System/Test // submodule
110
- import System/Collections/List // specific module
137
+ import System::Test // submodule
138
+ import System::Controls::Geometry // a single module file
111
139
  ```
112
140
 
141
+ Import paths must mirror the library's file layout — a typo'd path
142
+ (`import System::Contrls`) is a compile error.
143
+
144
+ Files in the same folder are one module — they already see each other's
145
+ declarations, so never import a sibling. Only cross-namespace references
146
+ (subfolders, libraries) need an import.
147
+
113
148
  ### No exceptions
114
149
 
115
150
  Failures are signalled via return values — nullable results, status structs,
116
151
  or boolean + out-parameter pairs. There is no `throw`/`try`/`catch`.
117
152
 
153
+ `Result<T, E>` is declared `must_use`: a call returning it must bind or match
154
+ the value (`var _ = f.close()` to discard deliberately; a bare `f.close()`
155
+ statement is a compile error). Any enum or bitset can opt into this with the
156
+ `must_use` modifier.
157
+
118
158
  ## Tests
119
159
 
120
160
  A test is any `pub func <name> = (ref Tester t)` in a `*.test.nm` file.
@@ -123,7 +163,7 @@ benchmarks.
123
163
 
124
164
  ```nomen
125
165
  import System
126
- import System/Test
166
+ import System::Test
127
167
 
128
168
  func add = (int a, int b, out int) => a + b
129
169
 
@@ -153,6 +193,10 @@ var int? maybe = null // nullable
153
193
  ref int ptr // reference to an int
154
194
  ```
155
195
 
196
+ Keywords (`out`, `in`, `match`, `if`, `of`, `as`, `swap`, …) are reserved and
197
+ cannot be used as variable, parameter, field, type, or case names. Function
198
+ names are exempt since calls go through `.name(...)` (e.g. `Regex.match`).
199
+
156
200
  ### Functions
157
201
 
158
202
  ```nomen
@@ -177,6 +221,7 @@ switch x {
177
221
  for i of 0 .. 10 { ... } // exclusive range
178
222
  for item of array { ... }
179
223
  while cond { ... }
224
+ while cond; update { ... } // post-statement (e.g. while i < n; i += 1)
180
225
  ```
181
226
 
182
227
  ### Structs and classes
@@ -114,13 +114,13 @@ pub struct Array<T>: Viewable {
114
114
  ```
115
115
  #arch: c
116
116
  T* _data = (T*)((char*)self + sizeof(*self));
117
- // T_NEEDS_STRDUP is 1 when T is string: the slot must own a heap copy
118
- // (free the outgoing string, strdup the incoming one) so auto_free can
119
- // soundly free every slot at scope exit and reassigning a slot doesn't
120
- // leak the previous value. 0 for all other T (plain assignment).
117
+ // T_NEEDS_STRDUP is 1 when T is string (a fat value whose backing
118
+ // bytes must be heap-copied per slot otherwise the slot would share
119
+ // the incoming pointer and auto_free would double-free it). 0 for all
120
+ // other T (plain assignment).
121
121
  #if T_NEEDS_STRDUP
122
- free(_data[index]);
123
- _data[index] = (T)strdup(value);
122
+ free(_data[index].ptr);
123
+ _data[index] = nomen_str_dup(value);
124
124
  #else
125
125
  _data[index] = value;
126
126
  #endif
@@ -258,12 +258,12 @@ pub struct Array<T>: Viewable {
258
258
  ((long*)_result)[1] = count;
259
259
  T* _data = (T*)((char*)_result + _header);
260
260
  for (long i = 0; i < count; i++) {
261
- // T_NEEDS_STRDUP is 1 when T is string (a `char*` pointer whose
262
- // backing bytes must be heap-copied per slot — otherwise all n
263
- // slots would share one pointer and auto_free would n-free it).
264
- // For all other T it is 0 and the plain assignment runs.
261
+ // T_NEEDS_STRDUP is 1 when T is string (a fat value whose backing
262
+ // bytes must be heap-copied per slot — otherwise all n slots would
263
+ // share one pointer and auto_free would n-free it). For all other
264
+ // T it is 0 and the plain assignment runs.
265
265
  #if T_NEEDS_STRDUP
266
- _data[i] = (T)strdup(value);
266
+ _data[i] = nomen_str_dup(value);
267
267
  #else
268
268
  _data[i] = value;
269
269
  #endif
@@ -445,11 +445,11 @@ pub struct Buffer<T> {
445
445
  }
446
446
 
447
447
  // Move a value out of a T_SIZE-sized slot: return it and zero the slot so the
448
- // buffer no longer holds a (possibly owning) copy. Declared `mov out T` so
448
+ // buffer no longer holds a (possibly owning) copy. Declared `move out T` so
449
449
  // the result is treated as OWNED by the borrow checker — the slot is
450
450
  // relinquished, so the caller is the sole remaining owner. The value
451
451
  // counterpart of ClassBuffer.move_int — used by List.pop for any value T.
452
- inline func move_T = (ref self, int i: i >= 0 && i < self.cap, mov out T) {
452
+ inline func move_T = (ref self, int i: i >= 0 && i < self.cap, move out T) {
453
453
  ```
454
454
  #arch: c
455
455
  T* _slots = (T*)(unsigned long long)self->data;
@@ -1,9 +1,18 @@
1
1
  // Channel — thread-safe single-direction queue for passing values between
2
2
  // tasks. Unbounded, FIFO, blocking receive.
3
3
  //
4
- // Stores uint64 values (Nomen values are pointer-sized on every target we
5
- // care about). A typed Channel<T> wrapper can come later — the storage story
6
- // is identical, only the call-site types change.
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.
9
+ //
10
+ // String payloads are copied in and moved out: `send_string` duplicates the
11
+ // value into the node (the sender keeps its own string), and
12
+ // `receive_string` moves that copy out to the receiver (zeroing the node's
13
+ // payload). A message that is never received is freed by #destroy's drain.
14
+ // This keeps literals (rodata), heap strings freed at the sender's scope
15
+ // exit, and messages crossing task boundaries all sound.
7
16
  //
8
17
  // Default stance in Nomen is "no shared mutable state" — channels are the
9
18
  // preferred way for tasks to communicate. See ASYNC.md.
@@ -24,6 +33,7 @@ pub class Channel: Sendable {
24
33
  #include <pthread.h>
25
34
  struct nomen_channel_node {
26
35
  unsigned long long value;
36
+ unsigned long long len;
27
37
  struct nomen_channel_node *next;
28
38
  };
29
39
  ```
@@ -58,6 +68,7 @@ pub class Channel: Sendable {
58
68
  #arch: c
59
69
  struct nomen_channel_node *n = (struct nomen_channel_node *)malloc(sizeof(struct nomen_channel_node));
60
70
  n->value = value;
71
+ n->len = 0;
61
72
  n->next = NULL;
62
73
  pthread_mutex_lock((pthread_mutex_t *)self->mu);
63
74
  if (self->tail) {
@@ -73,18 +84,19 @@ pub class Channel: Sendable {
73
84
  #arch: aarch64
74
85
  // x19 = self (set by prologue), x1 = value
75
86
  str x1, [sp, #-16]! // save value on stack
76
- mov x0, #16
87
+ mov x0, #24 // node = { value, len, next }
77
88
  bl _malloc // x0 = node
78
89
  ldr x1, [sp], #16 // restore value
79
90
  str x1, [x0, #0] // node->value = value
80
- str xzr, [x0, #8] // node->next = NULL
91
+ str xzr, [x0, #8] // node->len = 0
92
+ str xzr, [x0, #16] // node->next = NULL
81
93
  str x0, [sp, #-16]! // save node on stack
82
94
  ldr x0, [x19, #8] // self->mu
83
95
  bl _pthread_mutex_lock
84
96
  ldr x2, [sp], #16 // restore node
85
97
  ldr x1, [x19, #32] // tail
86
98
  cbz x1, .Lchannel_send_empty
87
- str x2, [x1, #8] // tail->next = node
99
+ str x2, [x1, #16] // tail->next = node
88
100
  b .Lchannel_send_enqueued
89
101
  .Lchannel_send_empty:
90
102
  str x2, [x19, #24] // head = node
@@ -97,6 +109,67 @@ pub class Channel: Sendable {
97
109
  ```
98
110
  }
99
111
 
112
+ // Pass a fat string through the channel. The value is DUPLICATED into the
113
+ // queue node — the sender keeps ownership of what it passed — so the
114
+ // message survives the sender's scope exit intact (true len, embedded
115
+ // NULs and all). Receive it with receive_string.
116
+ pub func send_string = (self, string value) {
117
+ ```
118
+ #arch: c
119
+ struct nomen_channel_node *n = (struct nomen_channel_node *)malloc(sizeof(struct nomen_channel_node));
120
+ nomen_string _v = value.ptr ? nomen_str_dup(value) : (nomen_string){0, 0};
121
+ n->value = (unsigned long long)_v.ptr;
122
+ n->len = (unsigned long long)_v.len;
123
+ n->next = NULL;
124
+ pthread_mutex_lock((pthread_mutex_t *)self->mu);
125
+ if (self->tail) {
126
+ ((struct nomen_channel_node *)self->tail)->next = n;
127
+ } else {
128
+ self->head = (unsigned long long)n;
129
+ }
130
+ self->tail = (unsigned long long)n;
131
+ pthread_cond_signal((pthread_cond_t *)self->not_empty_cv);
132
+ pthread_mutex_unlock((pthread_mutex_t *)self->mu);
133
+ ```
134
+ ```
135
+ #arch: aarch64
136
+ // x19 = self (set by prologue), x1 = value.ptr, x2 = value.len
137
+ stp x1, x2, [sp, #-32]! // [sp]=ptr [sp+8]=len [sp+16]=node scratch
138
+ mov x0, #24 // node = { value, len, next }
139
+ bl _malloc // x0 = node
140
+ str x0, [sp, #16] // save node across the strdup call
141
+ ldr x1, [sp, #0] // value.ptr
142
+ cbz x1, .Lchannel_send_str_zero
143
+ mov x0, x1
144
+ bl _strdup // x0 = owned copy (node owns its buffer)
145
+ b .Lchannel_send_str_have
146
+ .Lchannel_send_str_zero:
147
+ mov x0, #0
148
+ .Lchannel_send_str_have:
149
+ ldr x2, [sp, #16] // node
150
+ str x0, [x2, #0] // node->value = copied ptr
151
+ ldr x1, [sp, #8] // value.len
152
+ str x1, [x2, #8] // node->len = len
153
+ str xzr, [x2, #16] // node->next = NULL
154
+ ldr x0, [x19, #8] // self->mu
155
+ bl _pthread_mutex_lock
156
+ ldr x2, [sp, #16] // restore node
157
+ ldr x1, [x19, #32] // tail
158
+ cbz x1, .Lchannel_send_str_empty
159
+ str x2, [x1, #16] // tail->next = node
160
+ b .Lchannel_send_str_enqueued
161
+ .Lchannel_send_str_empty:
162
+ str x2, [x19, #24] // head = node
163
+ .Lchannel_send_str_enqueued:
164
+ str x2, [x19, #32] // tail = node
165
+ ldr x0, [x19, #16] // not_empty_cv
166
+ bl _pthread_cond_signal
167
+ ldr x0, [x19, #8] // self->mu
168
+ bl _pthread_mutex_unlock
169
+ add sp, sp, #32
170
+ ```
171
+ }
172
+
100
173
  pub func receive = (self, out uint64) {
101
174
  ```
102
175
  #arch: c
@@ -126,7 +199,7 @@ pub class Channel: Sendable {
126
199
  b .Lchannel_receive_wait
127
200
  .Lchannel_receive_got:
128
201
  // x1 = node
129
- ldr x2, [x1, #8] // node->next
202
+ ldr x2, [x1, #16] // node->next
130
203
  str x2, [x19, #24] // head = node->next
131
204
  cbnz x2, .Lchannel_receive_not_empty
132
205
  str xzr, [x19, #32] // tail = 0
@@ -141,14 +214,69 @@ pub class Channel: Sendable {
141
214
  ```
142
215
  }
143
216
 
217
+ // Block until a message is available, then return it as an OWNED string
218
+ // (the node's copy is moved out — the payload leaves the channel, and a
219
+ // second receive of the same message is not possible). The fat (ptr, len)
220
+ // pair survives intact. Pair with send_string.
221
+ pub func receive_string = (self, move out string) {
222
+ ```
223
+ #arch: c
224
+ pthread_mutex_lock((pthread_mutex_t *)self->mu);
225
+ while (!self->head) {
226
+ pthread_cond_wait((pthread_cond_t *)self->not_empty_cv, (pthread_mutex_t *)self->mu);
227
+ }
228
+ struct nomen_channel_node *n = (struct nomen_channel_node *)self->head;
229
+ self->head = (unsigned long long)n->next;
230
+ if (!self->head) self->tail = 0;
231
+ nomen_string v = { (char *)n->value, (long)n->len };
232
+ n->value = 0;
233
+ n->len = 0;
234
+ free(n);
235
+ pthread_mutex_unlock((pthread_mutex_t *)self->mu);
236
+ return v;
237
+ ```
238
+ ```
239
+ #arch: aarch64
240
+ // x19 = self (set by prologue); returns the fat pair in x0/x1
241
+ ldr x0, [x19, #8] // self->mu
242
+ bl _pthread_mutex_lock
243
+ .Lchannel_receive_str_wait:
244
+ ldr x1, [x19, #24] // head
245
+ cbnz x1, .Lchannel_receive_str_got
246
+ ldr x0, [x19, #16] // not_empty_cv
247
+ ldr x1, [x19, #8] // mu
248
+ bl _pthread_cond_wait
249
+ b .Lchannel_receive_str_wait
250
+ .Lchannel_receive_str_got:
251
+ // x1 = node
252
+ ldr x2, [x1, #16] // node->next
253
+ str x2, [x19, #24] // head = node->next
254
+ cbnz x2, .Lchannel_receive_str_not_empty
255
+ str xzr, [x19, #32] // tail = 0
256
+ .Lchannel_receive_str_not_empty:
257
+ ldr x0, [x1, #0] // value.ptr
258
+ ldr x2, [x1, #8] // value.len
259
+ stp x0, x2, [sp, #-16]! // push pair
260
+ str xzr, [x1, #0] // payload moves out of the node
261
+ str xzr, [x1, #8]
262
+ mov x0, x1 // x0 = node (for free)
263
+ bl _free
264
+ ldr x0, [x19, #8] // self->mu
265
+ bl _pthread_mutex_unlock
266
+ ldp x0, x1, [sp], #16 // pop pair → x0/x1 (return)
267
+ ```
268
+ }
269
+
144
270
  pub func #destroy = () {
145
271
  ```
146
272
  #arch: c
147
273
  if (self->mu) {
148
- // Drain any pending nodes.
274
+ // Drain any pending nodes. A string payload that was never
275
+ // received still owns its buffer — free it with the node.
149
276
  struct nomen_channel_node *n = (struct nomen_channel_node *)self->head;
150
277
  while (n) {
151
278
  struct nomen_channel_node *next = n->next;
279
+ if (n->value) free((void *)n->value);
152
280
  free(n);
153
281
  n = next;
154
282
  }
@@ -167,14 +295,20 @@ pub class Channel: Sendable {
167
295
  // x19 = self (set by prologue)
168
296
  ldr x0, [x19, #8] // self->mu
169
297
  cbz x0, .Lchannel_destroy_done
170
- // Drain nodes
298
+ // Drain nodes (freeing any unconsumed string payload)
171
299
  ldr x1, [x19, #24] // head
172
300
  .Lchannel_destroy_drain:
173
301
  cbz x1, .Lchannel_destroy_drained
174
- ldr x2, [x1, #8] // next (before free clobbers it)
175
- str x2, [sp, #-16]! // save next on stack
302
+ ldr x2, [x1, #16] // next (before calls clobber x1)
303
+ stp x1, x2, [sp, #-16]! // save node + next
304
+ ldr x0, [x1, #0] // payload ptr
305
+ cbz x0, .Lchannel_destroy_no_payload
306
+ bl _free
307
+ .Lchannel_destroy_no_payload:
308
+ ldr x0, [sp, #0] // node
176
309
  bl _free
177
- ldr x1, [sp], #16 // restore next
310
+ ldr x1, [sp, #8] // next
311
+ add sp, sp, #16
178
312
  b .Lchannel_destroy_drain
179
313
  .Lchannel_destroy_drained:
180
314
  // Destroy mutex and condvar
@@ -175,14 +175,14 @@ pub struct ClassBuffer<T> {
175
175
  }
176
176
 
177
177
  // Move a class pointer out of a slot: return it and zero the slot so the
178
- // buffer no longer references it. Declared `mov out T` so the borrow
178
+ // buffer no longer references it. Declared `move out T` so the borrow
179
179
  // checker treats the result as OWNED — the slot is relinquished, so the
180
180
  // caller is the sole remaining owner. This is the owning-extract primitive
181
181
  // (parallel to List.pop); use it (not load_T) when transferring ownership
182
182
  // out of a ClassBuffer slot, or moving a value into a fresh owning
183
183
  // container — load_T returns a BORROW (the slot is unchanged) and storing
184
184
  // it elsewhere would create shared ownership (double-free at destroy).
185
- inline func move_T = (ref self, int i: i >= 0 && i < self.cap, mov out T) {
185
+ inline func move_T = (ref self, int i: i >= 0 && i < self.cap, move out T) {
186
186
  ```
187
187
  #arch: c
188
188
  long *slots = (long*)(unsigned long long)self->data;
@@ -9,12 +9,12 @@ pub struct Console {
9
9
  ```
10
10
  ```
11
11
  #arch: aarch64
12
- sub sp, sp, #16
13
- mov x1, x0
14
- str x1, [sp]
12
+ // Fat-string ABI: line = (x0 ptr, x1 len); printf needs the fmt in
13
+ // x0 and the NUL-terminated ptr half in x1.
14
+ mov x2, x0
15
15
  adr x0, .Lfmt_console_s
16
+ mov x1, x2
16
17
  bl _printf
17
- add sp, sp, #16
18
18
  b .Lend_Console_write
19
19
  .Lfmt_console_s: .asciz "%s"
20
20
  .p2align 2
@@ -29,12 +29,11 @@ pub struct Console {
29
29
  ```
30
30
  ```
31
31
  #arch: aarch64
32
- sub sp, sp, #16
33
- mov x1, x0
34
- str x1, [sp]
32
+ // Fat-string ABI: line = (x0 ptr, x1 len).
33
+ mov x2, x0
35
34
  adr x0, .Lfmt_console_snl
35
+ mov x1, x2
36
36
  bl _printf
37
- add sp, sp, #16
38
37
  b .Lend_Console_write_line
39
38
  .Lfmt_console_snl: .asciz "%s\n"
40
39
  .p2align 2
@@ -42,7 +41,9 @@ pub struct Console {
42
41
  ```
43
42
  }
44
43
 
45
- pub func read_line = (out string) {
44
+ // Read one line from stdin (newline stripped). The raw body mallocs the
45
+ // buffer — `move out string` transfers ownership to the caller.
46
+ pub func read_line = (move out string) {
46
47
  ```
47
48
  #arch: c
48
49
  int cap = 16;
@@ -58,7 +59,9 @@ pub struct Console {
58
59
  c = getchar();
59
60
  }
60
61
  buf[len] = 0;
61
- return buf;
62
+ // Fat-string ABI: return the (ptr, len) pair — len is already
63
+ // counted, no strlen.
64
+ return (nomen_string){ buf, len };
62
65
  ```
63
66
  ```
64
67
  #arch: aarch64
@@ -121,42 +124,45 @@ pub struct Console {
121
124
  ```
122
125
  }
123
126
 
124
- pub func platform = (out string) {
127
+ // The platform name ("macos" / "ios" / …). The raw body strdups a static
128
+ // string — `move out string` transfers the owned copy to the caller.
129
+ pub func platform = (move out string) {
125
130
  ```
126
131
  #arch: c
127
132
  #platform: macos
128
- return strdup("macos");
133
+ return (nomen_string){ strdup("macos"), 5 };
129
134
  ```
130
135
  ```
131
136
  #arch: c
132
137
  #platform: ios
133
- return strdup("ios");
138
+ return (nomen_string){ strdup("ios"), 3 };
134
139
  ```
135
140
  ```
136
141
  #arch: c
137
142
  #platform: linux
138
- return strdup("linux");
143
+ return (nomen_string){ strdup("linux"), 5 };
139
144
  ```
140
145
  ```
141
146
  #arch: c
142
147
  #platform: android
143
- return strdup("android");
148
+ return (nomen_string){ strdup("android"), 7 };
144
149
  ```
145
150
  ```
146
151
  #arch: c
147
152
  #platform: windows
148
- return strdup("windows");
153
+ return (nomen_string){ strdup("windows"), 7 };
149
154
  ```
150
155
  ```
151
156
  #arch: c
152
157
  #platform: web
153
- return strdup("web");
158
+ return (nomen_string){ strdup("web"), 3 };
154
159
  ```
155
160
  ```
156
161
  #arch: aarch64
157
162
  #platform: macos
158
163
  adr x0, .Lconsole_plat_macos
159
164
  bl _strdup
165
+ mov x1, #5
160
166
  b .return_Console_platform
161
167
  .Lconsole_plat_macos: .asciz "macos"
162
168
  .p2align 2
@@ -166,6 +172,7 @@ pub struct Console {
166
172
  #platform: ios
167
173
  adr x0, .Lconsole_plat_ios
168
174
  bl _strdup
175
+ mov x1, #3
169
176
  b .return_Console_platform
170
177
  .Lconsole_plat_ios: .asciz "ios"
171
178
  .p2align 2
@@ -175,6 +182,7 @@ pub struct Console {
175
182
  #platform: linux
176
183
  adr x0, .Lconsole_plat_linux
177
184
  bl _strdup
185
+ mov x1, #5
178
186
  b .return_Console_platform
179
187
  .Lconsole_plat_linux: .asciz "linux"
180
188
  .p2align 2
@@ -184,6 +192,7 @@ pub struct Console {
184
192
  #platform: android
185
193
  adr x0, .Lconsole_plat_android
186
194
  bl _strdup
195
+ mov x1, #7
187
196
  b .return_Console_platform
188
197
  .Lconsole_plat_android: .asciz "android"
189
198
  .p2align 2
@@ -193,6 +202,7 @@ pub struct Console {
193
202
  #platform: windows
194
203
  adr x0, .Lconsole_plat_windows
195
204
  bl _strdup
205
+ mov x1, #7
196
206
  b .return_Console_platform
197
207
  .Lconsole_plat_windows: .asciz "windows"
198
208
  .p2align 2
@@ -202,6 +212,7 @@ pub struct Console {
202
212
  #platform: web
203
213
  adr x0, .Lconsole_plat_web
204
214
  bl _strdup
215
+ mov x1, #3
205
216
  b .return_Console_platform
206
217
  .Lconsole_plat_web: .asciz "web"
207
218
  .p2align 2