code wiki / (root) / nx_args.nx

nx_args.nx source

↩ module page · 308 lines · 11971 B

1// nx_args.nx -- getopt-style argument parser. 2// 3// Toolchain front-ends today inline ad-hoc argv scanning. Adding 4// a new flag means editing every tool's parser independently and 5// re-deriving the same edge-case logic for each one (long flags, 6// `--`, bundled short flags, `=`-attached values). 7// 8// This module gives them a shared parser: 9// 10// spec = [ 11// {short: 'o', long: "output", takes_value: 1}, 12// {short: 'O', long: "opt", takes_value: 0}, 13// {short: 'h', long: "help", takes_value: 0}, 14// ] 15// it = nx_args_iter_new(argc, argv, spec, n_spec) 16// while nx_args_next(it) > 0 { 17// if it.flag == 'o' { out_path = it.value } 18// if it.flag == 'O' { opt_on = 1 } 19// } 20// positional = it.positionals (i64 array of indices into argv) 21// 22// Conventions: 23// - "--" terminates options; everything after is positional. 24// - `--long=val` and `--long val` both work for value-taking longs. 25// - `-ovalue` and `-o value` both work for value-taking shorts. 26// - Bundled shorts (`-Ox` = `-O -x`) supported only for non-value flags. 27// - Unknown flags return -1 from next(); caller decides whether to 28// warn or error. 29 30// nx_safety_envelope: 31// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 32// sil_target: SIL1 33// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 34// verdict: NOT_YET_EVALUATED 35 36import "syscalls.nx" 37 38const NX_ARGS_MAX_SPEC: i64 = 64 39const NX_ARGS_MAX_POSITIONAL: i64 = 64 40 41struct NxArgsSpec { 42 short_flag: i64, // ASCII char; 0 means no short form 43 long_flag: *u8, // NUL-terminated; NULL means no long form 44 takes_value: i64, // 0 = bool, 1 = string-valued 45} 46 47const NX_ARGS_SPEC_BYTES: i64 = 24 48 49struct NxArgsIter { 50 argc: i64, 51 argv: *i64, // pointer-to-pointer; argv[i] is *u8 52 spec: *NxArgsSpec, 53 n_spec: i64, 54 cursor: i64, // current argv index (1..argc) 55 bundle_pos: i64, // position within a bundled short (-Oxy -> 1, 2) 56 after_dash_dash: i64, 57 flag: i64, // ASCII of last short flag returned (or 0) 58 value: *u8, // NUL-terminated value or NULL 59 positionals: *i64, 60 n_positionals: i64, 61} 62 63const NX_ARGS_ITER_BYTES: i64 = 88 64 65// strlen / streq canonical in runtime.nx. 66// strneq covers the (a, b, b_len) prefix-compare case (was nx_args_streq_n). 67 68func nx_args_iter_new(argc: i64, argv: *i64, spec: *NxArgsSpec, n_spec: i64) -> *NxArgsIter { 69 let raw: *u8 = sys_mmap(NX_ARGS_ITER_BYTES) 70 let it: *NxArgsIter = raw as *NxArgsIter 71 it.argc = argc 72 it.argv = argv 73 it.spec = spec 74 it.n_spec = n_spec 75 it.cursor = 1 // skip argv[0] 76 it.bundle_pos = 0 77 it.after_dash_dash = 0 78 it.flag = 0 79 it.value = 0 as *u8 80 let p_raw: *u8 = sys_mmap(NX_ARGS_MAX_POSITIONAL * 8) 81 it.positionals = p_raw as *i64 82 it.n_positionals = 0 83 return it 84} 85 86// Find a spec by short flag. Returns index or -1. 87func nx_args_find_short(it: *NxArgsIter, ch: i64) -> i64 { 88 var i: i64 = 0 89 while i < it.n_spec { 90 let s: *NxArgsSpec = (((it.spec as i64) + i * NX_ARGS_SPEC_BYTES) as *NxArgsSpec) 91 if s.short_flag == ch { return i } 92 i = i + 1 93 } 94 return -1 95} 96 97// Find a spec by long flag (NUL-terminated). Returns index or -1. 98func nx_args_find_long(it: *NxArgsIter, name: *u8, name_len: i64) -> i64 { 99 var i: i64 = 0 100 while i < it.n_spec { 101 let s: *NxArgsSpec = (((it.spec as i64) + i * NX_ARGS_SPEC_BYTES) as *NxArgsSpec) 102 if s.long_flag != (0 as *u8) { 103 if strlen(s.long_flag) == name_len { 104 if strneq(s.long_flag, name, name_len) == 1 { 105 return i 106 } 107 } 108 } 109 i = i + 1 110 } 111 return -1 112} 113 114// Add a positional. 115func nx_args_push_positional(it: *NxArgsIter, idx: i64) -> i64 { 116 if it.n_positionals >= NX_ARGS_MAX_POSITIONAL { return -1 } 117 it.positionals[it.n_positionals] = idx 118 it.n_positionals = it.n_positionals + 1 119 return 0 120} 121 122// Advance one step. Returns: 123// 1 = a flag was matched (it.flag, it.value populated) 124// 0 = end of args 125// -1 = unknown flag (it.flag set to the offending char, value 0) 126// -2 = flag requires a value but none provided 127func nx_args_next(it: *NxArgsIter) -> i64 { 128 // Continue an in-progress bundle of short flags first. 129 if it.bundle_pos > 0 { 130 let s_arg: *u8 = (it.argv[it.cursor]) as *u8 131 let ch: i64 = s_arg[it.bundle_pos] 132 if ch == 0 { 133 it.bundle_pos = 0 134 it.cursor = it.cursor + 1 135 } else { 136 let ix: i64 = nx_args_find_short(it, ch) 137 if ix < 0 { 138 it.flag = ch 139 it.value = 0 as *u8 140 it.bundle_pos = it.bundle_pos + 1 141 return -1 142 } 143 let sp: *NxArgsSpec = (((it.spec as i64) + ix * NX_ARGS_SPEC_BYTES) as *NxArgsSpec) 144 if sp.takes_value == 1 { 145 // Value follows immediately or in next argv. 146 if s_arg[it.bundle_pos + 1] != 0 { 147 it.flag = ch 148 it.value = (((s_arg as i64) + it.bundle_pos + 1) as *u8) 149 it.bundle_pos = 0 150 it.cursor = it.cursor + 1 151 return 1 152 } 153 it.cursor = it.cursor + 1 154 if it.cursor >= it.argc { 155 it.flag = ch 156 it.value = 0 as *u8 157 return -2 158 } 159 it.flag = ch 160 it.value = (it.argv[it.cursor]) as *u8 161 it.bundle_pos = 0 162 it.cursor = it.cursor + 1 163 return 1 164 } 165 it.flag = ch 166 it.value = 0 as *u8 167 it.bundle_pos = it.bundle_pos + 1 168 return 1 169 } 170 } 171 172 while it.cursor < it.argc { 173 let arg: *u8 = (it.argv[it.cursor]) as *u8 174 if it.after_dash_dash == 1 { 175 nx_args_push_positional(it, it.cursor) 176 it.cursor = it.cursor + 1 177 } else { 178 if arg[0] != 0x2D { 179 // Positional. 180 nx_args_push_positional(it, it.cursor) 181 it.cursor = it.cursor + 1 182 } else { 183 if arg[1] == 0 { 184 // Bare "-" is a positional. 185 nx_args_push_positional(it, it.cursor) 186 it.cursor = it.cursor + 1 187 } else { 188 if arg[1] == 0x2D { 189 if arg[2] == 0 { 190 // "--" terminator. 191 it.after_dash_dash = 1 192 it.cursor = it.cursor + 1 193 } else { 194 // Long flag. 195 // Find the '=' if any. 196 var eq: i64 = 2 197 while arg[eq] != 0 { 198 if arg[eq] == 0x3D { eq = (0 - eq) - 1; var e: i64 = 0 ; while e < 0 { e = e + 1 } } 199 else { eq = eq + 1 } 200 } 201 // Re-scan because the inline marker pattern was clumsy. 202 var name_len: i64 = 0 203 var has_eq: i64 = 0 204 var probe: i64 = 2 205 while arg[probe] != 0 { 206 if arg[probe] == 0x3D { has_eq = 1; name_len = probe - 2; probe = probe + 1; while arg[probe] != 0 { probe = probe + 1 } } 207 else { probe = probe + 1; if has_eq == 0 { name_len = probe - 2 } } 208 } 209 let name: *u8 = (((arg as i64) + 2) as *u8) 210 let ix2: i64 = nx_args_find_long(it, name, name_len) 211 if ix2 < 0 { 212 it.flag = 0 213 it.value = arg 214 it.cursor = it.cursor + 1 215 return -1 216 } 217 let sp2: *NxArgsSpec = (((it.spec as i64) + ix2 * NX_ARGS_SPEC_BYTES) as *NxArgsSpec) 218 it.flag = sp2.short_flag 219 if sp2.takes_value == 1 { 220 if has_eq == 1 { 221 it.value = (((arg as i64) + 2 + name_len + 1) as *u8) 222 it.cursor = it.cursor + 1 223 return 1 224 } 225 it.cursor = it.cursor + 1 226 if it.cursor >= it.argc { 227 it.value = 0 as *u8 228 return -2 229 } 230 it.value = (it.argv[it.cursor]) as *u8 231 it.cursor = it.cursor + 1 232 return 1 233 } 234 it.value = 0 as *u8 235 it.cursor = it.cursor + 1 236 return 1 237 } 238 } else { 239 // Short flag(s) -- start at position 1 of arg. 240 it.bundle_pos = 1 241 return nx_args_next(it) 242 } 243 } 244 } 245 } 246 } 247 return 0 248} 249 250// ---- self-test --------------------------------------------------- 251 252func main() -> i64 { 253 // Build a synthetic argv: ["prog", "-O", "--output=foo.s", "in.nx"] 254 let argv_raw: *u8 = sys_mmap(64) 255 let argv: *i64 = argv_raw as *i64 256 let s0: *u8 = sys_mmap(8) 257 s0[0] = 0x70; s0[1] = 0x72; s0[2] = 0x6F; s0[3] = 0x67; s0[4] = 0 258 let s1: *u8 = sys_mmap(8) 259 s1[0] = 0x2D; s1[1] = 0x4F; s1[2] = 0 260 let s2: *u8 = sys_mmap(32) 261 s2[0] = 0x2D; s2[1] = 0x2D; s2[2] = 0x6F; s2[3] = 0x75; s2[4] = 0x74 262 s2[5] = 0x70; s2[6] = 0x75; s2[7] = 0x74; s2[8] = 0x3D 263 s2[9] = 0x66; s2[10] = 0x6F; s2[11] = 0x6F; s2[12] = 0x2E 264 s2[13] = 0x73; s2[14] = 0 265 let s3: *u8 = sys_mmap(8) 266 s3[0] = 0x69; s3[1] = 0x6E; s3[2] = 0x2E; s3[3] = 0x6E 267 s3[4] = 0x78; s3[5] = 0 268 argv[0] = s0 as i64 269 argv[1] = s1 as i64 270 argv[2] = s2 as i64 271 argv[3] = s3 as i64 272 273 // spec: -O bool, -o/--output string 274 let spec_raw: *u8 = sys_mmap(NX_ARGS_MAX_SPEC * NX_ARGS_SPEC_BYTES) 275 let spec: *NxArgsSpec = spec_raw as *NxArgsSpec 276 let s0p: *NxArgsSpec = (((spec as i64) + 0) as *NxArgsSpec) 277 s0p.short_flag = 0x4F // 'O' 278 s0p.long_flag = 0 as *u8 279 s0p.takes_value = 0 280 let long_out: *u8 = sys_mmap(16) 281 long_out[0] = 0x6F; long_out[1] = 0x75; long_out[2] = 0x74 282 long_out[3] = 0x70; long_out[4] = 0x75; long_out[5] = 0x74 283 long_out[6] = 0 284 let s1p: *NxArgsSpec = (((spec as i64) + NX_ARGS_SPEC_BYTES) as *NxArgsSpec) 285 s1p.short_flag = 0x6F // 'o' 286 s1p.long_flag = long_out 287 s1p.takes_value = 1 288 289 let it: *NxArgsIter = nx_args_iter_new(4, argv, spec, 2) 290 291 let r1: i64 = nx_args_next(it) 292 if r1 != 1 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 293 if it.flag != 0x4F { return __syscall(93, 2, 0, 0, 0, 0, 0) } 294 295 let r2: i64 = nx_args_next(it) 296 if r2 != 1 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 297 if it.flag != 0x6F { return __syscall(93, 4, 0, 0, 0, 0, 0) } 298 if it.value[0] != 0x66 { return __syscall(93, 5, 0, 0, 0, 0, 0) } // 'f' 299 if it.value[2] != 0x6F { return __syscall(93, 6, 0, 0, 0, 0, 0) } 300 if it.value[5] != 0 { return __syscall(93, 7, 0, 0, 0, 0, 0) } 301 302 let r3: i64 = nx_args_next(it) 303 if r3 != 0 { return __syscall(93, 8, 0, 0, 0, 0, 0) } // exhausted 304 if it.n_positionals != 1 { return __syscall(93, 9, 0, 0, 0, 0, 0) } 305 if it.positionals[0] != 3 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 306 307 return 0 308}