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.