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.
- package/core/System/Arena.nm +9 -7
- package/core/System/Array.nm +1 -1
- package/core/System/Atomic.nm +3 -3
- package/core/System/BigInt.nm +182 -23
- package/core/System/Buffer.nm +37 -35
- package/core/System/ClassBuffer.nm +18 -18
- package/core/System/Controls/Alert.nm +1 -1
- package/core/System/Controls/App.nm +5 -5
- package/core/System/Controls/Button.nm +1 -1
- package/core/System/Controls/CheckBox.nm +1 -1
- package/core/System/Controls/ColorPicker.nm +1 -1
- package/core/System/Controls/ComboBox.nm +1 -1
- package/core/System/Controls/Container.nm +34 -34
- package/core/System/Controls/DatePicker.nm +1 -1
- package/core/System/Controls/Form.nm +2 -2
- package/core/System/Controls/GroupBox.nm +1 -1
- package/core/System/Controls/ImageView.nm +1 -1
- package/core/System/Controls/Layout.nm +5 -5
- package/core/System/Controls/LevelIndicator.nm +1 -1
- package/core/System/Controls/ListView.nm +1 -1
- package/core/System/Controls/Menu.nm +1 -1
- package/core/System/Controls/NumberInput.nm +3 -3
- package/core/System/Controls/OtpInput.nm +2 -2
- package/core/System/Controls/OutlineView.nm +1 -1
- package/core/System/Controls/Pagination.nm +6 -6
- package/core/System/Controls/Popover.nm +1 -1
- package/core/System/Controls/Progress.nm +1 -1
- package/core/System/Controls/Radio.nm +1 -1
- package/core/System/Controls/Scrollable.nm +1 -1
- package/core/System/Controls/SearchField.nm +1 -1
- package/core/System/Controls/SecureField.nm +1 -1
- package/core/System/Controls/Segmented.nm +1 -1
- package/core/System/Controls/Select.nm +1 -1
- package/core/System/Controls/Separator.nm +1 -1
- package/core/System/Controls/Sheet.nm +1 -1
- package/core/System/Controls/Slider.nm +1 -1
- package/core/System/Controls/Spinner.nm +3 -3
- package/core/System/Controls/SplitView.nm +2 -2
- package/core/System/Controls/Stepper.nm +1 -1
- package/core/System/Controls/Switch.nm +1 -1
- package/core/System/Controls/Table.nm +1 -1
- package/core/System/Controls/Tabs.nm +1 -1
- package/core/System/Controls/Text.nm +1 -1
- package/core/System/Controls/TextBox.nm +1 -1
- package/core/System/Controls/TextView.nm +1 -1
- package/core/System/Controls/Toggle.nm +1 -1
- package/core/System/Controls/Toolbar.nm +1 -1
- package/core/System/Controls/Window.nm +11 -3
- package/core/System/Fiber.nm +16 -3
- package/core/System/Format.nm +751 -0
- package/core/System/Graph.nm +2 -2
- package/core/System/LinkedList.nm +8 -6
- package/core/System/List.nm +16 -13
- package/core/System/Map.nm +11 -9
- package/core/System/Random.nm +3 -3
- package/core/System/Set.nm +9 -7
- package/core/System/StableList.nm +7 -7
- package/core/System/Stream/File.nm +16 -16
- package/core/System/Stream/Http.nm +3 -3
- package/core/System/Stream/Tcp.nm +5 -5
- package/core/System/StringBuilder.nm +16 -16
- package/core/System/Task.nm +56 -64
- package/core/System/Test.nm +18 -18
- package/core/System/Text/Chars.nm +1 -1
- package/core/System/Text/Json.nm +12 -12
- package/core/System/Text/JsonTree.nm +8 -8
- package/core/System/Text/Regex.nm +20 -20
- package/core/System/Thread.nm +26 -3
- package/core/System/Tree.nm +19 -3
- package/dist/index.mjs +3423 -1849
- package/package.json +1 -1
- 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
|
+
}
|