code wiki / (root) / path.nx

path.nx source

↩ module page · 219 lines · 6559 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 20import "syscalls.nx" 21 22const PATH_ERR_SHORT: i64 = -1 23 24// Return index of LAST '/' in path[0..n], or -1 if none. 25func path_last_slash(path: *u8, n: i64) -> i64 { 26 var i: i64 = n - 1 27 while i >= 0 { 28 if path[i] == 0x2F { return i } 29 i = i - 1 30 } 31 return -1 32} 33 34// Strip trailing slashes (but leave root \"/\" alone). Returns 35// new effective length. 36func path_trim_trailing(path: *u8, n: i64) -> i64 { 37 var e: i64 = n 38 while e > 1 { 39 if path[e - 1] != 0x2F { break } 40 e = e - 1 41 } 42 return e 43} 44 45// basename(path) -> offset + length. \"/a/b/c\" -> \"c\"; 46// \"/a/b/\" -> \"b\" (after trailing-slash trim). 47func path_basename(path: *u8, n: i64, 48 off_out: *i64, len_out: *i64) -> i64 { 49 let eff: i64 = path_trim_trailing(path, n) 50 if eff == 0 { 51 *off_out = 0 52 *len_out = 0 53 return 0 54 } 55 let slash: i64 = path_last_slash(path, eff) 56 if slash < 0 { 57 *off_out = 0 58 *len_out = eff 59 } else { 60 *off_out = slash + 1 61 *len_out = eff - slash - 1 62 } 63 return 0 64} 65 66// dirname(path) -> offset + length. 67// \"/a/b/c\" -> \"/a/b\" 68// \"/a\" -> \"/\" 69// \"a\" -> \".\" (via synthesised constant below) 70// \"\" -> \".\" 71func path_dirname(path: *u8, n: i64, 72 off_out: *i64, len_out: *i64, 73 synth_out: *u8) -> i64 { 74 let eff: i64 = path_trim_trailing(path, n) 75 if eff == 0 { 76 synth_out[0] = 0x2E // '.' 77 *off_out = -1 // signals \"use synth_out\" 78 *len_out = 1 79 return 0 80 } 81 let slash: i64 = path_last_slash(path, eff) 82 if slash < 0 { 83 synth_out[0] = 0x2E 84 *off_out = -1 85 *len_out = 1 86 return 0 87 } 88 if slash == 0 { 89 // Root. 90 synth_out[0] = 0x2F 91 *off_out = -1 92 *len_out = 1 93 return 0 94 } 95 *off_out = 0 96 *len_out = slash 97 return 0 98} 99 100// extension(path) -> (offset, length) of the final \".ext\" including 101// the dot, or length 0 if none. Recognises only extensions AFTER 102// the basename's last '/' so \"/a.b/c\" has no extension. 103func path_extension(path: *u8, n: i64, 104 off_out: *i64, len_out: *i64) -> i64 { 105 let bn_off_slot: *i64 = (sys_mmap(16)) as *i64 106 let bn_len_slot: *i64 = (sys_mmap(16)) as *i64 107 path_basename(path, n, bn_off_slot, bn_len_slot) 108 let bn_off: i64 = *bn_off_slot 109 let bn_len: i64 = *bn_len_slot 110 var i: i64 = bn_off + bn_len - 1 111 while i >= bn_off { 112 if path[i] == 0x2E { 113 if i > bn_off { // leading dot doesn't count 114 *off_out = i 115 *len_out = bn_off + bn_len - i 116 return 0 117 } 118 } 119 i = i - 1 120 } 121 *off_out = 0 122 *len_out = 0 123 return 0 124} 125 126// Is path absolute? 127func path_is_absolute(path: *u8, n: i64) -> i64 { 128 if n == 0 { return 0 } 129 if path[0] == 0x2F { return 1 } 130 return 0 131} 132 133// Join two paths into out. If `b` is absolute it replaces `a`. 134// Otherwise inserts \"/\" separator if needed. 135func path_join(a: *u8, a_len: i64, 136 b: *u8, b_len: i64, 137 out: *u8, cap: i64) -> i64 { 138 if path_is_absolute(b, b_len) == 1 { 139 if cap < b_len { return PATH_ERR_SHORT } 140 var i: i64 = 0 141 while i < b_len { out[i] = b[i]; i = i + 1 } 142 return b_len 143 } 144 if a_len == 0 { 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 var need_sep: i64 = 1 151 if a[a_len - 1] == 0x2F { need_sep = 0 } 152 153 let total: i64 = a_len + need_sep + b_len 154 if cap < total { return PATH_ERR_SHORT } 155 var i: i64 = 0 156 while i < a_len { out[i] = a[i]; i = i + 1 } 157 if need_sep == 1 { 158 out[a_len] = 0x2F 159 } 160 var j: i64 = 0 161 while j < b_len { 162 out[a_len + need_sep + j] = b[j] 163 j = j + 1 164 } 165 return total 166} 167 168// Compile-only smoke. 169func main() -> i64 { 170 let off: *i64 = (sys_mmap(16)) as *i64 171 let len: *i64 = (sys_mmap(16)) as *i64 172 let synth: *u8 = sys_mmap(16) 173 174 // basename. 175 path_basename("/home/elder/notes.md", 20, off, len) 176 if *off != 12 { return 1 } 177 if *len != 8 { return 2 } // "notes.md" 178 179 // basename with trailing slash. 180 path_basename("/a/b/", 5, off, len) 181 if *len != 1 { return 3 } // "b" 182 183 // dirname. 184 path_dirname("/home/elder/notes.md", 20, off, len, synth) 185 if *off != 0 { return 4 } 186 if *len != 11 { return 5 } // "/home/elder" 187 188 // dirname for root. 189 path_dirname("/a", 2, off, len, synth) 190 if *off != -1 { return 6 } 191 if *len != 1 { return 7 } 192 if synth[0] != 0x2F { return 8 } 193 194 // extension. 195 path_extension("/a/b.txt", 8, off, len) 196 if *off != 4 { return 9 } 197 if *len != 4 { return 10 } // ".txt" 198 199 // extension missing. 200 path_extension("/a/b", 4, off, len) 201 if *len != 0 { return 11 } 202 203 // is_absolute. 204 if path_is_absolute("/a", 2) != 1 { return 12 } 205 if path_is_absolute("a", 1) != 0 { return 13 } 206 207 // join. 208 let out: *u8 = sys_mmap(64) 209 let jn: i64 = path_join("/a", 2, "b/c", 3, out, 64) 210 if jn != 6 { return 14 } // "/a/b/c" 211 if out[2] != 0x2F { return 15 } 212 213 // join absolute right -> replaces. 214 let jn2: i64 = path_join("/a", 2, "/x", 2, out, 64) 215 if jn2 != 2 { return 16 } 216 if out[0] != 0x2F { return 17 } 217 if out[1] != 0x78 { return 18 } 218 return 0 219}