nx_strfmt.nx source
↩ module page · 270 lines · 9213 B
1// nx_strfmt.nx -- minimal printf-style formatter.
2//
3// Replaces the dozens of ad-hoc `out_str` + `out_int64` chains
4// scattered across the runtime with one canonical "format string +
5// args" interface.
6//
7// Format directives supported (printf subset):
8//
9// %d signed decimal i64
10// %u unsigned decimal i64
11// %x lowercase hex i64 (no 0x prefix)
12// %X uppercase hex i64
13// %s NUL-terminated *u8
14// %c single byte (low 8 bits of i64)
15// %p pointer (= 0x + 16-char zero-padded hex)
16// %% literal '%'
17//
18// Width / precision / alignment NOT supported (v0.0.1). Add
19// when the codegen-disassembler-debugger triangle needs prettier
20// output.
21//
22// Three entry points:
23//
24// nx_fmt_to_buf(out, cap, fmt, argv, argc)
25// Writes into a caller buffer; returns bytes written.
26//
27// nx_fmt_to_fd(fd, fmt, argv, argc)
28// Writes directly to fd via sys_write; returns total bytes.
29//
30// nx_fmt_int / nx_fmt_hex / nx_fmt_bytes (low-level helpers)
31//
32// Args are passed as a *i64 array of length argc. Each %d / %u /
33// %x / %X / %p / %c consumes one slot (i64). %s consumes one slot
34// (interpreted as *u8).
35//
36// Why this design (vs varargs):
37// Varargs in NishiLang would need ABI work; a flat i64 array is
38// already the calling convention for ir_emit_call internally.
39// Caller does:
40// let argv: *i64 = sys_mmap(8 * 4)
41// argv[0] = some_int as i64
42// argv[1] = some_str as i64
43// nx_fmt_to_fd(2, "x=%d s=%s\n" as *u8, argv, 2)
44
45// nx_safety_envelope:
46// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
47// sil_target: SIL1
48// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
49// verdict: NOT_YET_EVALUATED
50
51import "syscalls.nx"
52const K_MAGIC_4096: i64 = 4096
53
54// ---- low-level int -> ASCII --------------------------------------
55
56// Write decimal i64 to dst[off..); return new offset. Bounded by cap.
57func nx_fmt_int(dst: *u8, off: i64, cap: i64, v: i64) -> i64 {
58 var p: i64 = off
59 var n: i64 = v
60 if n < 0 {
61 if p >= cap { return p }
62 dst[p] = 0x2D
63 p = p + 1
64 n = 0 - n
65 }
66 if n == 0 {
67 if p >= cap { return p }
68 dst[p] = 0x30
69 p = p + 1
70 return p
71 }
72 let tmp: *u8 = sys_mmap(32)
73 var k: i64 = 0
74 while n > 0 {
75 tmp[k] = 0x30 + (n - (n / 10) * 10)
76 n = n / 10
77 k = k + 1
78 }
79 var ri: i64 = k - 1
80 while ri >= 0 {
81 if p >= cap { return p }
82 dst[p] = tmp[ri]
83 p = p + 1
84 ri = ri - 1
85 }
86 return p
87}
88
89// Write hex i64 to dst[off..); width = 0 -> minimal, > 0 -> zero-pad.
90// `upper`: 1 for A-F, 0 for a-f.
91func nx_fmt_hex(dst: *u8, off: i64, cap: i64, v: i64,
92 width: i64, upper: i64) -> i64 {
93 let tmp: *u8 = sys_mmap(20)
94 var n: i64 = v
95 var k: i64 = 0
96 if n == 0 { tmp[0] = 0x30; k = 1 }
97 while n > 0 {
98 let nib: i64 = n & 0xF
99 if nib < 10 {
100 tmp[k] = 0x30 + nib
101 } else {
102 if upper == 1 { tmp[k] = 0x41 + (nib - 10) }
103 if upper == 0 { tmp[k] = 0x61 + (nib - 10) }
104 }
105 n = (n >> 4) & 0x0FFFFFFFFFFFFFFF // unsigned shift
106 k = k + 1
107 }
108 // Pad with zeros to `width`.
109 while k < width {
110 tmp[k] = 0x30
111 k = k + 1
112 }
113 var p: i64 = off
114 var ri: i64 = k - 1
115 while ri >= 0 {
116 if p >= cap { return p }
117 dst[p] = tmp[ri]
118 p = p + 1
119 ri = ri - 1
120 }
121 return p
122}
123
124// Write a NUL-terminated string to dst[off..).
125func nx_fmt_bytes(dst: *u8, off: i64, cap: i64, src: *u8) -> i64 {
126 var p: i64 = off
127 var i: i64 = 0
128 while src[i] != 0 {
129 if p >= cap { return p }
130 dst[p] = src[i]
131 p = p + 1
132 i = i + 1
133 }
134 return p
135}
136
137// ---- format engine -----------------------------------------------
138
139// Format `fmt` with args from `argv[0..argc)` into `out[0..cap)`.
140// Returns total bytes written (may be < total formatted if cap was
141// exceeded).
142func nx_fmt_to_buf(out: *u8, cap: i64, fmt: *u8, argv: *i64, argc: i64) -> i64 {
143 var p: i64 = 0
144 var i: i64 = 0
145 var ai: i64 = 0
146 var go: i64 = 1
147 while go == 1 {
148 if fmt[i] == 0 { go = 0 }
149 if go == 1 {
150 if fmt[i] != 0x25 {
151 if p < cap { out[p] = fmt[i]; p = p + 1 }
152 i = i + 1
153 } else {
154 // '%' directive
155 i = i + 1
156 let c: i64 = fmt[i]
157 if c == 0 { go = 0 }
158 if go == 1 {
159 if c == 0x25 { // '%%'
160 if p < cap { out[p] = 0x25; p = p + 1 }
161 }
162 if c == 0x64 { // 'd'
163 if ai < argc { p = nx_fmt_int(out, p, cap, argv[ai]); ai = ai + 1 }
164 }
165 if c == 0x75 { // 'u'
166 if ai < argc { p = nx_fmt_int(out, p, cap, argv[ai]); ai = ai + 1 }
167 }
168 if c == 0x78 { // 'x'
169 if ai < argc { p = nx_fmt_hex(out, p, cap, argv[ai], 0, 0); ai = ai + 1 }
170 }
171 if c == 0x58 { // 'X'
172 if ai < argc { p = nx_fmt_hex(out, p, cap, argv[ai], 0, 1); ai = ai + 1 }
173 }
174 if c == 0x73 { // 's'
175 if ai < argc { p = nx_fmt_bytes(out, p, cap, argv[ai] as *u8); ai = ai + 1 }
176 }
177 if c == 0x63 { // 'c'
178 if ai < argc {
179 if p < cap { out[p] = argv[ai] & 0xFF; p = p + 1 }
180 ai = ai + 1
181 }
182 }
183 if c == 0x70 { // 'p'
184 if ai < argc {
185 if p + 2 <= cap { out[p] = 0x30; out[p+1] = 0x78; p = p + 2 }
186 p = nx_fmt_hex(out, p, cap, argv[ai], 16, 0)
187 ai = ai + 1
188 }
189 }
190 i = i + 1
191 }
192 }
193 }
194 }
195 return p
196}
197
198// Convenience: format directly to fd. Stack-allocated buffer up to
199// 4 KiB; for longer messages call nx_fmt_to_buf with a heap buffer.
200func nx_fmt_to_fd(fd: i64, fmt: *u8, argv: *i64, argc: i64) -> i64 {
201 let buf: *u8 = sys_mmap(K_MAGIC_4096)
202 let n: i64 = nx_fmt_to_buf(buf, K_MAGIC_4096, fmt, argv, argc)
203 sys_write(fd, buf, n)
204 return n
205}
206
207// ---- self-test ----------------------------------------------------
208
209func main() -> i64 {
210 let buf: *u8 = sys_mmap(256)
211 let argv_raw: *u8 = sys_mmap(64)
212 let argv: *i64 = argv_raw as *i64
213
214 // %d
215 argv[0] = 42
216 let n1: i64 = nx_fmt_to_buf(buf, 256, "%d" as *u8, argv, 1)
217 if n1 != 2 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
218 if buf[0] != 0x34 { return __syscall(93, 11, 0, 0, 0, 0, 0) }
219 if buf[1] != 0x32 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
220
221 // %d negative
222 argv[0] = -7
223 let n2: i64 = nx_fmt_to_buf(buf, 256, "%d" as *u8, argv, 1)
224 if n2 != 2 { return __syscall(93, 20, 0, 0, 0, 0, 0) }
225 if buf[0] != 0x2D { return __syscall(93, 21, 0, 0, 0, 0, 0) }
226 if buf[1] != 0x37 { return __syscall(93, 22, 0, 0, 0, 0, 0) }
227
228 // %x
229 argv[0] = 0xCAFE
230 let n3: i64 = nx_fmt_to_buf(buf, 256, "%x" as *u8, argv, 1)
231 if n3 != 4 { return __syscall(93, 30, 0, 0, 0, 0, 0) }
232 if buf[0] != 0x63 { return __syscall(93, 31, 0, 0, 0, 0, 0) } // 'c'
233 if buf[3] != 0x65 { return __syscall(93, 32, 0, 0, 0, 0, 0) } // 'e'
234
235 // %X
236 argv[0] = 0xCAFE
237 let n4: i64 = nx_fmt_to_buf(buf, 256, "%X" as *u8, argv, 1)
238 if buf[0] != 0x43 { return __syscall(93, 40, 0, 0, 0, 0, 0) } // 'C'
239
240 // %s
241 let str: *u8 = sys_mmap(8)
242 str[0] = 0x68; str[1] = 0x69; str[2] = 0 // "hi"
243 argv[0] = str as i64
244 let n5: i64 = nx_fmt_to_buf(buf, 256, "%s" as *u8, argv, 1)
245 if n5 != 2 { return __syscall(93, 50, 0, 0, 0, 0, 0) }
246 if buf[0] != 0x68 { return __syscall(93, 51, 0, 0, 0, 0, 0) }
247
248 // %c
249 argv[0] = 0x41 // 'A'
250 let n6: i64 = nx_fmt_to_buf(buf, 256, "%c" as *u8, argv, 1)
251 if n6 != 1 { return __syscall(93, 60, 0, 0, 0, 0, 0) }
252 if buf[0] != 0x41 { return __syscall(93, 61, 0, 0, 0, 0, 0) }
253
254 // Mixed: "x=%d s=%s"
255 argv[0] = 99
256 argv[1] = str as i64
257 let n7: i64 = nx_fmt_to_buf(buf, 256, "x=%d s=%s" as *u8, argv, 2)
258 if n7 != 9 { return __syscall(93, 70, 0, 0, 0, 0, 0) }
259 if buf[0] != 0x78 { return __syscall(93, 71, 0, 0, 0, 0, 0) } // 'x'
260 if buf[1] != 0x3D { return __syscall(93, 72, 0, 0, 0, 0, 0) } // '='
261 if buf[2] != 0x39 { return __syscall(93, 73, 0, 0, 0, 0, 0) } // '9'
262 if buf[7] != 0x68 { return __syscall(93, 74, 0, 0, 0, 0, 0) } // 'h'
263
264 // Literal %%
265 let n8: i64 = nx_fmt_to_buf(buf, 256, "%%" as *u8, argv, 0)
266 if n8 != 1 { return __syscall(93, 80, 0, 0, 0, 0, 0) }
267 if buf[0] != 0x25 { return __syscall(93, 81, 0, 0, 0, 0, 0) }
268
269 return 0
270}