code wiki / (root) / str.nx

str.nx source

↩ module page · 305 lines · 8469 B

1// str.nx -- first-class string / byte-slice type for NishiLang. 2// 3// Carries (ptr, length) by pointer. Fixes the null-terminator 4// problem: `str` can store embedded zeros, and length is O(1). 5// 6// Eventually generalizes to `Slice<T>` once A4 (generics) lands; 7// for now we specialize to bytes because that's 99% of the use 8// case (HTTP parsing, markdown, file contents, console I/O). 9// 10// Usage pattern: 11// let hello: *str = str_from_cstr("Hello, World\n") 12// sys_write(1, hello.ptr, hello.len) 13// 14// Helper `str_from_cstr(ptr)` computes length by walking to NUL. 15// For true runtime-constructed slices (e.g. a substring), use 16// `str_new(ptr, len)` which stores as-given. 17 18struct str { 19 ptr: *u8, 20 len: i64, 21} 22 23// Shared syscalls + sys_mmap now live in syscalls.nx so every runtime 24// module stops copy-pasting them. str_sys_mmap stays here as a 25// back-compat wrapper until callers migrate. 26import "syscalls.nx" 27 28func str_sys_mmap(size: i64) -> *u8 { 29 return sys_mmap(size) 30} 31 32// ---- constructors ---- 33 34// Wrap a null-terminated C-style pointer into a str. 35// Walks to NUL to determine length. O(n) but called once. 36func str_from_cstr(p: *u8) -> *str { 37 var n: i64 = 0 38 while p[n] != 0 { n = n + 1 } 39 let raw: *u8 = str_sys_mmap(16) 40 let s: *str = raw as *str 41 s.ptr = p 42 s.len = n 43 return s 44} 45 46// Allocate a str with (ptr, len) given directly. ptr must stay 47// valid for the lifetime of the str (caller responsibility). 48func str_new(p: *u8, len: i64) -> *str { 49 let raw: *u8 = str_sys_mmap(16) 50 let s: *str = raw as *str 51 s.ptr = p 52 s.len = len 53 return s 54} 55 56// Empty str -- ptr=0, len=0. Safe to compare lengths, don't 57// dereference ptr. 58func str_empty() -> *str { 59 return str_new(0 as *u8, 0) 60} 61 62// ---- basic accessors ---- 63 64func str_len(s: *str) -> i64 { return s.len } 65func str_is_empty(s: *str) -> i64 { 66 if s.len == 0 { return 1 } 67 return 0 68} 69 70// Unchecked byte access. In production mode we'd bounds-check; 71// for now caller checks. 72func str_at(s: *str, i: i64) -> i64 { 73 let p: *u8 = s.ptr 74 return p[i] 75} 76 77// ---- comparison ---- 78 79func str_eq(a: *str, b: *str) -> i64 { 80 if a.len != b.len { return 0 } 81 let ap: *u8 = a.ptr 82 let bp: *u8 = b.ptr 83 var i: i64 = 0 84 while i < a.len { 85 if ap[i] != bp[i] { return 0 } 86 i = i + 1 87 } 88 return 1 89} 90 91// Compare a str to a C-style literal. Convenient for matching 92// HTTP verbs, header names, etc. 93func str_eq_cstr(a: *str, cstr: *u8) -> i64 { 94 var i: i64 = 0 95 while i < a.len { 96 if cstr[i] == 0 { return 0 } // literal shorter 97 let ap: *u8 = a.ptr 98 if ap[i] != cstr[i] { return 0 } 99 i = i + 1 100 } 101 if cstr[i] != 0 { return 0 } // literal longer 102 return 1 103} 104 105// ---- sub-slicing ---- 106 107// s[begin..end) -- new str referring to the same backing bytes. 108// Caller ensures 0 <= begin <= end <= s.len. 109func str_substr(s: *str, begin: i64, end: i64) -> *str { 110 let p: *u8 = s.ptr 111 let base: i64 = p as i64 112 return str_new((base + begin) as *u8, end - begin) 113} 114 115// Does `s` start with `prefix`? 116func str_starts_with(s: *str, prefix: *str) -> i64 { 117 if s.len < prefix.len { return 0 } 118 let sp: *u8 = s.ptr 119 let pp: *u8 = prefix.ptr 120 var i: i64 = 0 121 while i < prefix.len { 122 if sp[i] != pp[i] { return 0 } 123 i = i + 1 124 } 125 return 1 126} 127 128// Index of first occurrence of `c`, or -1. 129func str_index_of(s: *str, c: i64) -> i64 { 130 let p: *u8 = s.ptr 131 var i: i64 = 0 132 while i < s.len { 133 if p[i] == c { return i } 134 i = i + 1 135 } 136 return -1 137} 138 139// Split s at the first occurrence of byte `c`. Returns the 140// prefix (before c). `remainder` (after c) is written back 141// through `rest_out`, which must be non-null. If `c` is not 142// present, returns s and writes empty string to *rest_out. 143func str_split_once(s: *str, c: i64, rest_out: *i64) -> *str { 144 let idx: i64 = str_index_of(s, c) 145 if idx < 0 { 146 *rest_out = str_empty() as i64 147 return s 148 } 149 let prefix: *str = str_substr(s, 0, idx) 150 let rest: *str = str_substr(s, idx + 1, s.len) 151 *rest_out = rest as i64 152 return prefix 153} 154 155// Trim ASCII whitespace from both ends. 156func str_trim(s: *str) -> *str { 157 let p: *u8 = s.ptr 158 var start: i64 = 0 159 while start < s.len { 160 let c: i64 = p[start] 161 if c != 0x20 { if c != 0x09 { if c != 0x0A { if c != 0x0D { break } } } } 162 start = start + 1 163 } 164 var end: i64 = s.len 165 while end > start { 166 let c: i64 = p[end - 1] 167 if c != 0x20 { if c != 0x09 { if c != 0x0A { if c != 0x0D { break } } } } 168 end = end - 1 169 } 170 return str_substr(s, start, end) 171} 172 173// ---- integer parsing ---- 174 175// Parse a decimal integer from the start of s. Stops at first 176// non-digit. Negative sign allowed. Returns parsed value; writes 177// the number of bytes consumed through `consumed_out`. 178func str_parse_int(s: *str, consumed_out: *i64) -> i64 { 179 let p: *u8 = s.ptr 180 var i: i64 = 0 181 var sign: i64 = 1 182 if i < s.len { 183 if p[0] == 0x2D { 184 sign = -1 185 i = 1 186 } 187 } 188 var v: i64 = 0 189 while i < s.len { 190 let c: i64 = p[i] 191 if c < 0x30 { break } 192 if c > 0x39 { break } 193 v = v * 10 + (c - 0x30) 194 i = i + 1 195 } 196 *consumed_out = i 197 return v * sign 198} 199 200// ---- writing ---- 201 202// Write a str to an fd. Returns bytes written. 203func str_write(fd: i64, s: *str) -> i64 { 204 return __syscall(64, fd, s.ptr, s.len) 205} 206 207// ---- F6 string manipulation (backlog task #88) --------------------- 208 209// Suffix check. Returns 1 if s ends with suffix, else 0. 210func str_ends_with(s: *str, suffix: *str) -> i64 { 211 if suffix.len > s.len { return 0 } 212 let off: i64 = s.len - suffix.len 213 var i: i64 = 0 214 while i < suffix.len { 215 if s.ptr[off + i] != suffix.ptr[i] { return 0 } 216 i = i + 1 217 } 218 return 1 219} 220 221// Substring search. Returns the starting index of `needle` in s, or 222// -1 if not found. Naive O(n*m); fine for the <= MTU strings we 223// typically process (HTTP headers, config lines). 224func str_contains(s: *str, needle: *str) -> i64 { 225 if needle.len == 0 { return 0 } 226 if needle.len > s.len { return -1 } 227 let limit: i64 = s.len - needle.len 228 var i: i64 = 0 229 while i <= limit { 230 var j: i64 = 0 231 var ok: i64 = 1 232 while j < needle.len { 233 if s.ptr[i + j] != needle.ptr[j] { ok = 0; j = needle.len } 234 j = j + 1 235 } 236 if ok == 1 { return i } 237 i = i + 1 238 } 239 return -1 240} 241 242// ASCII lowercase transform into a fresh buffer; non-alpha bytes 243// pass through. Returns a new *str with freshly mmap'd storage. 244func str_to_lower(s: *str) -> *str { 245 let buf: *u8 = sys_mmap(s.len + 8) 246 var i: i64 = 0 247 while i < s.len { 248 var c: i64 = s.ptr[i] 249 if c >= 0x41 { 250 if c <= 0x5A { c = c + 0x20 } 251 } 252 buf[i] = c 253 i = i + 1 254 } 255 return str_new(buf, s.len) 256} 257 258// ASCII uppercase transform, dual of str_to_lower. 259func str_to_upper(s: *str) -> *str { 260 let buf: *u8 = sys_mmap(s.len + 8) 261 var i: i64 = 0 262 while i < s.len { 263 var c: i64 = s.ptr[i] 264 if c >= 0x61 { 265 if c <= 0x7A { c = c - 0x20 } 266 } 267 buf[i] = c 268 i = i + 1 269 } 270 return str_new(buf, s.len) 271} 272 273// Format a signed i64 as a decimal str. Allocates 24 bytes (covers 274// the widest i64 plus sign). Parallels str_parse_int. 275func str_format_int(n: i64) -> *str { 276 let buf: *u8 = sys_mmap(32) 277 if n == 0 { 278 buf[0] = 0x30 279 return str_new(buf, 1) 280 } 281 var v: i64 = n 282 var neg: i64 = 0 283 if v < 0 { 284 neg = 1 285 v = 0 - v 286 } 287 // Write digits into scratch, reversed. 288 let scratch: *u8 = sys_mmap(32) 289 var sp: i64 = 0 290 while v > 0 { 291 scratch[sp] = 0x30 + (v % 10) 292 v = v / 10 293 sp = sp + 1 294 } 295 var k: i64 = 0 296 if neg == 1 { buf[0] = 0x2D; k = 1 } 297 while sp > 0 { 298 sp = sp - 1 299 buf[k] = scratch[sp] 300 k = k + 1 301 } 302 return str_new(buf, k) 303} 304 305// Library only; self-test lives in str_test.nx.