nx_glob.nx source
↩ module page · 221 lines · 8109 B
1// nx_glob.nx -- POSIX-style glob pattern matching.
2//
3// Used by toolchain CLI wrappers that take "*.nx", "tests/*.s",
4// "runtime/nx_dwarf_*.nx" arguments. Today each tool that wants
5// glob support either calls out to a shell or implements its own
6// matcher. This module provides a single canonical matcher.
7//
8// Supported syntax (POSIX subset):
9// * match zero or more chars (NEVER crosses '/')
10// ? match exactly one char
11// [abc] character class
12// [a-z] character range
13// [^abc] negated class
14// \\x literal escape
15//
16// Out of scope:
17// ** (recursive wildcard)
18// {a,b,c} brace expansion
19// ~ home-dir expansion
20//
21// Matcher is recursive (textbook NFA-style); for the pattern lengths
22// real CLIs use (< 100 chars) the depth is fine.
23
24// nx_safety_envelope:
25// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
26// sil_target: SIL1
27// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
28// verdict: NOT_YET_EVALUATED
29
30import "syscalls.nx"
31
32// Match a single character against a `[...]` class starting at
33// pat[i] (which is `[`). Returns the new pat index AFTER the
34// closing `]`, with `*matched_out` set to 1 if the class matched.
35// Returns -1 on malformed class (no closing `]`).
36func nx_glob_match_class(pat: *u8, i: i64, ch: i64, matched_out: *i64) -> i64 {
37 var k: i64 = i + 1
38 var negate: i64 = 0
39 if pat[k] == 0x5E { negate = 1; k = k + 1 } // '^'
40 var matched: i64 = 0
41 var done: i64 = 0
42 while done == 0 {
43 let c: i64 = pat[k]
44 if c == 0 { return -1 }
45 if c == 0x5D { done = 1 }
46 else {
47 // Range "x-y"?
48 if pat[k + 1] == 0x2D {
49 if pat[k + 2] != 0 {
50 if pat[k + 2] != 0x5D {
51 if ch >= c {
52 if ch <= pat[k + 2] { matched = 1 }
53 }
54 k = k + 3
55 } else {
56 if c == ch { matched = 1 }
57 k = k + 1
58 }
59 } else {
60 if c == ch { matched = 1 }
61 k = k + 1
62 }
63 } else {
64 if c == ch { matched = 1 }
65 k = k + 1
66 }
67 }
68 }
69 if negate == 1 {
70 if matched == 1 { *matched_out = 0 }
71 else { *matched_out = 1 }
72 } else {
73 *matched_out = matched
74 }
75 return k + 1
76}
77
78// Match `text` (NUL-terminated) against `pat` (NUL-terminated).
79// Returns 1 on full match, 0 otherwise.
80func nx_glob_match(pat: *u8, text: *u8) -> i64 {
81 var p: i64 = 0
82 var t: i64 = 0
83 var done: i64 = 0
84 var result: i64 = 0
85 while done == 0 {
86 let pc: i64 = pat[p]
87 let tc: i64 = text[t]
88 if pc == 0 {
89 if tc == 0 { result = 1 }
90 done = 1
91 } else {
92 if pc == 0x2A {
93 // '*' -- backtrack with successive prefixes. '*' must
94 // not cross '/'. Try each suffix of text until one
95 // matches the rest of pat.
96 let rest: *u8 = (((pat as i64) + p + 1) as *u8)
97 var k: i64 = t
98 var inner_done: i64 = 0
99 while inner_done == 0 {
100 let tail: *u8 = (((text as i64) + k) as *u8)
101 if nx_glob_match(rest, tail) == 1 {
102 result = 1
103 inner_done = 1
104 done = 1
105 } else {
106 if text[k] == 0 { inner_done = 1; done = 1 }
107 else {
108 if text[k] == 0x2F { inner_done = 1; done = 1 }
109 else { k = k + 1 }
110 }
111 }
112 }
113 } else {
114 if tc == 0 {
115 done = 1
116 } else {
117 if pc == 0x3F {
118 // '?' matches any single char (but not /).
119 if tc == 0x2F { done = 1 }
120 else { p = p + 1; t = t + 1 }
121 } else {
122 if pc == 0x5C {
123 // Escape: pat[p+1] is literal.
124 if pat[p + 1] == 0 { done = 1 }
125 else {
126 if pat[p + 1] != tc { done = 1 }
127 else { p = p + 2; t = t + 1 }
128 }
129 } else {
130 if pc == 0x5B {
131 let m_raw: *u8 = sys_mmap(8)
132 let m: *i64 = m_raw as *i64
133 let new_p: i64 = nx_glob_match_class(pat, p, tc, m)
134 if new_p < 0 { done = 1 }
135 else {
136 if *m == 1 { p = new_p; t = t + 1 }
137 else { done = 1 }
138 }
139 } else {
140 if pc != tc { done = 1 }
141 else { p = p + 1; t = t + 1 }
142 }
143 }
144 }
145 }
146 }
147 }
148 }
149 return result
150}
151
152// ---- self-test ---------------------------------------------------
153
154func main() -> i64 {
155 let p1: *u8 = sys_mmap(16)
156 let t1: *u8 = sys_mmap(32)
157
158 // Plain literal
159 p1[0] = 0x66; p1[1] = 0x6F; p1[2] = 0x6F; p1[3] = 0
160 t1[0] = 0x66; t1[1] = 0x6F; t1[2] = 0x6F; t1[3] = 0
161 if nx_glob_match(p1, t1) != 1 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
162
163 // Mismatch
164 t1[2] = 0x78
165 if nx_glob_match(p1, t1) != 0 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
166
167 // * -- zero-or-more
168 let p2: *u8 = sys_mmap(8)
169 p2[0] = 0x66; p2[1] = 0x2A; p2[2] = 0
170 let t2: *u8 = sys_mmap(8)
171 t2[0] = 0x66; t2[1] = 0x6F; t2[2] = 0x6F; t2[3] = 0
172 if nx_glob_match(p2, t2) != 1 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
173
174 // *.nx
175 let p3: *u8 = sys_mmap(8)
176 p3[0] = 0x2A; p3[1] = 0x2E; p3[2] = 0x6E; p3[3] = 0x78; p3[4] = 0
177 let t3: *u8 = sys_mmap(16)
178 t3[0] = 0x6E; t3[1] = 0x78; t3[2] = 0x5F; t3[3] = 0x68; t3[4] = 0x61
179 t3[5] = 0x73; t3[6] = 0x68; t3[7] = 0x2E; t3[8] = 0x6E; t3[9] = 0x78; t3[10] = 0
180 if nx_glob_match(p3, t3) != 1 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
181
182 // ? matches one char
183 let p4: *u8 = sys_mmap(8)
184 p4[0] = 0x66; p4[1] = 0x3F; p4[2] = 0x6F; p4[3] = 0
185 let t4: *u8 = sys_mmap(8)
186 t4[0] = 0x66; t4[1] = 0x6F; t4[2] = 0x6F; t4[3] = 0
187 if nx_glob_match(p4, t4) != 1 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
188
189 // ? does NOT match zero chars
190 let t4b: *u8 = sys_mmap(8)
191 t4b[0] = 0x66; t4b[1] = 0x6F; t4b[2] = 0
192 if nx_glob_match(p4, t4b) != 0 { return __syscall(93, 6, 0, 0, 0, 0, 0) }
193
194 // [abc] class
195 let p5: *u8 = sys_mmap(8)
196 p5[0] = 0x5B; p5[1] = 0x61; p5[2] = 0x62; p5[3] = 0x63; p5[4] = 0x5D; p5[5] = 0
197 let t5: *u8 = sys_mmap(8)
198 t5[0] = 0x62; t5[1] = 0
199 if nx_glob_match(p5, t5) != 1 { return __syscall(93, 7, 0, 0, 0, 0, 0) }
200 t5[0] = 0x64
201 if nx_glob_match(p5, t5) != 0 { return __syscall(93, 8, 0, 0, 0, 0, 0) }
202
203 // [a-z] range
204 let p6: *u8 = sys_mmap(8)
205 p6[0] = 0x5B; p6[1] = 0x61; p6[2] = 0x2D; p6[3] = 0x7A; p6[4] = 0x5D; p6[5] = 0
206 let t6: *u8 = sys_mmap(8)
207 t6[0] = 0x6A; t6[1] = 0
208 if nx_glob_match(p6, t6) != 1 { return __syscall(93, 9, 0, 0, 0, 0, 0) }
209 t6[0] = 0x41
210 if nx_glob_match(p6, t6) != 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
211
212 // * does not cross '/'
213 let p7: *u8 = sys_mmap(8)
214 p7[0] = 0x2A; p7[1] = 0x2E; p7[2] = 0x6E; p7[3] = 0x78; p7[4] = 0
215 let t7: *u8 = sys_mmap(16)
216 t7[0] = 0x64; t7[1] = 0x69; t7[2] = 0x72; t7[3] = 0x2F
217 t7[4] = 0x66; t7[5] = 0x2E; t7[6] = 0x6E; t7[7] = 0x78; t7[8] = 0
218 if nx_glob_match(p7, t7) != 0 { return __syscall(93, 11, 0, 0, 0, 0, 0) }
219
220 return 0
221}