nx_func_extract.nx source
↩ module page · 331 lines · 12899 B
1// nx_extract.nx -- function-body extraction + normalization + hashing.
2//
3// Substrate-native primitive for semantic-equivalence verification.
4// Foundation for the dedup-then-consolidate workflow: extract a
5// named function from a file, normalize away comments and whitespace,
6// compute a deterministic hash, compare across files.
7//
8// Capabilities provided:
9// - nx_extract_func_body : locate `func NAME(...)` and return its
10// body byte range [body_start, body_end)
11// - nx_extract_normalize : copy a buffer slice with line comments
12// removed and runs of whitespace collapsed
13// to single spaces; strings preserved verbatim
14// - nx_extract_hash : FNV-1a 64-bit hash of a byte range
15// - nx_extract_func_hash : convenience -- extract + normalize + hash
16//
17// All pure-NishiLang. No reliance on external grep/awk/sed. This is
18// the substrate's answer to "diff two functions for equivalence"
19// without leaving NishiLang.
20//
21// genealogy_id: fowler_noll_vo_1991_fnv1a + baker_1995_clone_detection
22// lineage_id: syntax_directed_function_extraction + content_hash
23// axioms: NX_AX_REL_REFLEXIVITY (hash(a) = hash(a))
24// + NX_AX_REL_SYMMETRY (hash(a)=hash(b) <-> hash(b)=hash(a))
25// + NX_AX_REL_TRANSITIVITY (a~b, b~c -> a~c)
26// together: hash-equality is an equivalence relation;
27// collisions are theoretically possible (Pigeonhole) but
28// FNV-1a 64-bit collision probability is negligible at
29// our function-population scale (thousands, not billions).
30
31// nx_safety_envelope:
32// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
33// sil_target: SIL1
34// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
35// verdict: NOT_YET_EVALUATED
36
37import "syscalls.nx"
38import "runtime.nx"
39import "nx_axioms.nx"
40import "nx_lex.nx"
41
42const NX_EXTRACT_FNV64_OFFSET: i64 = 0xcbf29ce484222325
43const NX_EXTRACT_FNV64_PRIME: i64 = 1099511628211 // 0x100000001b3
44
45// ===== character class helpers =========================================
46
47// is_ws canonical in nx_lex.nx.
48
49func nx_extract_is_alnum_us(c: i64) -> i64 {
50 if c >= 48 { if c <= 57 { return 1 } } // 0-9
51 if c >= 65 { if c <= 90 { return 1 } } // A-Z
52 if c >= 97 { if c <= 122 { return 1 } } // a-z
53 if c == 95 { return 1 } // _
54 return 0
55}
56
57// ===== function body extraction ========================================
58//
59// Walks the buffer looking for `func NAME(`. When found, scans
60// forward for the opening `{`, then tracks brace depth (ignoring
61// braces inside string literals) until depth returns to 0.
62//
63// Writes the body byte range (start at the `{`, end just past matching `}`)
64// to out_start / out_end. Returns 0 on success, -1 if not found.
65
66func nx_extract_func_body(buf: *u8, len: i64, fname: *u8,
67 out_start: *i64, out_end: *i64) -> i64 {
68 let fname_len: i64 = strlen(fname)
69 if fname_len <= 0 { return -1 }
70
71 var i: i64 = 0
72 var found: i64 = 0
73 while found == 0 {
74 if i >= len { return -1 }
75
76 // Match "func " at line start.
77 var at_line_start: i64 = 0
78 if i == 0 { at_line_start = 1 }
79 if at_line_start == 0 {
80 if buf[i - 1] == 10 { at_line_start = 1 }
81 }
82
83 var is_match: i64 = 0
84 if at_line_start == 1 {
85 if i + 5 <= len {
86 if buf[i] == 102 { // 'f'
87 if buf[i + 1] == 117 { // 'u'
88 if buf[i + 2] == 110 { // 'n'
89 if buf[i + 3] == 99 { // 'c'
90 let sep: i64 = buf[i + 4]
91 if sep == 32 { is_match = 1 }
92 if sep == 9 { is_match = 1 }
93 }
94 }
95 }
96 }
97 }
98 }
99
100 if is_match == 1 {
101 // Skip whitespace after "func".
102 var p: i64 = i + 5
103 var done_ws: i64 = 0
104 while done_ws == 0 {
105 if p >= len { done_ws = 1 }
106 if done_ws == 0 {
107 if buf[p] == 32 { p = p + 1 }
108 if buf[p] != 32 {
109 if buf[p] == 9 { p = p + 1 }
110 if buf[p] != 9 { done_ws = 1 }
111 }
112 }
113 }
114 // Compare with fname (must be followed by '(').
115 if p + fname_len + 1 <= len {
116 if strneq(((buf as i64) + p) as *u8, fname, fname_len) == 1 {
117 if buf[p + fname_len] == 40 { // '('
118 // Found. Now find next '{'.
119 var q: i64 = p + fname_len
120 var found_lbrace: i64 = 0
121 while found_lbrace == 0 {
122 if q >= len { return -1 }
123 if buf[q] == 123 { // '{'
124 found_lbrace = 1
125 }
126 if found_lbrace == 0 { q = q + 1 }
127 }
128 // q points at '{'. Walk to matching '}'.
129 let start: i64 = q
130 var depth: i64 = 1
131 q = q + 1
132 while depth > 0 {
133 if q >= len { return -1 }
134 let c: i64 = buf[q]
135 var handled: i64 = 0
136
137 // string literal
138 if c == 34 { // '"'
139 q = q + 1
140 var done_str: i64 = 0
141 while done_str == 0 {
142 if q >= len { return -1 }
143 let sc: i64 = buf[q]
144 if sc == 92 { // '\'
145 q = q + 2
146 }
147 if sc != 92 {
148 if sc == 34 { // closing "
149 done_str = 1
150 }
151 q = q + 1
152 }
153 }
154 handled = 1
155 }
156
157 // line comment "//"
158 if handled == 0 {
159 if c == 47 {
160 if q + 1 < len {
161 if buf[q + 1] == 47 {
162 q = q + 2
163 var done_cmt: i64 = 0
164 while done_cmt == 0 {
165 if q >= len { done_cmt = 1 }
166 if done_cmt == 0 {
167 if buf[q] == 10 { done_cmt = 1 }
168 q = q + 1
169 }
170 }
171 handled = 1
172 }
173 }
174 }
175 }
176
177 // open brace
178 if handled == 0 {
179 if c == 123 {
180 depth = depth + 1
181 q = q + 1
182 handled = 1
183 }
184 }
185
186 // close brace
187 if handled == 0 {
188 if c == 125 {
189 depth = depth - 1
190 q = q + 1
191 handled = 1
192 }
193 }
194
195 // default: advance one byte
196 if handled == 0 { q = q + 1 }
197 }
198 out_start[0] = start
199 out_end[0] = q // just past matching '}'
200 return 0
201 }
202 }
203 }
204 }
205 i = i + 1
206 }
207 return -1
208}
209
210// ===== normalization =======================================================
211//
212// Copy buf[start..end) into out, removing:
213// - line comments (// ... \n)
214// - runs of whitespace collapsed to single space (ASCII 32)
215// Strings preserved byte-for-byte.
216// Leading/trailing whitespace dropped.
217// Returns output length.
218
219func nx_extract_normalize(buf: *u8, start: i64, end: i64, out: *u8) -> i64 {
220 var out_pos: i64 = 0
221 var last_was_ws: i64 = 1 // start "before" content
222 var i: i64 = start
223 while i < end {
224 let c: i64 = buf[i]
225
226 // line comment
227 var consumed: i64 = 0
228 if c == 47 { // '/'
229 if i + 1 < end {
230 if buf[i + 1] == 47 {
231 // skip to newline
232 i = i + 2
233 var done_cmt: i64 = 0
234 while done_cmt == 0 {
235 if i >= end { done_cmt = 1 }
236 if done_cmt == 0 {
237 if buf[i] == 10 { done_cmt = 1 }
238 i = i + 1
239 }
240 }
241 consumed = 1
242 }
243 }
244 }
245
246 if consumed == 0 {
247 // string literal: copy verbatim including quotes
248 if c == 34 {
249 out[out_pos] = c
250 out_pos = out_pos + 1
251 last_was_ws = 0
252 i = i + 1
253 var done_str: i64 = 0
254 while done_str == 0 {
255 if i >= end { done_str = 1 }
256 if done_str == 0 {
257 let sc: i64 = buf[i]
258 out[out_pos] = sc
259 out_pos = out_pos + 1
260 if sc == 92 { // '\'
261 i = i + 1
262 if i < end {
263 out[out_pos] = buf[i]
264 out_pos = out_pos + 1
265 }
266 i = i + 1
267 }
268 if sc != 92 {
269 if sc == 34 { done_str = 1 }
270 i = i + 1
271 }
272 }
273 }
274 consumed = 1
275 }
276 }
277
278 if consumed == 0 {
279 if nx_lex_is_ws(c) == 1 {
280 if last_was_ws == 0 {
281 out[out_pos] = 32 // single space
282 out_pos = out_pos + 1
283 last_was_ws = 1
284 }
285 i = i + 1
286 }
287 if nx_lex_is_ws(c) == 0 {
288 out[out_pos] = c
289 out_pos = out_pos + 1
290 last_was_ws = 0
291 i = i + 1
292 }
293 }
294 }
295 // Drop trailing space if any.
296 if out_pos > 0 {
297 if out[out_pos - 1] == 32 { out_pos = out_pos - 1 }
298 }
299 out[out_pos] = 0
300 return out_pos
301}
302
303// ===== FNV-1a 64-bit hash ==============================================
304//
305// h = FNV_OFFSET
306// for each byte b: h = (h XOR b) * FNV_PRIME
307// In i64: NishiLang's multiplication wraps to 2^63 -- equivalent mod 2^64
308// for our purposes since collisions are content-dependent, not modulus-
309// dependent.
310
311func nx_extract_hash(buf: *u8, len: i64) -> i64 {
312 var h: i64 = NX_EXTRACT_FNV64_OFFSET
313 var i: i64 = 0
314 while i < len {
315 h = h ^ buf[i]
316 h = h * NX_EXTRACT_FNV64_PRIME
317 i = i + 1
318 }
319 return h
320}
321
322// ===== convenience =====================================================
323
324func nx_extract_func_hash(buf: *u8, len: i64, fname: *u8,
325 norm_buf: *u8) -> i64 {
326 let s: *i64 = (sys_mmap(8)) as *i64
327 let e: *i64 = (sys_mmap(8)) as *i64
328 if nx_extract_func_body(buf, len, fname, s, e) != 0 { return 0 }
329 let nlen: i64 = nx_extract_normalize(buf, s[0], e[0], norm_buf)
330 return nx_extract_hash(norm_buf, nlen)
331}