nomen-lang 0.9.0 → 0.11.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 (72) hide show
  1. package/core/System/Arena.nm +9 -7
  2. package/core/System/Array.nm +1 -1
  3. package/core/System/Atomic.nm +3 -3
  4. package/core/System/BigInt.nm +182 -23
  5. package/core/System/Buffer.nm +37 -35
  6. package/core/System/ClassBuffer.nm +18 -18
  7. package/core/System/Controls/Alert.nm +1 -1
  8. package/core/System/Controls/App.nm +5 -5
  9. package/core/System/Controls/Button.nm +1 -1
  10. package/core/System/Controls/CheckBox.nm +1 -1
  11. package/core/System/Controls/ColorPicker.nm +1 -1
  12. package/core/System/Controls/ComboBox.nm +1 -1
  13. package/core/System/Controls/Container.nm +34 -34
  14. package/core/System/Controls/DatePicker.nm +1 -1
  15. package/core/System/Controls/Form.nm +2 -2
  16. package/core/System/Controls/GroupBox.nm +1 -1
  17. package/core/System/Controls/ImageView.nm +1 -1
  18. package/core/System/Controls/Layout.nm +5 -5
  19. package/core/System/Controls/LevelIndicator.nm +1 -1
  20. package/core/System/Controls/ListView.nm +1 -1
  21. package/core/System/Controls/Menu.nm +1 -1
  22. package/core/System/Controls/NumberInput.nm +3 -3
  23. package/core/System/Controls/OtpInput.nm +2 -2
  24. package/core/System/Controls/OutlineView.nm +1 -1
  25. package/core/System/Controls/Pagination.nm +6 -6
  26. package/core/System/Controls/Popover.nm +1 -1
  27. package/core/System/Controls/Progress.nm +1 -1
  28. package/core/System/Controls/Radio.nm +1 -1
  29. package/core/System/Controls/Scrollable.nm +1 -1
  30. package/core/System/Controls/SearchField.nm +1 -1
  31. package/core/System/Controls/SecureField.nm +1 -1
  32. package/core/System/Controls/Segmented.nm +1 -1
  33. package/core/System/Controls/Select.nm +1 -1
  34. package/core/System/Controls/Separator.nm +1 -1
  35. package/core/System/Controls/Sheet.nm +1 -1
  36. package/core/System/Controls/Slider.nm +1 -1
  37. package/core/System/Controls/Spinner.nm +3 -3
  38. package/core/System/Controls/SplitView.nm +2 -2
  39. package/core/System/Controls/Stepper.nm +1 -1
  40. package/core/System/Controls/Switch.nm +1 -1
  41. package/core/System/Controls/Table.nm +1 -1
  42. package/core/System/Controls/Tabs.nm +1 -1
  43. package/core/System/Controls/Text.nm +1 -1
  44. package/core/System/Controls/TextBox.nm +1 -1
  45. package/core/System/Controls/TextView.nm +1 -1
  46. package/core/System/Controls/Toggle.nm +1 -1
  47. package/core/System/Controls/Toolbar.nm +1 -1
  48. package/core/System/Controls/Window.nm +11 -3
  49. package/core/System/Fiber.nm +16 -3
  50. package/core/System/Format.nm +751 -0
  51. package/core/System/Graph.nm +2 -2
  52. package/core/System/LinkedList.nm +8 -6
  53. package/core/System/List.nm +16 -13
  54. package/core/System/Map.nm +11 -9
  55. package/core/System/Random.nm +3 -3
  56. package/core/System/Set.nm +9 -7
  57. package/core/System/StableList.nm +7 -7
  58. package/core/System/Stream/File.nm +16 -16
  59. package/core/System/Stream/Http.nm +3 -3
  60. package/core/System/Stream/Tcp.nm +5 -5
  61. package/core/System/StringBuilder.nm +16 -16
  62. package/core/System/Task.nm +56 -64
  63. package/core/System/Test.nm +18 -18
  64. package/core/System/Text/Chars.nm +1 -1
  65. package/core/System/Text/Json.nm +12 -12
  66. package/core/System/Text/JsonTree.nm +8 -8
  67. package/core/System/Text/Regex.nm +20 -20
  68. package/core/System/Thread.nm +26 -3
  69. package/core/System/Tree.nm +19 -3
  70. package/dist/index.mjs +3423 -1849
  71. package/package.json +1 -1
  72. package/src/docs.ts +10 -1
@@ -0,0 +1,751 @@
1
+ /**
2
+ * Compile-time-checked rendering helpers behind interpolation format specs
3
+ * (`\{expr:spec}` — SPEC, String Interpolation). The checker validates each
4
+ * spec against the argument's type and lowers the argument into one of these
5
+ * calls, so the interpolation helper keeps receiving an ordinary owned
6
+ * string. Every function is also usable directly.
7
+ *
8
+ * Widths are signed: positive right-aligns, negative left-aligns, 0 means
9
+ * "no padding". `zero` pads with '0' between the sign and the digits (the
10
+ * printf `0` flag); alignment and zero-padding never combine (`<`/`>` and
11
+ * `0` are alternative fills in the spec grammar).
12
+ **/
13
+ pub struct Format {
14
+ // Render a float with a fixed number of decimal places. `spec` is the
15
+ // full printf directive the checker built from the format spec (e.g.
16
+ // "%.2f", "%08.2f", "%-10.3f") — the body formats nothing itself, it
17
+ // only hands the directive to snprintf (twice: size, then render, so
18
+ // any width is safe).
19
+ pub func float_fixed = (float value, string spec, mv out string) {
20
+ ```
21
+ #arch: c
22
+ int need = snprintf(NULL, 0, spec.ptr, value);
23
+ char* str = (char*)malloc((size_t)need + 1);
24
+ snprintf(str, (size_t)need + 1, spec.ptr, value);
25
+ str[need] = 0;
26
+ return (nomen_string){ str, need };
27
+ ```
28
+ ```
29
+ #arch: aarch64
30
+ // x0 = value (double bits — GP param), x1/x2 = the spec's (ptr, len)
31
+ // pair. The spec IS the directive. The variadic double rides the
32
+ // stack (Darwin AAPCS64 varargs); [sp] is re-armed before each call.
33
+ stp x19, x20, [sp, #-16]!
34
+ stp x21, x22, [sp, #-16]!
35
+ mov x19, x0 // value bits
36
+ mov x20, x1 // spec ptr
37
+ sub sp, sp, #16
38
+ fmov d0, x19
39
+ str d0, [sp] // variadic double slot
40
+ mov x0, #0 // NULL — sizing pass
41
+ mov x1, #0
42
+ mov x2, x20
43
+ bl _snprintf
44
+ mov x21, x0 // need
45
+ add x0, x21, #1
46
+ bl _malloc
47
+ mov x22, x0 // str
48
+ fmov d0, x19
49
+ str d0, [sp] // re-arm the slot
50
+ mov x0, x22
51
+ add x1, x21, #1
52
+ mov x2, x20
53
+ bl _snprintf // writes need bytes + NUL at str[need]
54
+ mov x0, x22
55
+ mov x1, x21
56
+ add sp, sp, #16
57
+ ldp x21, x22, [sp], #16
58
+ ldp x19, x20, [sp], #16
59
+ ```
60
+ }
61
+
62
+ // Grouped fixed-point rendering — the `\{value:.2n}` / `\{value:n}` spec
63
+ // (C#'s "N" standard format): render with %.Nf (2 decimals when the spec
64
+ // omits a precision, the C# default), then comma the integer part every
65
+ // three digits. Width and alignment compose at the call site through
66
+ // `Format.pad`; zero-padding and grouping never combine (checked).
67
+ pub func float_grouped = (float value, int places: places >= 0, mv out string) {
68
+ var sb = StringBuilder()
69
+ sb.append_char('%')
70
+ sb.append_char('.')
71
+ sb.append_int(places)
72
+ sb.append_char('f')
73
+ var string rendered = Format.float_fixed(value, sb.to_string())
74
+ return Format.group_int_part(rendered)
75
+ }
76
+
77
+ // Insert ',' every three digits of the integer part of an already
78
+ // rendered number ("1234.50" → "1,234.50", "-987654" → "-987,654").
79
+ // A sign and any fractional part pass through untouched.
80
+ internal func group_int_part = (string text, mv out string) { var int len = text.length
81
+ var int dot = len
82
+ var int i = 0
83
+ while i < len; i += 1 {
84
+ if text.at(i) == '.' {
85
+ dot = i
86
+ }
87
+ }
88
+ var int digits = 0
89
+ i = 0
90
+ while i < dot; i += 1 {
91
+ var char c = text.at(i)
92
+ if c != '-' && c != '+' {
93
+ digits += 1
94
+ }
95
+ }
96
+ var sb = StringBuilder()
97
+ var int seen = 0
98
+ i = 0
99
+ while i < len; i += 1 {
100
+ var char c = text.at(i)
101
+ sb.append_char(c)
102
+ if i < dot && c != '-' && c != '+' {
103
+ seen += 1
104
+ var int remaining = digits - seen
105
+ if remaining > 0 && remaining % 3 == 0 {
106
+ sb.append_char(',')
107
+ }
108
+ }
109
+ }
110
+ return sb.to_string()
111
+ }
112
+
113
+ // Render a signed integer in base 2/8/10/16. A negative value renders
114
+ // its magnitude with a leading '-' in every base (not two's complement).
115
+ // `group` inserts thousands separators every three digits — intended for
116
+ // base 10 (the `\{value:n}` spec); a grouped other base is the caller's
117
+ // choice and commas every three digits regardless.
118
+ pub func int_base = (int64 value, int base: base >= 2 && base <= 16, bool upper, int width, bool zero, bool group, mv out string) {
119
+ const string digits = "0123456789abcdef"
120
+ const string upper_digits = "0123456789ABCDEF"
121
+ var int w = width
122
+ var bool left = false
123
+ if w < 0 {
124
+ left = true
125
+ w = 0 - w
126
+ }
127
+ var bool neg = value < 0
128
+ var uint64 mag = value as uint64
129
+ if neg {
130
+ // Two's-complement negate on the UNSIGNED magnitude (wraps
131
+ // correctly for INT64_MIN — no signed-overflow UB).
132
+ var uint64 zero = 0
133
+ mag = zero - mag
134
+ }
135
+ var uint64 b = base as uint64
136
+ // Digits arrive least-significant first; the emit walks the scratch
137
+ // backwards. 72 slots covers binary's 64 digits plus the sign.
138
+ var Array<char> chars = Array<char>.with('0', 72)
139
+ var int count = 0
140
+ if mag == 0 {
141
+ chars.set(0, '0')
142
+ count = 1
143
+ } else {
144
+ while mag > 0 {
145
+ var uint64 d = mag % b
146
+ mag = mag / b
147
+ var int di = d as int
148
+ if di < 10 {
149
+ chars.set(count, digits.at(di))
150
+ }
151
+ if di >= 10 && upper {
152
+ chars.set(count, upper_digits.at(di))
153
+ }
154
+ if di >= 10 && !upper {
155
+ chars.set(count, digits.at(di))
156
+ }
157
+ count += 1
158
+ }
159
+ }
160
+ var int total = count
161
+ if neg {
162
+ total += 1
163
+ }
164
+ var int padding = 0
165
+ if w > total {
166
+ padding = w - total
167
+ }
168
+ var sb = StringBuilder()
169
+ var int i = 0
170
+ if left {
171
+ // sign, digits, spaces
172
+ if neg {
173
+ sb.append_char('-')
174
+ }
175
+ while i < count; i += 1 {
176
+ sb.append_char(chars.at(count - 1 - i))
177
+ if group && base == 10 && count - 1 - i > 0 && (count - 1 - i) % 3 == 0 {
178
+ sb.append_char(',')
179
+ }
180
+ }
181
+ i = 0
182
+ while i < padding; i += 1 {
183
+ sb.append_char(' ')
184
+ }
185
+ } else {
186
+ if zero {
187
+ // sign, zeros, digits (printf's `0` flag)
188
+ if neg {
189
+ sb.append_char('-')
190
+ }
191
+ while i < padding; i += 1 {
192
+ sb.append_char('0')
193
+ }
194
+ } else {
195
+ // spaces, sign, digits
196
+ while i < padding; i += 1 {
197
+ sb.append_char(' ')
198
+ }
199
+ if neg {
200
+ sb.append_char('-')
201
+ }
202
+ }
203
+ i = 0
204
+ while i < count; i += 1 {
205
+ sb.append_char(chars.at(count - 1 - i))
206
+ if group && base == 10 && count - 1 - i > 0 && (count - 1 - i) % 3 == 0 {
207
+ sb.append_char(',')
208
+ }
209
+ }
210
+ }
211
+ return sb.to_string()
212
+ }
213
+
214
+ // Unsigned twin of int_base — the value argument keeps the uint64 family
215
+ // (uint8..uint64) so no value is reinterpreted through a signed width.
216
+ pub func uint_base = (uint64 value, int base: base >= 2 && base <= 16, bool upper, int width, bool zero, bool group, mv out string) {
217
+ const string digits = "0123456789abcdef"
218
+ const string upper_digits = "0123456789ABCDEF"
219
+ var int w = width
220
+ var bool left = false
221
+ if w < 0 {
222
+ left = true
223
+ w = 0 - w
224
+ }
225
+ var uint64 mag = value
226
+ var uint64 b = base as uint64
227
+ var Array<char> chars = Array<char>.with('0', 72)
228
+ var int count = 0
229
+ if mag == 0 {
230
+ chars.set(0, '0')
231
+ count = 1
232
+ } else {
233
+ while mag > 0 {
234
+ var uint64 d = mag % b
235
+ mag = mag / b
236
+ var int di = d as int
237
+ if di < 10 {
238
+ chars.set(count, digits.at(di))
239
+ }
240
+ if di >= 10 && upper {
241
+ chars.set(count, upper_digits.at(di))
242
+ }
243
+ if di >= 10 && !upper {
244
+ chars.set(count, digits.at(di))
245
+ }
246
+ count += 1
247
+ }
248
+ }
249
+ var int padding = 0
250
+ if w > count {
251
+ padding = w - count
252
+ }
253
+ var sb = StringBuilder()
254
+ var int i = 0
255
+ if left {
256
+ while i < count; i += 1 {
257
+ sb.append_char(chars.at(count - 1 - i))
258
+ if group && base == 10 && count - 1 - i > 0 && (count - 1 - i) % 3 == 0 {
259
+ sb.append_char(',')
260
+ }
261
+ }
262
+ i = 0
263
+ while i < padding; i += 1 {
264
+ sb.append_char(' ')
265
+ }
266
+ } else {
267
+ while i < padding; i += 1 {
268
+ if zero {
269
+ sb.append_char('0')
270
+ } else {
271
+ sb.append_char(' ')
272
+ }
273
+ }
274
+ i = 0
275
+ while i < count; i += 1 {
276
+ sb.append_char(chars.at(count - 1 - i))
277
+ if group && base == 10 && count - 1 - i > 0 && (count - 1 - i) % 3 == 0 {
278
+ sb.append_char(',')
279
+ }
280
+ }
281
+ }
282
+ return sb.to_string()
283
+ }
284
+
285
+ // Align an already-rendered text in a field of `|width|` spaces. The
286
+ // text is borrowed; the result is always a fresh owned string.
287
+ pub func pad = (string text, int width, mv out string) {
288
+ var int w = width
289
+ var bool left = false
290
+ if w < 0 {
291
+ left = true
292
+ w = 0 - w
293
+ }
294
+ if w <= text.length {
295
+ return text.copy()
296
+ }
297
+ var int padding = w - text.length
298
+ var sb = StringBuilder()
299
+ if left {
300
+ // text, then spaces
301
+ sb.append(text)
302
+ var int i = 0
303
+ while i < padding; i += 1 {
304
+ sb.append_char(' ')
305
+ }
306
+ } else {
307
+ // spaces, then text
308
+ var int i = 0
309
+ while i < padding; i += 1 {
310
+ sb.append_char(' ')
311
+ }
312
+ sb.append(text)
313
+ }
314
+ return sb.to_string()
315
+ }
316
+
317
+ // --- Custom patterns (the `\{value:#,##0.00}` mini-language) ------------
318
+ //
319
+ // Subset, validated at compile time (SPEC, String Interpolation):
320
+ // 0 digit slot, always renders (zero-pads)
321
+ // # digit slot, renders only when a digit exists
322
+ // . decimal point; trailing '#' fraction slots trim zeros
323
+ // , group separator between digit slots; the rightmost comma
324
+ // sets the group size for all groups
325
+ // % value scaled x100; '%' emitted at its position
326
+ // '...' quoted literal; any other character passes through
327
+ // ; at most two sections (positive;negative) — the negative
328
+ // section renders the absolute value, sign and all
329
+ // Not in the language: exponential patterns (use the `:e` spec), the
330
+ // per-mille sign, trailing-comma scaling, three-section forms.
331
+ //
332
+ // The scanners below share one shape: snapshot `was_quoted`, toggle the
333
+ // quote state on a quote character, then run each structural check as
334
+ // its own top-level `if` gated on "this character is structural" —
335
+ // Nomen has no `else if`.
336
+
337
+ // The section of `pattern` that applies to `negative` values.
338
+ internal func pat_section = (string pattern, bool negative, mv out string) {
339
+ var int split = -1
340
+ var bool quoted = false
341
+ var int i = 0
342
+ while i < pattern.length; i += 1 {
343
+ var char c = pattern.at(i)
344
+ var bool was_quoted = quoted
345
+ if c == '\'' {
346
+ if was_quoted {
347
+ quoted = false
348
+ }
349
+ if !was_quoted {
350
+ quoted = true
351
+ }
352
+ }
353
+ if !was_quoted && !quoted && c == ';' {
354
+ split = i
355
+ }
356
+ }
357
+ var sb = StringBuilder()
358
+ if split == -1 {
359
+ return pattern.copy()
360
+ }
361
+ var int from = 0
362
+ if negative {
363
+ from = split + 1
364
+ }
365
+ i = from
366
+ while i < pattern.length; i += 1 {
367
+ sb.append_char(pattern.at(i))
368
+ }
369
+ return sb.to_string()
370
+ }
371
+
372
+ // True when the pattern carries two ';'-separated sections.
373
+ internal func pat_has2 = (string pattern, out bool) {
374
+ var bool quoted = false
375
+ var bool found = false
376
+ var int i = 0
377
+ while i < pattern.length; i += 1 {
378
+ var char c = pattern.at(i)
379
+ var bool was_quoted = quoted
380
+ if c == '\'' {
381
+ if was_quoted {
382
+ quoted = false
383
+ }
384
+ if !was_quoted {
385
+ quoted = true
386
+ }
387
+ }
388
+ if !was_quoted && !quoted && c == ';' {
389
+ found = true
390
+ }
391
+ }
392
+ return found
393
+ }
394
+
395
+ // True when the pattern carries a '%' anywhere (unquoted).
396
+ internal func pat_has_percent = (string pattern, out bool) {
397
+ var bool quoted = false
398
+ var bool found = false
399
+ var int i = 0
400
+ while i < pattern.length; i += 1 {
401
+ var char c = pattern.at(i)
402
+ var bool was_quoted = quoted
403
+ if c == '\'' {
404
+ if was_quoted {
405
+ quoted = false
406
+ }
407
+ if !was_quoted {
408
+ quoted = true
409
+ }
410
+ }
411
+ if !was_quoted && !quoted && c == '%' {
412
+ found = true
413
+ }
414
+ }
415
+ return found
416
+ }
417
+
418
+ // The widest fraction-slot count across both sections — the snprintf
419
+ // pass renders at least this many digits; shaping trims to the applied
420
+ // section's own slots.
421
+ internal func pat_max_frac = (string pattern, out int) {
422
+ var bool quoted = false
423
+ var bool in_frac = false
424
+ var int count = 0
425
+ var int best = 0
426
+ var int i = 0
427
+ while i < pattern.length; i += 1 {
428
+ var char c = pattern.at(i)
429
+ var bool was_quoted = quoted
430
+ if c == '\'' {
431
+ if was_quoted {
432
+ quoted = false
433
+ }
434
+ if !was_quoted {
435
+ quoted = true
436
+ }
437
+ }
438
+ var bool structural = !was_quoted && !quoted
439
+ if structural && c == '.' {
440
+ in_frac = true
441
+ }
442
+ if structural && (c == '0' || c == '#') {
443
+ if in_frac {
444
+ count += 1
445
+ if count > best {
446
+ best = count
447
+ }
448
+ }
449
+ }
450
+ if structural && c == ';' {
451
+ in_frac = false
452
+ count = 0
453
+ }
454
+ if structural && c != '.' && c != '0' && c != '#' {
455
+ // any other literal ends the fraction run
456
+ in_frac = false
457
+ count = 0
458
+ }
459
+ }
460
+ return best
461
+ }
462
+
463
+ // Decimal digits of a magnitude, most-significant first, no sign.
464
+ internal func pat_digits10 = (uint64 mag, mv out string) {
465
+ var sb = StringBuilder()
466
+ if mag == 0 {
467
+ sb.append_char('0')
468
+ return sb.to_string()
469
+ }
470
+ var Array<char> chars = Array<char>.with('0', 20)
471
+ var int count = 0
472
+ var uint64 m = mag
473
+ var string dig = "0123456789"
474
+ while m > 0 {
475
+ var uint64 d = m % 10
476
+ m = m / 10
477
+ chars.set(count, dig.at(d as int))
478
+ count += 1
479
+ }
480
+ var int j = count - 1
481
+ while j >= 0; j -= 1 {
482
+ sb.append_char(chars.at(j))
483
+ }
484
+ return sb.to_string()
485
+ }
486
+
487
+ // Shape digit strings against one pattern section. `int_digits` is the
488
+ // unsigned integer part; `frac_raw` the rendered fraction digits at the
489
+ // section's full precision ("" for integers — zeros fill up to the
490
+ // section's minimum). Emits prefix literals, the grouped integer part,
491
+ // the trimmed fraction, and the suffix. Every helper call result lands
492
+ // in a single-binding local: no owned string is ever reassigned (the
493
+ // displaced-free pass does not cover every conditional reassignment
494
+ // shape — FOLLOWUP.md).
495
+ internal func pat_render = (string section, string int_digits, string frac_raw, bool negative, mv out string) {
496
+ // Pass 1 — locate the regions: prefix (before the first integer
497
+ // slot), the integer slot run, the fraction slots, and the suffix
498
+ // (after the last slot).
499
+ var bool quoted = false
500
+ var int first_slot = -1
501
+ var int last_slot = -1
502
+ var int dot = -1
503
+ var int pad_slots = 0
504
+ var int frac_slots = 0
505
+ var int frac_min = 0
506
+ var bool has_comma = false
507
+ var int slots_after_comma = 0
508
+ var int i = 0
509
+ while i < section.length; i += 1 {
510
+ var char c = section.at(i)
511
+ var bool was_quoted = quoted
512
+ if c == '\'' {
513
+ if was_quoted {
514
+ quoted = false
515
+ }
516
+ if !was_quoted {
517
+ quoted = true
518
+ }
519
+ }
520
+ var bool structural = !was_quoted && !quoted
521
+ if structural && c == '.' {
522
+ if dot == -1 {
523
+ dot = i
524
+ }
525
+ }
526
+ if structural && (c == '0' || c == '#') {
527
+ last_slot = i
528
+ if dot == -1 {
529
+ // an integer slot — never counted before the dot
530
+ if first_slot == -1 {
531
+ first_slot = i
532
+ }
533
+ slots_after_comma += 1
534
+ if c == '0' {
535
+ // only '0' slots force zero-padding ('#' drops out)
536
+ pad_slots += 1
537
+ }
538
+ } else {
539
+ frac_slots += 1
540
+ if c == '0' {
541
+ frac_min = frac_slots
542
+ }
543
+ }
544
+ }
545
+ if structural && c == ',' && dot == -1 {
546
+ has_comma = true
547
+ slots_after_comma = 0
548
+ }
549
+ }
550
+ // Pass 2 — emit.
551
+ var sb = StringBuilder()
552
+ if negative {
553
+ sb.append_char('-')
554
+ }
555
+ // prefix: [0, first_slot), quotes stripped
556
+ i = 0
557
+ quoted = false
558
+ while i < first_slot; i += 1 {
559
+ var char c = section.at(i)
560
+ var bool was_quoted = quoted
561
+ if c == '\'' {
562
+ if was_quoted {
563
+ quoted = false
564
+ }
565
+ if !was_quoted {
566
+ quoted = true
567
+ }
568
+ }
569
+ if was_quoted && c != '\'' {
570
+ sb.append_char(c)
571
+ }
572
+ if !was_quoted && c != '\'' {
573
+ sb.append_char(c)
574
+ }
575
+ }
576
+ // integer part: zero-pad to the '0' slots, group per the rightmost
577
+ // comma's run, drop a lone zero when every slot is '#' (the shape
578
+ // helper's own empty case). The shape result lands in a local — an
579
+ // inline call argument to append_string would never be anchored.
580
+ sb.append_string(Format.pat_shape_int(int_digits, pad_slots, has_comma, slots_after_comma))
581
+ // fraction: trim trailing zeros down to the section minimum, then
582
+ // zero-fill up to it (integer values arrive with no fraction at all).
583
+ var string kept = Format.pat_shape_frac(frac_raw, frac_slots, frac_min)
584
+ if kept.length > 0 {
585
+ sb.append_char('.')
586
+ sb.append(kept)
587
+ }
588
+ // suffix: (last_slot, end), quotes stripped, structural dot dropped
589
+ i = last_slot + 1
590
+ quoted = false
591
+ while i < section.length; i += 1 {
592
+ var char c = section.at(i)
593
+ var bool was_quoted = quoted
594
+ if c == '\'' {
595
+ if was_quoted {
596
+ quoted = false
597
+ }
598
+ if !was_quoted {
599
+ quoted = true
600
+ }
601
+ }
602
+ if was_quoted && c != '\'' {
603
+ sb.append_char(c)
604
+ }
605
+ if !was_quoted && c != '\'' && i != dot {
606
+ sb.append_char(c)
607
+ }
608
+ }
609
+ return sb.to_string()
610
+ }
611
+
612
+ // The integer part of a pattern: zero-pad to the '0' slots, then insert
613
+ // group separators every `group_size` digits. A value of "0" with no '0'
614
+ // slots renders empty (the `#`-only shapes).
615
+ internal func pat_shape_int = (string int_digits, int pad_slots, bool has_comma, int group_size, mv out string) {
616
+ var sb = StringBuilder()
617
+ if int_digits == "0" && pad_slots == 0 {
618
+ return sb.to_string()
619
+ }
620
+ var int total = int_digits.length
621
+ if total < pad_slots {
622
+ total = pad_slots
623
+ }
624
+ var int pad_count = total - int_digits.length
625
+ var int k = 0
626
+ while k < total; k += 1 {
627
+ if k < pad_count {
628
+ sb.append_char('0')
629
+ } else {
630
+ sb.append_char(int_digits.at(k - pad_count))
631
+ }
632
+ var int remaining = total - k - 1
633
+ if has_comma && group_size > 0 && remaining > 0 && remaining % group_size == 0 {
634
+ sb.append_char(',')
635
+ }
636
+ }
637
+ return sb.to_string()
638
+ }
639
+
640
+ // The fraction part of a pattern: clip to the slot count, trim trailing
641
+ // zeros down to the minimum ('0' slots), then zero-fill back up to it.
642
+ internal func pat_shape_frac = (string frac_raw, int frac_slots, int frac_min, mv out string) {
643
+ var sb = StringBuilder()
644
+ var int n = frac_raw.length
645
+ if n > frac_slots {
646
+ n = frac_slots
647
+ }
648
+ var bool trimming = true
649
+ while trimming && n > frac_min {
650
+ var char last = frac_raw.at(n - 1)
651
+ if last == '0' {
652
+ n -= 1
653
+ }
654
+ if last != '0' {
655
+ trimming = false
656
+ }
657
+ }
658
+ var int i = 0
659
+ while i < n; i += 1 {
660
+ sb.append_char(frac_raw.at(i))
661
+ }
662
+ while n < frac_min; n += 1 {
663
+ sb.append_char('0')
664
+ }
665
+ return sb.to_string()
666
+ }
667
+
668
+ // Pick the sign's section, render, and prepend '-' when a negative value
669
+ // has no dedicated section.
670
+ internal func pat_render_all = (string pattern, bool negative, string int_digits, string frac_raw, mv out string) {
671
+ var string section = Format.pat_section(pattern, negative)
672
+ var bool has2 = Format.pat_has2(pattern)
673
+ return Format.pat_render(section, int_digits, frac_raw, negative && !has2)
674
+ }
675
+
676
+ // `#,##0`-style rendering of a signed integer.
677
+ pub func int_pattern = (int64 value, string pattern, mv out string) {
678
+ var bool neg = value < 0
679
+ var uint64 mag = value as uint64
680
+ if neg {
681
+ var uint64 zero = 0
682
+ mag = zero - mag
683
+ }
684
+ if Format.pat_has_percent(pattern) {
685
+ mag = mag * 100
686
+ }
687
+ // The digit string stays a named local on purpose: a plain `string`
688
+ // parameter is a borrow, so this local must free at scope exit, and
689
+ // the per-function ownership sets must isolate it from the
690
+ // same-named `const string digits` locals in int_base/uint_base
691
+ // (name-keyed marks bleeding across library methods suppressed this
692
+ // free — FOLLOWUP receipt; displaced_free.test.ts pins the class).
693
+ var string digits = Format.pat_digits10(mag)
694
+ var string rendered = Format.pat_render_all(pattern, neg, digits, "")
695
+ return rendered
696
+ }
697
+
698
+ // Unsigned twin of int_pattern — no sign, no reinterpretation.
699
+ pub func uint_pattern = (uint64 value, string pattern, mv out string) {
700
+ var uint64 mag = value
701
+ if Format.pat_has_percent(pattern) {
702
+ mag = mag * 100
703
+ }
704
+ return Format.pat_render_all(pattern, false, Format.pat_digits10(mag), "")
705
+ }
706
+
707
+ // Fixed-point pattern rendering of a float: snprintf renders the
708
+ // fraction at the pattern's widest precision, then the shaping pass
709
+ // trims to the applied section's slots.
710
+ pub func float_pattern = (float value, string pattern, mv out string) {
711
+ var bool neg = value < 0
712
+ var float v = value
713
+ if neg {
714
+ var float zero = 0
715
+ v = zero - v
716
+ }
717
+ if Format.pat_has_percent(pattern) {
718
+ v = v * (100 as float)
719
+ }
720
+ var int max_frac = Format.pat_max_frac(pattern)
721
+ var spec_sb = StringBuilder()
722
+ spec_sb.append_char('%')
723
+ spec_sb.append_char('.')
724
+ spec_sb.append_int(max_frac)
725
+ spec_sb.append_char('f')
726
+ var string rendered = Format.float_fixed(v, spec_sb.to_string())
727
+ // rendered = digits '.' frac — split them
728
+ var int dot = rendered.length
729
+ var int i = 0
730
+ while i < rendered.length; i += 1 {
731
+ if rendered.at(i) == '.' {
732
+ dot = i
733
+ }
734
+ }
735
+ var int_sb = StringBuilder()
736
+ var frac_sb = StringBuilder()
737
+ var int s = 0
738
+ while s < rendered.length; s += 1 {
739
+ if s < dot {
740
+ int_sb.append_char(rendered.at(s))
741
+ }
742
+ if s > dot {
743
+ frac_sb.append_char(rendered.at(s))
744
+ }
745
+ }
746
+ // Both digit strings pass as call results (not locals at their last
747
+ // use) so the boundary keeps them borrows the call site anchors.
748
+ return Format.pat_render_all(pattern, neg, int_sb.to_string(), frac_sb.to_string())
749
+ }
750
+
751
+ }