nx_str.nx source
↩ module page · 221 lines · 6754 B
1// nx_str.nx -- C-style string utilities (NUL-terminated bytes).
2//
3// Almost every NishiLang module re-implements strlen / streq / a
4// scratch copy loop. Centralise the basic vocabulary here so all
5// callers agree on edge-case handling (empty strings, prefix
6// boundaries, NUL handling).
7//
8// Bytes are i64 in NishiLang (we don't have u8 scalars), but byte
9// slots in arrays / pointed-to memory ARE u8. We treat every byte
10// as 0..255 and never sign-extend.
11//
12// nx_strconv handles parse/format for integers; this module handles
13// raw character-level operations.
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"
22
23func nx_str_len(s: *u8) -> i64 {
24 var n: i64 = 0
25 while s[n] != 0 { n = n + 1 }
26 return n
27}
28
29// Returns 1 if a == b, 0 otherwise.
30func nx_str_eq(a: *u8, b: *u8) -> i64 {
31 var i: i64 = 0
32 while a[i] != 0 {
33 if b[i] != a[i] { return 0 }
34 i = i + 1
35 }
36 if b[i] != 0 { return 0 }
37 return 1
38}
39
40// strcmp-style: returns negative if a<b, 0 if equal, positive if a>b.
41func nx_str_cmp(a: *u8, b: *u8) -> i64 {
42 var i: i64 = 0
43 while a[i] != 0 {
44 if a[i] != b[i] { return a[i] - b[i] }
45 if b[i] == 0 { return 1 } // shouldn't reach due to a[i]!=0 && b[i]==0
46 i = i + 1
47 }
48 if b[i] != 0 { return 0 - b[i] }
49 return 0
50}
51
52// Copy `n` bytes from src to dst. Source/dest must not overlap.
53// Returns dst.
54func nx_str_memcpy(dst: *u8, src: *u8, n: i64) -> *u8 {
55 var i: i64 = 0
56 while i < n {
57 dst[i] = src[i]
58 i = i + 1
59 }
60 return dst
61}
62
63// Set first `n` bytes of dst to `v`. Returns dst.
64func nx_str_memset(dst: *u8, v: i64, n: i64) -> *u8 {
65 var i: i64 = 0
66 while i < n {
67 dst[i] = v
68 i = i + 1
69 }
70 return dst
71}
72
73// Compare first `n` bytes. strcmp-style.
74func nx_str_memcmp(a: *u8, b: *u8, n: i64) -> i64 {
75 var i: i64 = 0
76 while i < n {
77 if a[i] != b[i] { return a[i] - b[i] }
78 i = i + 1
79 }
80 return 0
81}
82
83// Copy src (NUL-terminated) into dst, including the NUL. Returns dst.
84func nx_str_cpy(dst: *u8, src: *u8) -> *u8 {
85 var i: i64 = 0
86 while src[i] != 0 {
87 dst[i] = src[i]
88 i = i + 1
89 }
90 dst[i] = 0
91 return dst
92}
93
94// Append src (NUL-terminated) to dst (NUL-terminated, must have room).
95// Returns dst.
96func nx_str_cat(dst: *u8, src: *u8) -> *u8 {
97 let dn: i64 = nx_str_len(dst)
98 var i: i64 = 0
99 while src[i] != 0 {
100 dst[dn + i] = src[i]
101 i = i + 1
102 }
103 dst[dn + i] = 0
104 return dst
105}
106
107// Find first occurrence of byte `c` in `s`. Returns offset or -1.
108func nx_str_chr(s: *u8, c: i64) -> i64 {
109 var i: i64 = 0
110 while s[i] != 0 {
111 if s[i] == c { return i }
112 i = i + 1
113 }
114 if c == 0 { return i }
115 return -1
116}
117
118// Find LAST occurrence of byte `c` in `s`. Returns offset or -1.
119func nx_str_rchr(s: *u8, c: i64) -> i64 {
120 let n: i64 = nx_str_len(s)
121 var i: i64 = n - 1
122 while i >= 0 {
123 if s[i] == c { return i }
124 i = i - 1
125 }
126 return -1
127}
128
129// Find first occurrence of substring `needle` in `hay`. Returns
130// offset or -1. Naive O(n*m) -- fine for short needles, which is
131// the toolchain use case.
132func nx_str_str(hay: *u8, needle: *u8) -> i64 {
133 let nn: i64 = nx_str_len(needle)
134 if nn == 0 { return 0 }
135 let hn: i64 = nx_str_len(hay)
136 if nn > hn { return -1 }
137 var i: i64 = 0
138 while i <= hn - nn {
139 var j: i64 = 0
140 var ok: i64 = 1
141 while j < nn {
142 if hay[i + j] != needle[j] { ok = 0; j = nn }
143 else { j = j + 1 }
144 }
145 if ok == 1 { return i }
146 i = i + 1
147 }
148 return -1
149}
150
151// Returns 1 if `hay` starts with `pfx`, 0 otherwise.
152func nx_str_starts_with(hay: *u8, pfx: *u8) -> i64 {
153 var i: i64 = 0
154 while pfx[i] != 0 {
155 if hay[i] != pfx[i] { return 0 }
156 i = i + 1
157 }
158 return 1
159}
160
161// Returns 1 if `hay` ends with `sfx`, 0 otherwise.
162func nx_str_ends_with(hay: *u8, sfx: *u8) -> i64 {
163 let hn: i64 = nx_str_len(hay)
164 let sn: i64 = nx_str_len(sfx)
165 if sn > hn { return 0 }
166 var i: i64 = 0
167 while i < sn {
168 if hay[hn - sn + i] != sfx[i] { return 0 }
169 i = i + 1
170 }
171 return 1
172}
173
174// ---- self-test ---------------------------------------------------
175
176func main() -> i64 {
177 let foo: *u8 = sys_mmap(8)
178 foo[0] = 0x66; foo[1] = 0x6F; foo[2] = 0x6F; foo[3] = 0
179 let foobar: *u8 = sys_mmap(16)
180 foobar[0] = 0x66; foobar[1] = 0x6F; foobar[2] = 0x6F
181 foobar[3] = 0x62; foobar[4] = 0x61; foobar[5] = 0x72; foobar[6] = 0
182 let bar: *u8 = sys_mmap(8)
183 bar[0] = 0x62; bar[1] = 0x61; bar[2] = 0x72; bar[3] = 0
184
185 if nx_str_len(foo) != 3 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
186 if nx_str_len(foobar) != 6 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
187
188 if nx_str_eq(foo, foo) != 1 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
189 if nx_str_eq(foo, bar) != 0 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
190 if nx_str_eq(foo, foobar) != 0 { return __syscall(93, 5, 0, 0, 0, 0, 0) } // foo prefix of foobar
191
192 if nx_str_starts_with(foobar, foo) != 1 { return __syscall(93, 6, 0, 0, 0, 0, 0) }
193 if nx_str_ends_with(foobar, bar) != 1 { return __syscall(93, 7, 0, 0, 0, 0, 0) }
194 if nx_str_ends_with(foobar, foo) != 0 { return __syscall(93, 8, 0, 0, 0, 0, 0) }
195
196 if nx_str_chr(foobar, 0x62) != 3 { return __syscall(93, 9, 0, 0, 0, 0, 0) }
197 if nx_str_rchr(foobar, 0x6F) != 2 { return __syscall(93, 10, 0, 0, 0, 0, 0) } // last 'o' is at idx 2
198 if nx_str_chr(foobar, 0x7A) != -1 { return __syscall(93, 11, 0, 0, 0, 0, 0) }
199
200 if nx_str_str(foobar, bar) != 3 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
201 if nx_str_str(foobar, foo) != 0 { return __syscall(93, 13, 0, 0, 0, 0, 0) }
202
203 let scratch: *u8 = sys_mmap(32)
204 nx_str_cpy(scratch, foobar)
205 if scratch[0] != 0x66 { return __syscall(93, 14, 0, 0, 0, 0, 0) }
206 if scratch[5] != 0x72 { return __syscall(93, 15, 0, 0, 0, 0, 0) }
207 if scratch[6] != 0 { return __syscall(93, 16, 0, 0, 0, 0, 0) }
208
209 nx_str_cat(scratch, bar)
210 if scratch[6] != 0x62 { return __syscall(93, 17, 0, 0, 0, 0, 0) }
211 if scratch[8] != 0x72 { return __syscall(93, 18, 0, 0, 0, 0, 0) }
212 if scratch[9] != 0 { return __syscall(93, 19, 0, 0, 0, 0, 0) }
213
214 nx_str_memset(scratch, 0xAA, 4)
215 if scratch[0] != 0xAA { return __syscall(93, 20, 0, 0, 0, 0, 0) }
216 if scratch[3] != 0xAA { return __syscall(93, 21, 0, 0, 0, 0, 0) }
217
218 if nx_str_memcmp(foo, foo, 3) != 0 { return __syscall(93, 22, 0, 0, 0, 0, 0) }
219
220 return 0
221}