nx_path.nx source
↩ module page · 234 lines · 7646 B
1// nx_path.nx -- POSIX path utilities (basename, dirname, join, normalize, ext).
2//
3// The toolchain front-ends (nx_toolchain, nxc_native_wrap, nxld,
4// nxasm) all need to manipulate paths: derive output filenames
5// from input filenames (foo.nx -> foo.s -> foo.o -> foo.elf), strip
6// directory prefixes for log lines, build search paths.
7//
8// Today each tool inlines its own ad-hoc string scanning. This
9// module centralises the operations under a clean API.
10//
11// Conventions match POSIX (forward slashes, "." and ".." segments).
12// Windows backslash paths are out of scope -- nxc2 normalises them
13// at the input layer.
14
15// nx_safety_envelope:
16// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
17// sil_target: SIL1
18// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
19// verdict: NOT_YET_EVALUATED
20
21import "syscalls.nx"
22import "runtime.nx"
23
24// strlen / streq canonical in runtime.nx.
25
26// Find the offset of the LAST '/' in `s`, or -1 if none.
27func nx_path_last_slash(s: *u8) -> i64 {
28 let n: i64 = strlen(s)
29 var i: i64 = n - 1
30 while i >= 0 {
31 if s[i] == 0x2F { return i }
32 i = i - 1
33 }
34 return -1
35}
36
37// Find the offset of the LAST '.' in `s` (excluding leading-dot
38// hidden-files like ".bashrc"). Returns -1 if no extension.
39// Searches only after the last slash so "foo.bar/baz" returns -1.
40func nx_path_last_dot(s: *u8) -> i64 {
41 let n: i64 = strlen(s)
42 let slash: i64 = nx_path_last_slash(s)
43 let start: i64 = slash + 1
44 if start >= n { return -1 }
45 if s[start] == 0x2E {
46 // Either ".bashrc" (no ext) or "..foo" -- look from start+1
47 if start + 1 >= n { return -1 }
48 }
49 var i: i64 = n - 1
50 while i > start {
51 if s[i] == 0x2E { return i }
52 i = i - 1
53 }
54 return -1
55}
56
57// Copy `n` bytes from `src` to `dst`, then write a trailing NUL.
58// Returns the destination pointer.
59func nx_path_copy_z(dst: *u8, src: *u8, n: i64) -> *u8 {
60 var i: i64 = 0
61 while i < n {
62 dst[i] = src[i]
63 i = i + 1
64 }
65 dst[n] = 0
66 return dst
67}
68
69// Return basename: strip everything up to and including the last slash.
70// "foo/bar/baz.nx" -> "baz.nx"
71// "baz.nx" -> "baz.nx"
72// "foo/bar/" -> "" (empty, trailing slash)
73//
74// Caller-supplied output buffer must be at least strlen(p)+1 bytes.
75func nx_path_basename(p: *u8, out: *u8) -> *u8 {
76 let n: i64 = strlen(p)
77 let slash: i64 = nx_path_last_slash(p)
78 let start: i64 = slash + 1
79 let len: i64 = n - start
80 return nx_path_copy_z(out, (((p as i64) + start) as *u8), len)
81}
82
83// Return dirname: keep everything up to (not including) the last slash.
84// "foo/bar/baz.nx" -> "foo/bar"
85// "baz.nx" -> "."
86// "/baz" -> "/"
87//
88// Caller-supplied output must be at least strlen(p)+1 bytes (we may
89// write up to slash bytes + NUL).
90func nx_path_dirname(p: *u8, out: *u8) -> *u8 {
91 let slash: i64 = nx_path_last_slash(p)
92 if slash < 0 {
93 out[0] = 0x2E // "."
94 out[1] = 0
95 return out
96 }
97 if slash == 0 {
98 out[0] = 0x2F // "/"
99 out[1] = 0
100 return out
101 }
102 return nx_path_copy_z(out, p, slash)
103}
104
105// Join two path segments with a single '/' separator. Avoids double
106// slashes when `a` already ends with '/'. Caller supplies output.
107func nx_path_join(a: *u8, b: *u8, out: *u8) -> *u8 {
108 let na: i64 = strlen(a)
109 let nb: i64 = strlen(b)
110 var k: i64 = 0
111 while k < na {
112 out[k] = a[k]
113 k = k + 1
114 }
115 var off: i64 = na
116 if na > 0 {
117 if a[na - 1] != 0x2F {
118 out[off] = 0x2F
119 off = off + 1
120 }
121 }
122 var j: i64 = 0
123 while j < nb {
124 out[off + j] = b[j]
125 j = j + 1
126 }
127 out[off + nb] = 0
128 return out
129}
130
131// Replace the extension of `p` with `new_ext` (which should NOT
132// include the leading dot). If `p` has no extension, append one.
133// ("foo.nx", "s") -> "foo.s"
134// ("dir/foo.nx", "o") -> "dir/foo.o"
135// ("noext", "txt") -> "noext.txt"
136func nx_path_replace_ext(p: *u8, new_ext: *u8, out: *u8) -> *u8 {
137 let n: i64 = strlen(p)
138 let dot: i64 = nx_path_last_dot(p)
139 var stem_len: i64 = n
140 if dot >= 0 { stem_len = dot }
141 var i: i64 = 0
142 while i < stem_len {
143 out[i] = p[i]
144 i = i + 1
145 }
146 out[stem_len] = 0x2E
147 let ne: i64 = strlen(new_ext)
148 var j: i64 = 0
149 while j < ne {
150 out[stem_len + 1 + j] = new_ext[j]
151 j = j + 1
152 }
153 out[stem_len + 1 + ne] = 0
154 return out
155}
156
157// Return the extension of `p` (without the dot), or empty string
158// if no extension. Caller-supplied buffer.
159func nx_path_ext(p: *u8, out: *u8) -> *u8 {
160 let dot: i64 = nx_path_last_dot(p)
161 if dot < 0 {
162 out[0] = 0
163 return out
164 }
165 let n: i64 = strlen(p)
166 let len: i64 = n - dot - 1
167 return nx_path_copy_z(out, (((p as i64) + dot + 1) as *u8), len)
168}
169
170// streq compares NUL-terminated strings; returns 1 on match, 0 else.
171// streq canonical in runtime.nx.
172
173// ---- self-test ---------------------------------------------------
174
175func main() -> i64 {
176 let buf: *u8 = sys_mmap(256)
177
178 // basename
179 let p1: *u8 = sys_mmap(64)
180 p1[0] = 0x66; p1[1] = 0x6F; p1[2] = 0x6F; p1[3] = 0x2F
181 p1[4] = 0x62; p1[5] = 0x61; p1[6] = 0x72; p1[7] = 0x2F
182 p1[8] = 0x62; p1[9] = 0x61; p1[10] = 0x7A; p1[11] = 0x2E
183 p1[12] = 0x6E; p1[13] = 0x78; p1[14] = 0
184 nx_path_basename(p1, buf)
185 if buf[0] != 0x62 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
186 if buf[1] != 0x61 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
187 if buf[5] != 0x78 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
188 if buf[6] != 0 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
189
190 // dirname
191 nx_path_dirname(p1, buf)
192 if buf[0] != 0x66 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
193 if buf[3] != 0x2F { return __syscall(93, 6, 0, 0, 0, 0, 0) }
194 if buf[7] != 0 { return __syscall(93, 7, 0, 0, 0, 0, 0) }
195
196 // dirname of bare "baz.nx" -> "."
197 let p2: *u8 = sys_mmap(16)
198 p2[0] = 0x62; p2[1] = 0x61; p2[2] = 0x7A; p2[3] = 0x2E
199 p2[4] = 0x6E; p2[5] = 0x78; p2[6] = 0
200 nx_path_dirname(p2, buf)
201 if buf[0] != 0x2E { return __syscall(93, 8, 0, 0, 0, 0, 0) }
202 if buf[1] != 0 { return __syscall(93, 9, 0, 0, 0, 0, 0) }
203
204 // replace_ext: foo.nx -> foo.s
205 let p3: *u8 = sys_mmap(16)
206 p3[0] = 0x66; p3[1] = 0x6F; p3[2] = 0x6F; p3[3] = 0x2E
207 p3[4] = 0x6E; p3[5] = 0x78; p3[6] = 0
208 let new_ext: *u8 = sys_mmap(8)
209 new_ext[0] = 0x73; new_ext[1] = 0
210 nx_path_replace_ext(p3, new_ext, buf)
211 if buf[0] != 0x66 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
212 if buf[3] != 0x2E { return __syscall(93, 11, 0, 0, 0, 0, 0) }
213 if buf[4] != 0x73 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
214 if buf[5] != 0 { return __syscall(93, 13, 0, 0, 0, 0, 0) }
215
216 // join: dir + file
217 let p4a: *u8 = sys_mmap(16)
218 p4a[0] = 0x64; p4a[1] = 0x69; p4a[2] = 0x72; p4a[3] = 0
219 let p4b: *u8 = sys_mmap(16)
220 p4b[0] = 0x66; p4b[1] = 0x69; p4b[2] = 0x6C; p4b[3] = 0x65; p4b[4] = 0
221 nx_path_join(p4a, p4b, buf)
222 if buf[0] != 0x64 { return __syscall(93, 14, 0, 0, 0, 0, 0) }
223 if buf[3] != 0x2F { return __syscall(93, 15, 0, 0, 0, 0, 0) }
224 if buf[4] != 0x66 { return __syscall(93, 16, 0, 0, 0, 0, 0) }
225 if buf[8] != 0 { return __syscall(93, 17, 0, 0, 0, 0, 0) }
226
227 // ext extraction
228 nx_path_ext(p3, buf)
229 if buf[0] != 0x6E { return __syscall(93, 18, 0, 0, 0, 0, 0) }
230 if buf[1] != 0x78 { return __syscall(93, 19, 0, 0, 0, 0, 0) }
231 if buf[2] != 0 { return __syscall(93, 20, 0, 0, 0, 0, 0) }
232
233 return 0
234}