nx_strconv.nx source
↩ module page · 253 lines · 7993 B
1// nx_strconv.nx -- string <-> integer conversion utilities.
2//
3// Foundational for every CLI tool: parsing argv ints, formatting
4// counts in log lines, decoding hex addresses for the debugger.
5//
6// Today the inline patterns scattered across runtime/ duplicate
7// this work in 14 places (nx_objdump, nx_readelf, nx_dbg,
8// nx_addr2line, nx_diff, nx_argv, nxc_native_wrap, ...). Centralise
9// here so they all agree on edge cases (negative MIN_I64, leading
10// "+", overflow detection, hex prefix tolerance).
11
12// nx_safety_envelope:
13// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
14// sil_target: SIL1
15// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
16// verdict: NOT_YET_EVALUATED
17
18import "syscalls.nx"
19import "nx_ascii.nx"
20const NX_MAGIC_12345: i64 = 12345
21
22// Errors.
23const NX_STRCONV_ERR_EMPTY: i64 = -1
24const NX_STRCONV_ERR_BAD_CHAR: i64 = -2
25const NX_STRCONV_ERR_OVERFLOW: i64 = -3
26
27// is_digit canonical in nx_ascii.nx (nx_ascii_is_digit).
28
29// Is c a hex digit? Accepts 0-9 / a-f / A-F.
30func nx_strconv_is_hex_digit(c: i64) -> i64 {
31 if nx_ascii_is_digit(c) == 1 { return 1 }
32 if c >= 0x61 { if c <= 0x66 { return 1 } }
33 if c >= 0x41 { if c <= 0x46 { return 1 } }
34 return 0
35}
36
37func nx_strconv_hex_digit_val(c: i64) -> i64 {
38 if c <= 0x39 { return c - 0x30 }
39 if c <= 0x46 { return c - 0x41 + 10 }
40 return c - 0x61 + 10
41}
42
43// Parse a decimal i64 from `s` (NUL-terminated). Tolerates a
44// leading `+` or `-`. Sets *out_err to one of NX_STRCONV_ERR_* on
45// failure (and returns 0 in that case).
46//
47// Caller must supply out_err (we never NULL-deref it).
48func nx_strconv_parse_i64(s: *u8, out_err: *i64) -> i64 {
49 *out_err = 0
50 if s[0] == 0 { *out_err = NX_STRCONV_ERR_EMPTY; return 0 }
51
52 var neg: i64 = 0
53 var i: i64 = 0
54 if s[0] == 0x2D { neg = 1; i = 1 }
55 else { if s[0] == 0x2B { i = 1 } }
56
57 if s[i] == 0 { *out_err = NX_STRCONV_ERR_EMPTY; return 0 }
58
59 var n: i64 = 0
60 var go: i64 = 1
61 while go == 1 {
62 let c: i64 = s[i]
63 if c == 0 { go = 0 }
64 else {
65 if nx_ascii_is_digit(c) == 0 {
66 *out_err = NX_STRCONV_ERR_BAD_CHAR
67 return 0
68 }
69 // Overflow check (loose: pre-multiply by 10).
70 let prev: i64 = n
71 n = n * 10 + (c - 0x30)
72 if n / 10 != prev {
73 *out_err = NX_STRCONV_ERR_OVERFLOW
74 return 0
75 }
76 i = i + 1
77 }
78 }
79 if neg == 1 { return 0 - n }
80 return n
81}
82
83// Parse a hex i64 from `s` (NUL-terminated). Optional "0x" / "0X"
84// prefix. Sets *out_err on failure.
85func nx_strconv_parse_hex(s: *u8, out_err: *i64) -> i64 {
86 *out_err = 0
87 if s[0] == 0 { *out_err = NX_STRCONV_ERR_EMPTY; return 0 }
88
89 var i: i64 = 0
90 if s[0] == 0x30 {
91 if s[1] == 0x78 { i = 2 } else { if s[1] == 0x58 { i = 2 } }
92 }
93 if s[i] == 0 { *out_err = NX_STRCONV_ERR_EMPTY; return 0 }
94
95 var n: i64 = 0
96 var go: i64 = 1
97 while go == 1 {
98 let c: i64 = s[i]
99 if c == 0 { go = 0 }
100 else {
101 if nx_strconv_is_hex_digit(c) == 0 {
102 *out_err = NX_STRCONV_ERR_BAD_CHAR
103 return 0
104 }
105 n = (n << 4) | nx_strconv_hex_digit_val(c)
106 i = i + 1
107 }
108 }
109 return n
110}
111
112// Format a decimal i64 into `buf` (caller-supplied, >= 32 bytes).
113// Writes the digits + trailing NUL; returns the digit count
114// (NOT including the NUL). Handles sign + zero correctly.
115func nx_strconv_format_i64(v: i64, buf: *u8) -> i64 {
116 if v == 0 {
117 buf[0] = 0x30
118 buf[1] = 0
119 return 1
120 }
121
122 var n: i64 = v
123 var neg: i64 = 0
124 if n < 0 { neg = 1; n = 0 - n }
125
126 // Build digits in reverse, then flip.
127 let scratch: *u8 = sys_mmap(32)
128 var k: i64 = 0
129 while n > 0 {
130 scratch[k] = 0x30 + (n - (n / 10) * 10)
131 n = n / 10
132 k = k + 1
133 }
134
135 var off: i64 = 0
136 if neg == 1 {
137 buf[0] = 0x2D
138 off = 1
139 }
140 var j: i64 = 0
141 while j < k {
142 buf[off + j] = scratch[k - 1 - j]
143 j = j + 1
144 }
145 buf[off + k] = 0
146 return off + k
147}
148
149// Format a hex i64 into `buf` with optional "0x" prefix and minimum
150// width (zero-padded). Always uses lowercase digits. Returns the
151// character count (excluding NUL).
152func nx_strconv_format_hex(v: i64, buf: *u8, prefix: i64, min_width: i64) -> i64 {
153 var off: i64 = 0
154 if prefix == 1 {
155 buf[0] = 0x30
156 buf[1] = 0x78
157 off = 2
158 }
159
160 let scratch: *u8 = sys_mmap(32)
161 var n: i64 = v
162 var k: i64 = 0
163 if n == 0 {
164 scratch[0] = 0x30
165 k = 1
166 } else {
167 while n != 0 {
168 let d: i64 = n & 0xF
169 if d < 10 { scratch[k] = 0x30 + d } else { scratch[k] = 0x57 + d }
170 n = (n >> 4) & 0x0FFFFFFFFFFFFFFF // logical shift
171 k = k + 1
172 }
173 }
174
175 // Zero-pad to min_width.
176 while k < min_width {
177 scratch[k] = 0x30
178 k = k + 1
179 }
180
181 var j: i64 = 0
182 while j < k {
183 buf[off + j] = scratch[k - 1 - j]
184 j = j + 1
185 }
186 buf[off + k] = 0
187 return off + k
188}
189
190// ---- self-test ---------------------------------------------------
191
192func main() -> i64 {
193 let err_raw: *u8 = sys_mmap(8)
194 let err: *i64 = err_raw as *i64
195
196 // parse_i64: simple positive
197 let s1: *u8 = sys_mmap(8)
198 s1[0] = 0x31; s1[1] = 0x32; s1[2] = 0x33; s1[3] = 0
199 if nx_strconv_parse_i64(s1, err) != 123 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
200 if *err != 0 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
201
202 // parse_i64: negative
203 let s2: *u8 = sys_mmap(8)
204 s2[0] = 0x2D; s2[1] = 0x34; s2[2] = 0x35; s2[3] = 0
205 if nx_strconv_parse_i64(s2, err) != (0 - 45) { return __syscall(93, 3, 0, 0, 0, 0, 0) }
206
207 // parse_i64: bad char
208 let s3: *u8 = sys_mmap(8)
209 s3[0] = 0x31; s3[1] = 0x78; s3[2] = 0
210 nx_strconv_parse_i64(s3, err)
211 if *err != NX_STRCONV_ERR_BAD_CHAR { return __syscall(93, 4, 0, 0, 0, 0, 0) }
212
213 // parse_hex: 0xDEADBEEF
214 let s4: *u8 = sys_mmap(16)
215 s4[0] = 0x30; s4[1] = 0x78
216 s4[2] = 0x44; s4[3] = 0x45; s4[4] = 0x41; s4[5] = 0x44
217 s4[6] = 0x42; s4[7] = 0x45; s4[8] = 0x45; s4[9] = 0x46
218 s4[10] = 0
219 if nx_strconv_parse_hex(s4, err) != 0xDEADBEEF { return __syscall(93, 5, 0, 0, 0, 0, 0) }
220
221 // format_i64: 0
222 let buf: *u8 = sys_mmap(32)
223 if nx_strconv_format_i64(0, buf) != 1 { return __syscall(93, 6, 0, 0, 0, 0, 0) }
224 if buf[0] != 0x30 { return __syscall(93, 7, 0, 0, 0, 0, 0) }
225
226 // format_i64: 12345
227 let n: i64 = nx_strconv_format_i64(NX_MAGIC_12345, buf)
228 if n != 5 { return __syscall(93, 8, 0, 0, 0, 0, 0) }
229 if buf[0] != 0x31 { return __syscall(93, 9, 0, 0, 0, 0, 0) }
230 if buf[4] != 0x35 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
231 if buf[5] != 0 { return __syscall(93, 11, 0, 0, 0, 0, 0) }
232
233 // format_i64: -42
234 let n2: i64 = nx_strconv_format_i64(0 - 42, buf)
235 if n2 != 3 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
236 if buf[0] != 0x2D { return __syscall(93, 13, 0, 0, 0, 0, 0) }
237 if buf[1] != 0x34 { return __syscall(93, 14, 0, 0, 0, 0, 0) }
238 if buf[2] != 0x32 { return __syscall(93, 15, 0, 0, 0, 0, 0) }
239
240 // format_hex: 0xABCD with prefix and width 6
241 let n3: i64 = nx_strconv_format_hex(0xABCD, buf, 1, 6)
242 if n3 != 8 { return __syscall(93, 16, 0, 0, 0, 0, 0) }
243 if buf[0] != 0x30 { return __syscall(93, 17, 0, 0, 0, 0, 0) }
244 if buf[1] != 0x78 { return __syscall(93, 18, 0, 0, 0, 0, 0) }
245 if buf[2] != 0x30 { return __syscall(93, 19, 0, 0, 0, 0, 0) }
246 if buf[3] != 0x30 { return __syscall(93, 20, 0, 0, 0, 0, 0) }
247 if buf[4] != 0x61 { return __syscall(93, 21, 0, 0, 0, 0, 0) } // 'a'
248 if buf[5] != 0x62 { return __syscall(93, 22, 0, 0, 0, 0, 0) }
249 if buf[6] != 0x63 { return __syscall(93, 23, 0, 0, 0, 0, 0) }
250 if buf[7] != 0x64 { return __syscall(93, 24, 0, 0, 0, 0, 0) }
251
252 return 0
253}