code wiki / (root) / nx_path_v1.nx

nx_path_v1.nx source

↩ module page · 225 lines · 6594 B

1// path.nx -- POSIX filesystem path manipulation. 2// 3// Same operations as Python's posixpath / Node's path.posix / 4// Go's filepath: join / dirname / basename / extension / clean / 5// absolute-check. Byte-oriented (not Unicode-aware), which 6// matches how filesystems actually work. 7// 8// Forward-slash separator only. Windows backslashes fall outside 9// the sovereign-Linux target (Milk-V Pioneer + NishiOS). 10// 11// Invariants: 12// P1 Zero-alloc when possible: offsets into caller's buffer 13// for basename/dirname/extension. Only `path_join` writes 14// into a fresh output buffer. 15// P2 Empty-path handling matches POSIX: dirname(\"\")=\".\" 16// basename(\"\")=\"\" extension(\"\")=\"\". 17// P3 Trailing slashes: dirname(\"/a/b/\")=\"/a\" matches 18// POSIX; basename(\"/a/b/\")=\"b\" after trim. 19 20// nx_safety_envelope: 21// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 22// sil_target: SIL1 23// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 24// verdict: NOT_YET_EVALUATED 25 26import "nx_syscalls.nx" 27 28const PATH_ERR_SHORT: i64 = -1 29 30// Return index of LAST '/' in path[0..n], or -1 if none. 31func path_last_slash(path: *u8, n: i64) -> i64 { 32 var i: i64 = n - 1 33 while i >= 0 { 34 if path[i] == 0x2F { return i } 35 i = i - 1 36 } 37 return -1 38} 39 40// Strip trailing slashes (but leave root \"/\" alone). Returns 41// new effective length. 42func path_trim_trailing(path: *u8, n: i64) -> i64 { 43 var e: i64 = n 44 while e > 1 { 45 if path[e - 1] != 0x2F { break } 46 e = e - 1 47 } 48 return e 49} 50 51// basename(path) -> offset + length. \"/a/b/c\" -> \"c\"; 52// \"/a/b/\" -> \"b\" (after trailing-slash trim). 53func path_basename(path: *u8, n: i64, 54 off_out: *i64, len_out: *i64) -> i64 { 55 let eff: i64 = path_trim_trailing(path, n) 56 if eff == 0 { 57 *off_out = 0 58 *len_out = 0 59 return 0 60 } 61 let slash: i64 = path_last_slash(path, eff) 62 if slash < 0 { 63 *off_out = 0 64 *len_out = eff 65 } else { 66 *off_out = slash + 1 67 *len_out = eff - slash - 1 68 } 69 return 0 70} 71 72// dirname(path) -> offset + length. 73// \"/a/b/c\" -> \"/a/b\" 74// \"/a\" -> \"/\" 75// \"a\" -> \".\" (via synthesised constant below) 76// \"\" -> \".\" 77func path_dirname(path: *u8, n: i64, 78 off_out: *i64, len_out: *i64, 79 synth_out: *u8) -> i64 { 80 let eff: i64 = path_trim_trailing(path, n) 81 if eff == 0 { 82 synth_out[0] = 0x2E // '.' 83 *off_out = -1 // signals \"use synth_out\" 84 *len_out = 1 85 return 0 86 } 87 let slash: i64 = path_last_slash(path, eff) 88 if slash < 0 { 89 synth_out[0] = 0x2E 90 *off_out = -1 91 *len_out = 1 92 return 0 93 } 94 if slash == 0 { 95 // Root. 96 synth_out[0] = 0x2F 97 *off_out = -1 98 *len_out = 1 99 return 0 100 } 101 *off_out = 0 102 *len_out = slash 103 return 0 104} 105 106// extension(path) -> (offset, length) of the final \".ext\" including 107// the dot, or length 0 if none. Recognises only extensions AFTER 108// the basename's last '/' so \"/a.b/c\" has no extension. 109func path_extension(path: *u8, n: i64, 110 off_out: *i64, len_out: *i64) -> i64 { 111 let bn_off_slot: *i64 = (sys_mmap(16)) as *i64 112 let bn_len_slot: *i64 = (sys_mmap(16)) as *i64 113 path_basename(path, n, bn_off_slot, bn_len_slot) 114 let bn_off: i64 = *bn_off_slot 115 let bn_len: i64 = *bn_len_slot 116 var i: i64 = bn_off + bn_len - 1 117 while i >= bn_off { 118 if path[i] == 0x2E { 119 if i > bn_off { // leading dot doesn't count 120 *off_out = i 121 *len_out = bn_off + bn_len - i 122 return 0 123 } 124 } 125 i = i - 1 126 } 127 *off_out = 0 128 *len_out = 0 129 return 0 130} 131 132// Is path absolute? 133func path_is_absolute(path: *u8, n: i64) -> i64 { 134 if n == 0 { return 0 } 135 if path[0] == 0x2F { return 1 } 136 return 0 137} 138 139// Join two paths into out. If `b` is absolute it replaces `a`. 140// Otherwise inserts \"/\" separator if needed. 141func path_join(a: *u8, a_len: i64, 142 b: *u8, b_len: i64, 143 out: *u8, cap: i64) -> i64 { 144 if path_is_absolute(b, b_len) == 1 { 145 if cap < b_len { return PATH_ERR_SHORT } 146 var i: i64 = 0 147 while i < b_len { out[i] = b[i]; i = i + 1 } 148 return b_len 149 } 150 if a_len == 0 { 151 if cap < b_len { return PATH_ERR_SHORT } 152 var i: i64 = 0 153 while i < b_len { out[i] = b[i]; i = i + 1 } 154 return b_len 155 } 156 var need_sep: i64 = 1 157 if a[a_len - 1] == 0x2F { need_sep = 0 } 158 159 let total: i64 = a_len + need_sep + b_len 160 if cap < total { return PATH_ERR_SHORT } 161 var i: i64 = 0 162 while i < a_len { out[i] = a[i]; i = i + 1 } 163 if need_sep == 1 { 164 out[a_len] = 0x2F 165 } 166 var j: i64 = 0 167 while j < b_len { 168 out[a_len + need_sep + j] = b[j] 169 j = j + 1 170 } 171 return total 172} 173 174// Compile-only smoke. 175func main() -> i64 { 176 let off: *i64 = (sys_mmap(16)) as *i64 177 let len: *i64 = (sys_mmap(16)) as *i64 178 let synth: *u8 = sys_mmap(16) 179 180 // basename. 181 path_basename("/home/elder/notes.md", 20, off, len) 182 if *off != 12 { return 1 } 183 if *len != 8 { return 2 } // "notes.md" 184 185 // basename with trailing slash. 186 path_basename("/a/b/", 5, off, len) 187 if *len != 1 { return 3 } // "b" 188 189 // dirname. 190 path_dirname("/home/elder/notes.md", 20, off, len, synth) 191 if *off != 0 { return 4 } 192 if *len != 11 { return 5 } // "/home/elder" 193 194 // dirname for root. 195 path_dirname("/a", 2, off, len, synth) 196 if *off != -1 { return 6 } 197 if *len != 1 { return 7 } 198 if synth[0] != 0x2F { return 8 } 199 200 // extension. 201 path_extension("/a/b.txt", 8, off, len) 202 if *off != 4 { return 9 } 203 if *len != 4 { return 10 } // ".txt" 204 205 // extension missing. 206 path_extension("/a/b", 4, off, len) 207 if *len != 0 { return 11 } 208 209 // is_absolute. 210 if path_is_absolute("/a", 2) != 1 { return 12 } 211 if path_is_absolute("a", 1) != 0 { return 13 } 212 213 // join. 214 let out: *u8 = sys_mmap(64) 215 let jn: i64 = path_join("/a", 2, "b/c", 3, out, 64) 216 if jn != 6 { return 14 } // "/a/b/c" 217 if out[2] != 0x2F { return 15 } 218 219 // join absolute right -> replaces. 220 let jn2: i64 = path_join("/a", 2, "/x", 2, out, 64) 221 if jn2 != 2 { return 16 } 222 if out[0] != 0x2F { return 17 } 223 if out[1] != 0x78 { return 18 } 224 return 0 225}