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}