code wiki / (root) / nx_strconv.nx

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}