code wiki / (root) / nx_strfmt.nx

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}