code wiki / (root) / nx_path.nx

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}