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
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The failure mode of a file operation.
3
+ *
4
+ * File.write_all("out.txt", "payload")
5
+ * var Result<string, FileError> body = File.read_all("out.txt")
6
+ * match body {
7
+ * case .ok(text) -> Console.write(text)
8
+ * case .error(e) -> Console.write("failed")
9
+ * }
10
+ *
11
+ * Cases map from the underlying errno: `not_found` (ENOENT), `access_denied`
12
+ * (EACCES), `already_exists` (EEXIST), `not_open` (operating on a `File`
13
+ * whose open failed or that was already closed), `other` (anything else).
14
+ **/
15
+ pub enum FileError {
16
+ case not_found
17
+ case access_denied
18
+ case already_exists
19
+ case not_open
20
+ case other
21
+ }
@@ -4,7 +4,8 @@
4
4
  * Plain `http://` only — no TLS (an `https://` URL cannot be served; use a
5
5
  * TLS proxy or wait for a TLS-capable transport). Construct an instance,
6
6
  * call `get`/`post` (which block until the full response is received), then
7
- * read the `status` field for the response's status code. `status` is 0 when
7
+ * read the `status` field for the response's status code. Fallible calls
8
+ * return a `Result` whose error side is an `HttpError`; `status` is 0 when
8
9
  * DNS resolution or the connection fails.
9
10
  *
10
11
  * URLs may carry an explicit port (`http://host:8080/path`); the default is
@@ -14,20 +15,47 @@
14
15
  * separator line) is returned.
15
16
  *
16
17
  * var Http h = Http()
17
- * var string body = h.get("http://example.com/")
18
- * var int code = h.status
19
- * var string r = h.post("http://example.com/submit", "hello=world")
18
+ * match h.get("http://example.com/") {
19
+ * case .ok(body) -> Console.write(body)
20
+ * case .error(e) -> Console.write("request failed")
21
+ * }
22
+ * var int code = h.status
20
23
  **/
21
24
  pub struct Http {
22
- // HTTP status code from the most recent request (e.g. 200). 0 if DNS or
23
- // the connection failed.
25
+ // HTTP status code from the most recent request (e.g. 200). 0 when the
26
+ // request failed (the `error` side of the get/post Result says why).
24
27
  pub var int status = 0
25
28
 
29
+ // Internal failure code from the most recent request: 0 = success,
30
+ // 1 = DNS resolution, 2 = socket creation, 3 = connect. Raw #arch bodies
31
+ // set it, the Result-returning wrappers map it to HttpError.
32
+ var int error = 0
33
+
34
+ // Maps the raw failure code to its HttpError case.
35
+ func error_of = (int err, out HttpError) {
36
+ switch {
37
+ case err == 1 {
38
+ return HttpError.dns
39
+ }
40
+ case err == 2 {
41
+ return HttpError.socket
42
+ }
43
+ case err == 3 {
44
+ return HttpError.connect
45
+ }
46
+ else {
47
+ return HttpError.other
48
+ }
49
+ }
50
+ }
51
+
26
52
  // Shared socket round-trip used by get/post. `method` is the request
27
53
  // method string ("GET" / "POST"); a leading 'P' selects POST semantics
28
- // (sends `body` and the Content-* headers). Sets self->status and returns
29
- // the response body (empty string on failure).
30
- func exchange = (ref self, string method, string url, string body, out string) {
54
+ // (sends `body` and the Content-* headers). Sets self->status (0 on
55
+ // failure) and self->error, and returns the response body (empty string
56
+ // on failure). The raw body mallocs the response — `move out string`
57
+ // transfers ownership to the caller.
58
+ func raw_exchange = (ref self, string method, string url, string body, move out string) {
31
59
  ```
32
60
  #arch: c
33
61
  #scope: file
@@ -39,8 +67,10 @@ pub struct Http {
39
67
  ```
40
68
  ```
41
69
  #arch: c
42
- const char *scheme = strstr(url, "://");
43
- const char *hp = scheme ? scheme + 3 : url;
70
+ // Fat-string ABI: params are (ptr, len) pairs; consume the .ptr
71
+ // halves at each FFI boundary.
72
+ const char *scheme = strstr(url.ptr, "://");
73
+ const char *hp = scheme ? scheme + 3 : url.ptr;
44
74
  const char *colon = strchr(hp, ':');
45
75
  const char *slash = strchr(hp, '/');
46
76
  char host[256];
@@ -70,33 +100,36 @@ pub struct Http {
70
100
  snprintf(portstr, sizeof(portstr), "%d", port);
71
101
  if (getaddrinfo(host, portstr, &hints, &res) != 0) {
72
102
  self->status = 0;
73
- return strdup("");
103
+ self->error = 1;
104
+ return (nomen_string){ strdup(""), 0 };
74
105
  }
75
106
  int sock = socket(AF_INET, SOCK_STREAM, 0);
76
107
  if (sock < 0) {
77
108
  freeaddrinfo(res);
78
109
  self->status = 0;
79
- return strdup("");
110
+ self->error = 2;
111
+ return (nomen_string){ strdup(""), 0 };
80
112
  }
81
113
  if (connect(sock, res->ai_addr, res->ai_addrlen) < 0) {
82
114
  freeaddrinfo(res);
83
115
  close(sock);
84
116
  self->status = 0;
85
- return strdup("");
117
+ self->error = 3;
118
+ return (nomen_string){ strdup(""), 0 };
86
119
  }
87
120
  freeaddrinfo(res);
88
121
 
89
122
  char req[1600];
90
123
  int off = snprintf(req, sizeof(req),
91
124
  "%s %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n",
92
- method, path, host);
93
- if (method[0] == 'P') {
94
- int blen = (int)strlen(body);
125
+ method.ptr, path, host);
126
+ if (method.ptr[0] == 'P') {
127
+ int blen = (int)body.len;
95
128
  off += snprintf(req + off, sizeof(req) - off,
96
129
  "Content-Type: application/x-www-form-urlencoded\r\nContent-Length: %d\r\n\r\n",
97
130
  blen);
98
131
  send(sock, req, off, 0);
99
- send(sock, body, blen, 0);
132
+ send(sock, body.ptr, blen, 0);
100
133
  } else {
101
134
  off += snprintf(req + off, sizeof(req) - off, "\r\n");
102
135
  send(sock, req, off, 0);
@@ -120,18 +153,24 @@ pub struct Http {
120
153
  code = atoi(buf + 9);
121
154
  }
122
155
  self->status = code;
156
+ self->error = 0;
123
157
 
124
158
  char *body_start = strstr(buf, "\r\n\r\n");
125
159
  if (body_start) {
126
160
  char *result = strdup(body_start + 4);
161
+ long rlen = (long)len - (long)(body_start + 4 - buf);
127
162
  free(buf);
128
- return result;
163
+ return (nomen_string){ result, rlen };
129
164
  }
130
- return buf;
165
+ return (nomen_string){ buf, len };
131
166
  ```
132
167
  ```
133
168
  #arch: aarch64
134
- // x0=self(->x19), x1=method, x2=url, x3=body
169
+ // Fat-string ABI: x0=self(->x19); method=(x1,x2), url=(x3,x4),
170
+ // body=(x5,x6) — only the ptr halves are consumed below, plus
171
+ // body.len (stashed at #200) for Content-Length/send.
172
+ // Struct layout: self->status at [x19, #8], self->error at
173
+ // [x19, #16] (1 = dns, 2 = socket, 3 = connect; cleared on success).
135
174
  // macOS arm64 passes variadic args on the caller stack at [sp+0..], so
136
175
  // [sp+0..47] is reserved scratch for snprintf variadic args and the
137
176
  // addrinfo hints live above it. Stack frame (2048 bytes):
@@ -146,8 +185,9 @@ pub struct Http {
146
185
  // #208 host buffer (256) #464 request buffer (1584)
147
186
  sub sp, sp, #2048
148
187
  str x1, [sp, #128]
149
- str x2, [sp, #136]
150
- str x3, [sp, #144]
188
+ str x3, [sp, #136]
189
+ str x5, [sp, #144]
190
+ str x6, [sp, #200] // body len
151
191
  mov x0, #80
152
192
  str x0, [sp, #112]
153
193
  adr x0, .Lhttp_slash
@@ -245,7 +285,7 @@ pub struct Http {
245
285
  add x2, sp, #48
246
286
  add x3, sp, #96
247
287
  bl _getaddrinfo
248
- cbnz w0, .Lhttp_fail
288
+ cbnz w0, .Lhttp_dnsfail
249
289
 
250
290
  // sock = socket(AF_INET, SOCK_STREAM, 0)
251
291
  mov x0, #2
@@ -256,6 +296,8 @@ pub struct Http {
256
296
  b.ge .Lhttp_sockok
257
297
  ldr x0, [sp, #96]
258
298
  bl _freeaddrinfo
299
+ mov x9, #2
300
+ str x9, [x19, #16]
259
301
  b .Lhttp_fail
260
302
  .Lhttp_sockok:
261
303
  str w0, [sp, #104]
@@ -271,6 +313,8 @@ pub struct Http {
271
313
  bl _freeaddrinfo
272
314
  ldr w0, [sp, #104]
273
315
  bl _close
316
+ mov x9, #3
317
+ str x9, [x19, #16]
274
318
  b .Lhttp_fail
275
319
  .Lhttp_connected:
276
320
  ldr x0, [sp, #96]
@@ -297,10 +341,9 @@ pub struct Http {
297
341
  cmp w0, #0x50
298
342
  b.ne .Lhttp_getsend
299
343
 
300
- // POST: blen = strlen(body); append Content-Length; send headers; send body
301
- ldr x0, [sp, #144]
302
- bl _strlen
303
- str x0, [sp, #160]
344
+ // POST: blen = body.len (fat half); append Content-Length; send both
345
+ ldr x9, [sp, #200]
346
+ str x9, [sp, #160]
304
347
  // snprintf(req+off, 1584-off, reqpost, blen) — blen on stack at [sp+0]
305
348
  ldr x9, [sp, #160]
306
349
  str x9, [sp, #0]
@@ -321,11 +364,9 @@ pub struct Http {
321
364
  ldr x2, [sp, #176]
322
365
  mov x3, xzr
323
366
  bl _send
324
- ldr x0, [sp, #144]
325
- bl _strlen
326
- mov x2, x0
327
367
  ldr w0, [sp, #104]
328
368
  ldr x1, [sp, #144]
369
+ ldr x2, [sp, #200]
329
370
  mov x3, xzr
330
371
  bl _send
331
372
  b .Lhttp_recv
@@ -394,36 +435,51 @@ pub struct Http {
394
435
  strb wzr, [x0, x1]
395
436
  ldr w0, [sp, #104]
396
437
  bl _close
438
+ // success: clear error before storing the status code
439
+ str xzr, [x19, #16]
397
440
  // self->status = atoi(buf + 9)
398
441
  ldr x0, [sp, #160]
399
442
  add x0, x0, #9
400
443
  bl _atoi
401
444
  sxtw x0, w0
402
445
  str x0, [x19, #8]
403
- // body_start = strstr(buf, "\r\n\r\n")
446
+ // body_start = strstr(buf, "\r\n\r\n"); fat returns carry lens.
404
447
  ldr x0, [sp, #160]
405
448
  adr x1, .Lhttp_hdrsep
406
449
  bl _strstr
407
450
  cbz x0, .Lhttp_retbuf
408
- // result = strdup(body_start + 4); free(buf); return result
451
+ // result = strdup(body_start + 4); rlen = len - (body_start+4-buf)
452
+ add x9, x0, #4
453
+ ldr x10, [sp, #160]
454
+ sub x9, x9, x10
455
+ ldr x10, [sp, #96]
456
+ sub x9, x10, x9 // rlen
457
+ str x9, [sp, #168]
409
458
  add x0, x0, #4
410
459
  bl _strdup
460
+ ldr x1, [sp, #168]
411
461
  str x0, [sp, #112]
412
462
  ldr x0, [sp, #160]
413
463
  bl _free
414
464
  ldr x0, [sp, #112]
415
465
  add sp, sp, #2048
416
- b .return_Http_exchange
466
+ b .return_Http_raw_exchange
417
467
  .Lhttp_retbuf:
418
468
  ldr x0, [sp, #160]
469
+ ldr x1, [sp, #96]
419
470
  add sp, sp, #2048
420
- b .return_Http_exchange
471
+ b .return_Http_raw_exchange
472
+ .Lhttp_dnsfail:
473
+ mov x9, #1
474
+ str x9, [x19, #16]
475
+ b .Lhttp_fail
421
476
  .Lhttp_fail:
422
477
  str xzr, [x19, #8]
423
478
  adr x0, .Lhttp_empty
424
479
  bl _strdup
480
+ mov x1, #0
425
481
  add sp, sp, #2048
426
- b .return_Http_exchange
482
+ b .return_Http_raw_exchange
427
483
 
428
484
  .Lhttp_scheme: .asciz "://"
429
485
  .Lhttp_slash: .asciz "/"
@@ -438,16 +494,25 @@ pub struct Http {
438
494
  return ""
439
495
  }
440
496
 
441
- // Issue a blocking GET request to `url`. Returns the response body and
442
- // sets `self.status` to the response's status code (0 on failure).
443
- pub func get = (ref self, string url, out string) {
444
- return self.exchange("GET", url, "")
497
+ // Issue a blocking GET request to `url`. `.ok(body)` with the response
498
+ // body and `self.status` set to the response's status code, or
499
+ // `.error(HttpError)` (`status` is 0).
500
+ pub func get = (ref self, string url, out Result<string, HttpError>) {
501
+ const string response = self.raw_exchange("GET", url, "")
502
+ if self.error == 0 {
503
+ return Result.ok(response)
504
+ }
505
+ return Result.error(Http.error_of(self.error))
445
506
  }
446
507
 
447
508
  // Issue a blocking POST request to `url` with `body` as the payload.
448
- // Returns the response body and sets `self.status` to the response's
449
- // status code (0 on failure).
450
- pub func post = (ref self, string url, string body, out string) {
451
- return self.exchange("POST", url, body)
509
+ // `.ok(body)` with the response body and `self.status` set to the
510
+ // response's status code, or `.error(HttpError)` (`status` is 0).
511
+ pub func post = (ref self, string url, string body, out Result<string, HttpError>) {
512
+ const string response = self.raw_exchange("POST", url, body)
513
+ if self.error == 0 {
514
+ return Result.ok(response)
515
+ }
516
+ return Result.error(Http.error_of(self.error))
452
517
  }
453
518
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The failure mode of an HTTP request.
3
+ *
4
+ * var Http h = Http()
5
+ * var Result<string, HttpError> body = h.get("http://example.com/")
6
+ * match body {
7
+ * case .ok(text) -> Console.write(text)
8
+ * case .error(e) -> Console.write("failed")
9
+ * }
10
+ *
11
+ * Cases map from the request lifecycle: `dns` (hostname resolution failed),
12
+ * `socket` (the socket could not be created), `connect` (the connection was
13
+ * refused or timed out), `other` (anything else).
14
+ **/
15
+ pub enum HttpError {
16
+ case dns
17
+ case socket
18
+ case connect
19
+ case other
20
+ }
@@ -1,42 +1,31 @@
1
+ // FFI: the C library's heap-copy entry point (string.h). The adapter
2
+ // marshals the fat string to the thin char* the C ABI expects; the string
3
+ // result is re-wrapped with its length.
4
+ extern func strdup = (string s, move out string)
5
+
1
6
  /**
2
7
  * A UTF-8 text string — a heap-owned, length-tracked sequence of bytes
3
8
  **/
4
9
  pub struct string: Stringable, Hashable, Equatable {
5
10
  pub func hash = (self, out uint) {
6
- ```
7
- #arch: c
8
- unsigned long h = 5381;
9
- for (const char* p = self; *p; p++) {
10
- h = h * 33 + (unsigned long)(unsigned char)*p;
11
+ // djb2 over exactly self.length bytes — matches the byte count of
12
+ // owned strings and literals (views hash the viewed bytes only).
13
+ var uint h = 5381
14
+ var i = 0
15
+ while i < self.length; i += 1 {
16
+ h = h * 33 + (self.at(i) as uint)
11
17
  }
12
- return h;
13
- ```
14
- ```
15
- #arch: aarch64
16
- // x19 = self (char*), result in x0 (djb2 hash)
17
- mov x0, #5381
18
- mov x3, x19
19
- .Lstring_hash_loop:
20
- ldrb w1, [x3]
21
- cbz w1, .Lstring_hash_done
22
- mov x2, #33
23
- mul x0, x0, x2
24
- add x0, x0, x1
25
- add x3, x3, #1
26
- b .Lstring_hash_loop
27
- .Lstring_hash_done:
28
- ```
18
+ return h
29
19
  }
30
20
 
31
- pub func to_string = (self, out string) {
32
- ```
33
- #arch: c
34
- return strdup(self);
35
- ```
36
- ```
37
- #arch: aarch64
38
- mov x0, x19
39
- ```
21
+ // Returns an OWNED copy of self (`move out string`). Both backends hand
22
+ // back a fresh allocation the caller frees — never an alias of self's
23
+ // storage — so the result can outlive the receiver (e.g. escape a match
24
+ // branch that reclaims the scrutinee temp). Goes through the `strdup`
25
+ // extern: the adapter marshals self.ptr to libc and re-wraps the copy
26
+ // with its length.
27
+ pub func to_string = (self, move out string) {
28
+ return strdup(self)
40
29
  }
41
30
 
42
31
  pub func at = (self, int index: index >= 0 && index < self.length, out char) {
@@ -46,8 +35,9 @@ pub struct string: Stringable, Hashable, Equatable {
46
35
  ```
47
36
  ```
48
37
  #arch: aarch64
49
- // x19 = self (pointer), x1 = index
50
- ldrb w0, [x19, x1]
38
+ // Fat-string ABI: x19 = self ptr, x20 = self len; index in x2
39
+ // (self's pair occupies slots 0-1).
40
+ ldrb w0, [x19, x2]
51
41
  ```
52
42
  }
53
43
 
@@ -89,9 +79,10 @@ pub struct string: Stringable, Hashable, Equatable {
89
79
  ```
90
80
  #arch: aarch64
91
81
  // view string is (ptr, len), returned in the x0/x1 register pair.
92
- // x19 = self (char*), x1 = start, x2 = end (retained through prologue).
93
- add x0, x19, x1 // ptr = self + start
94
- sub x1, x2, x1 // len = end - start
82
+ // Fat-string ABI: x19 = self ptr, x20 = self len; start in x2,
83
+ // end in x3 (self's pair occupies slots 0-1).
84
+ add x0, x19, x2 // ptr = self + start
85
+ sub x1, x3, x2 // len = end - start
95
86
  ```
96
87
  }
97
88
 
@@ -114,10 +105,22 @@ pub struct string: Stringable, Hashable, Equatable {
114
105
  ```
115
106
  ```
116
107
  #arch: aarch64
117
- // x0 = self, x1 = other on entry
118
- bl _strcmp
108
+ // Fat-string ABI: x19/x20 = self (ptr, len); other = (x2 ptr, x3
109
+ // len). Equal lengths + equal bytes — no NUL scan.
110
+ stp x20, x21, [sp, #-16]!
111
+ cmp x20, x3
112
+ b.ne .Lstring_eq_len_ne
113
+ mov x0, x19
114
+ mov x1, x2
115
+ mov x2, x20
116
+ bl _memcmp
119
117
  cmp x0, #0
120
118
  cset x0, eq
119
+ b .Lstring_eq_done
120
+ .Lstring_eq_len_ne:
121
+ mov x0, #0
122
+ .Lstring_eq_done:
123
+ ldp x20, x21, [sp], #16
121
124
  ```
122
125
  }
123
126
 
@@ -133,23 +136,29 @@ pub struct string: Stringable, Hashable, Equatable {
133
136
  ```
134
137
  ```
135
138
  #arch: aarch64
136
- stp x20, x21, [sp, #-16]!
137
- mov x20, x1
138
- bl _strlen
139
- mov x21, x0
140
- mov x0, x20
141
- bl _strlen
142
- add x0, x21, x0
139
+ // Fat-string ABI: x19/x20 = self (ptr, len); other = (x2 ptr, x3
140
+ // len). Two memcpys of known length — no strlen/strcpy/strcat.
141
+ // Returns the (buf, total) pair.
142
+ stp x21, x22, [sp, #-16]!
143
+ stp x2, x3, [sp, #-16]! // other (ptr, len)
144
+ add x0, x20, x3
143
145
  add x0, x0, #1
144
146
  bl _malloc
145
- mov x21, x0
146
- mov x1, x19
147
- bl _strcpy
147
+ mov x21, x0 // buf (callee-saved across memcpys)
148
148
  mov x0, x21
149
- mov x1, x20
150
- bl _strcat
149
+ mov x1, x19
150
+ mov x2, x20
151
+ bl _memcpy // left half
152
+ ldp x1, x2, [sp] // other ptr, len
153
+ add x0, x21, x20
154
+ bl _memcpy // right half
155
+ ldp x1, x2, [sp], #16 // reload other len; pop
156
+ add x3, x20, x2 // total bytes
157
+ add x0, x21, x3
158
+ strb wzr, [x0] // NUL-terminate for libc consumers
159
+ mov x1, x3
151
160
  mov x0, x21
152
- ldp x20, x21, [sp], #16
161
+ ldp x21, x22, [sp], #16
153
162
  ```
154
163
  }
155
164
 
@@ -169,29 +178,34 @@ pub struct string: Stringable, Hashable, Equatable {
169
178
  ```
170
179
  ```
171
180
  #arch: aarch64
181
+ // Fat-string ABI: x19/x20 = self (ptr, len); count in x2. memcpy
182
+ // loop of known stride; returns the (buf, total) pair.
172
183
  stp x20, x21, [sp, #-16]!
173
- stp x22, xzr, [sp, #-16]!
174
- mov x20, x1
175
- bl _strlen
176
- mov x21, x0
177
- mul x0, x0, x20
178
- add x0, x0, #1
184
+ stp x22, x23, [sp, #-16]!
185
+ stp x24, x25, [sp, #-16]!
186
+ mov x21, x20 // self len (stride)
187
+ mov x22, x2 // count
188
+ mul x23, x21, x22 // total bytes
189
+ add x0, x23, #1
179
190
  bl _malloc
180
- str x0, [x29, #8]
181
- mov x22, x0
182
- .Lmul_loop:
183
- cbz x20, .Lmul_done
184
- mov x0, x22
191
+ mov x24, x0 // buf
192
+ mov x25, x0 // cursor
193
+ .Lstring_mul_loop:
194
+ cbz x22, .Lstring_mul_done
195
+ mov x0, x25
185
196
  mov x1, x19
186
197
  mov x2, x21
187
198
  bl _memcpy
188
- add x22, x22, x21
189
- sub x20, x20, #1
190
- b .Lmul_loop
191
- .Lmul_done:
192
- strb wzr, [x22]
193
- ldr x0, [x29, #8]
194
- ldp x22, xzr, [sp], #16
199
+ add x25, x25, x21
200
+ sub x22, x22, #1
201
+ b .Lstring_mul_loop
202
+ .Lstring_mul_done:
203
+ add x0, x24, x23
204
+ strb wzr, [x0] // NUL-terminate for libc consumers
205
+ mov x1, x23
206
+ mov x0, x24
207
+ ldp x24, x25, [sp], #16
208
+ ldp x22, x23, [sp], #16
195
209
  ldp x20, x21, [sp], #16
196
210
  ```
197
211
  }
@@ -59,7 +59,7 @@ pub struct StringBuilder {
59
59
  }
60
60
 
61
61
  // Append a single byte (char).
62
- func append_char = (ref self, char c) {
62
+ pub inline func append_char = (ref self, char c) {
63
63
  ```
64
64
  #arch: c
65
65
  int idx = self->len;
@@ -128,15 +128,13 @@ pub struct StringBuilder {
128
128
  ```
129
129
  ```
130
130
  #arch: aarch64
131
- // x19 = self, x1 = s
132
- // Save s across strlen/realloc.
133
- mov x0, x1
131
+ // Fat-string ABI: x19 = self; s = (x1 ptr, x2 len). The length is
132
+ // already in a register — no strlen.
134
133
  sub sp, sp, #32
135
- str x0, [sp, #0]
136
- bl _strlen
137
- str x0, [sp, #8] // sl
134
+ str x1, [sp, #0] // s ptr
135
+ str x2, [sp, #8] // sl
138
136
  ldr x1, [x19, #16] // len
139
- add x1, x1, x0 // len + sl
137
+ add x1, x1, x2 // len + sl
140
138
  add x1, x1, #1 // +1 for null room
141
139
  // inline ensure(x1)
142
140
  ldr x2, [x19, #24]
@@ -157,12 +155,11 @@ pub struct StringBuilder {
157
155
  bl _realloc
158
156
  str x0, [x19, #8]
159
157
  .Lsb_as_copy:
160
- // memcpy(data + len, s, sl)
161
- ldr x1, [sp, #8] // sl
158
+ // memcpy(data + len, s.ptr, sl)
162
159
  ldr x0, [x19, #8] // data
163
- ldr x2, [x19, #16] // len
164
- add x0, x0, x2 // dest = data + len
165
- ldr x1, [sp, #0] // src = s
160
+ ldr x1, [x19, #16] // len
161
+ add x0, x0, x1 // dest = data + len
162
+ ldr x1, [sp, #0] // src = s ptr
166
163
  ldr x2, [sp, #8] // n = sl
167
164
  bl _memcpy
168
165
  // len = len + sl
@@ -221,6 +218,7 @@ pub struct StringBuilder {
221
218
  strb wzr, [x0, x1] // data[len] = 0
222
219
  // transfer ownership: clear self->data. x0 retains the old ptr.
223
220
  str xzr, [x19, #8]
221
+ // Fat-string ABI: (x0 = data, x1 = len) is already the return pair.
224
222
  ```
225
223
  }
226
224