nx_doc_extractor_nx.nx source
↩ module page · 255 lines · 10152 B
1// nx_doc_extractor_nx.nx -- V2.0 P-6: deep .nx artifact parser.
2//
3// Reads .nx body bytes; extracts import_count + export_count for
4// populating NxArtifactStore.import_count + export_count fields.
5// Composes existing primitives.
6//
7// COMPOSES:
8// nx_html_extract.nx_he_match_ci (case-sensitive byte prefix match)
9// (caller passes body bytes already loaded via sys_read_file)
10//
11// COMPOSED BY:
12// wiki/nx_pipeline_walker (V3+ swap; V2.0 uses hardcoded zero values)
13//
14// V2.0 P-6 SCOPE:
15// - Count "import " line-starts -> import_count
16// - Count "func " line-starts (non-comment) -> export_count
17// - Skip lines inside `// ...` comment blocks (single-line)
18// - Bounded by body byte count
19// - Pure-function; no allocation; no state
20//
21// V3+ SCOPE (TODO):
22// - Parse sealed verdict const ranges (NX_XXX_OK = 0)
23// - Parse struct decls (struct NxXxx { ... })
24// - Parse `// depends:` comment-block declared deps
25// - Parse `// disk_kb:` budget hint
26// - Identify pub vs internal funcs (current: all `func` count as exports)
27//
28// Status: V2.0 P-6. 2026-05-27.
29
30import "nx_syscalls.nx"
31import "nx_html_extract.nx"
32
33// ===== Sealed verdict surface (codes 3020-3024) =================================================
34const NX_DXNX_OK: i64 = 0
35const NX_DXNX_BAD_INPUT: i64 = 3020
36const NX_DXNX_LOOP_BUDGET: i64 = 3021
37
38// ===== Named constants (M7) =================================================
39const NX_DXNX_MAX_BODY: i64 = 1048576 // 1 MB cap
40const NX_DXNX_LOOP_BUDGET_CAP: i64 = 10000000
41
42// ASCII (M7)
43const NX_DXNX_ASCII_NL: i64 = 0x0A
44const NX_DXNX_ASCII_SLASH: i64 = 0x2F
45const NX_DXNX_ASCII_SPACE: i64 = 0x20
46const NX_DXNX_ASCII_TAB: i64 = 0x09
47
48// ===== Line-start detection: skip whitespace; check for prefix =================================================
49
50func nx_dxnx_line_starts_with(src: *u8, line_off: i64, body_n: i64,
51 prefix_z: *u8, prefix_n: i64) -> i64 {
52 if line_off < 0 { return 0 }
53 if line_off >= body_n { return 0 }
54 if prefix_n < 1 { return 0 }
55 // Skip leading whitespace
56 var p: i64 = line_off
57 while p < body_n {
58 let c: i64 = src[p] as i64
59 if c == NX_DXNX_ASCII_SPACE { p = p + 1 }
60 if c == NX_DXNX_ASCII_TAB { p = p + 1 }
61 if c != NX_DXNX_ASCII_SPACE {
62 if c != NX_DXNX_ASCII_TAB { p = body_n + 1 }
63 }
64 }
65 if p > body_n { p = body_n }
66 if p + prefix_n > body_n { return 0 }
67 return nx_he_match_ci(src, p, body_n, prefix_z, prefix_n)
68}
69
70// ===== Detect line is a comment-only line (//) =================================================
71
72func nx_dxnx_line_is_comment(src: *u8, line_off: i64, body_n: i64) -> i64 {
73 if line_off < 0 { return 0 }
74 if line_off >= body_n { return 0 }
75 var p: i64 = line_off
76 while p < body_n {
77 let c: i64 = src[p] as i64
78 if c == NX_DXNX_ASCII_SPACE { p = p + 1 }
79 if c == NX_DXNX_ASCII_TAB { p = p + 1 }
80 if c != NX_DXNX_ASCII_SPACE {
81 if c != NX_DXNX_ASCII_TAB { p = body_n + 1 }
82 }
83 }
84 if p > body_n { p = body_n }
85 if p + 1 >= body_n { return 0 }
86 if (src[p] as i64) == NX_DXNX_ASCII_SLASH {
87 if (src[p + 1] as i64) == NX_DXNX_ASCII_SLASH { return 1 }
88 }
89 return 0
90}
91
92// ===== Walk body counting import_count + export_count =================================================
93//
94// import_count = lines starting with `import "...nx"` (substrate convention)
95// export_count = lines starting with `func ` that are NOT inside a comment
96//
97// V2.0: treats every `func` as export (substrate convention: top-level
98// funcs are all callable across modules; no private/public distinction).
99// V3+ may refine via `func _xxx` naming convention or // private: comments.
100
101func nx_doc_extract_nx_stats(body: *u8, body_n: i64,
102 out_imports: *i64, out_exports: *i64) -> i64 {
103 if (body as i64) == 0 { return 0 - NX_DXNX_BAD_INPUT }
104 if (out_imports as i64) == 0 { return 0 - NX_DXNX_BAD_INPUT }
105 if (out_exports as i64) == 0 { return 0 - NX_DXNX_BAD_INPUT }
106 if body_n < 0 { return 0 - NX_DXNX_BAD_INPUT }
107 if body_n > NX_DXNX_MAX_BODY { return 0 - NX_DXNX_BAD_INPUT }
108 out_imports[0] = 0
109 out_exports[0] = 0
110 if body_n < 1 { return NX_DXNX_OK }
111
112 var imports: i64 = 0
113 var exports: i64 = 0
114
115 // Walk line by line
116 var i: i64 = 0
117 var iter: i64 = 0
118 var line_start: i64 = 0
119 while i <= body_n {
120 if iter >= NX_DXNX_LOOP_BUDGET_CAP { return 0 - NX_DXNX_LOOP_BUDGET }
121 iter = iter + 1
122 let at_end: i64 = if i == body_n then 1 else 0
123 var is_eol: i64 = at_end
124 if at_end == 0 {
125 if (body[i] as i64) == NX_DXNX_ASCII_NL { is_eol = 1 }
126 }
127 if is_eol == 1 {
128 // Process line [line_start, i)
129 // Skip pure comment lines (first non-whitespace is `//`)
130 let is_cmt: i64 = nx_dxnx_line_is_comment(body, line_start, body_n)
131 if is_cmt == 0 {
132 // Check for "import "
133 if nx_dxnx_line_starts_with(body, line_start, body_n,
134 "import " as *u8, 7) == 1 {
135 imports = imports + 1
136 }
137 // Check for "func "
138 if nx_dxnx_line_starts_with(body, line_start, body_n,
139 "func " as *u8, 5) == 1 {
140 exports = exports + 1
141 }
142 }
143 line_start = i + 1
144 }
145 i = i + 1
146 }
147
148 out_imports[0] = imports
149 out_exports[0] = exports
150 return NX_DXNX_OK
151}
152
153// ===== V2.0 P-8.5: extract import PATHS into packed buffer =================================================
154//
155// For dep-graph construction: not just count, but the actual import
156// strings. Buffer format: per import [len_byte][bytes_no_quote_no_ext]
157// e.g., `import "wiki/nx_artifact_store.nx"` becomes
158// [25 bytes] "wiki/nx_artifact_store.nx"
159//
160// Caller-owned out_buf; returns total imports written + total bytes used.
161
162const NX_DXNX_MAX_IMPORT_LEN: i64 = 128
163
164func nx_doc_extract_nx_imports(body: *u8, body_n: i64,
165 out_buf: *u8, out_buf_cap: i64,
166 out_count: *i64, out_used: *i64) -> i64 {
167 if (body as i64) == 0 { return 0 - NX_DXNX_BAD_INPUT }
168 if (out_buf as i64) == 0 { return 0 - NX_DXNX_BAD_INPUT }
169 if (out_count as i64) == 0 { return 0 - NX_DXNX_BAD_INPUT }
170 if (out_used as i64) == 0 { return 0 - NX_DXNX_BAD_INPUT }
171 if body_n < 0 { return 0 - NX_DXNX_BAD_INPUT }
172 if body_n > NX_DXNX_MAX_BODY { return 0 - NX_DXNX_BAD_INPUT }
173 out_count[0] = 0
174 out_used[0] = 0
175 if body_n < 1 { return NX_DXNX_OK }
176
177 var count: i64 = 0
178 var used: i64 = 0
179
180 var i: i64 = 0
181 var iter: i64 = 0
182 var line_start: i64 = 0
183 while i <= body_n {
184 if iter >= NX_DXNX_LOOP_BUDGET_CAP { return 0 - NX_DXNX_LOOP_BUDGET }
185 iter = iter + 1
186 let at_end: i64 = if i == body_n then 1 else 0
187 var is_eol: i64 = at_end
188 if at_end == 0 {
189 if (body[i] as i64) == NX_DXNX_ASCII_NL { is_eol = 1 }
190 }
191 if is_eol == 1 {
192 if nx_dxnx_line_is_comment(body, line_start, body_n) == 0 {
193 if nx_dxnx_line_starts_with(body, line_start, body_n,
194 "import " as *u8, 7) == 1 {
195 // Find opening quote
196 var p: i64 = line_start
197 while p < body_n {
198 if (body[p] as i64) == 0x22 { p = body_n + 1 } // '"'
199 if p <= body_n { if p < body_n { p = p + 1 } }
200 }
201 let q_start: i64 = if p > body_n then p - 1 else 0
202 if q_start > 0 {
203 // Find closing quote
204 var e: i64 = q_start + 1
205 while e < body_n {
206 if (body[e] as i64) == 0x22 { e = body_n + 1 }
207 if e <= body_n { if e < body_n { e = e + 1 } }
208 }
209 let real_e: i64 = if e > body_n then e - 1 else 0
210 if real_e > 0 {
211 let path_start: i64 = q_start + 1
212 let path_len: i64 = real_e - path_start
213 if path_len > 0 {
214 if path_len <= NX_DXNX_MAX_IMPORT_LEN {
215 if used + 1 + path_len <= out_buf_cap {
216 out_buf[used] = (path_len & 0xff) as u8
217 used = used + 1
218 var k: i64 = 0
219 while k < path_len {
220 out_buf[used + k] = body[path_start + k]
221 k = k + 1
222 }
223 used = used + path_len
224 count = count + 1
225 }
226 }
227 }
228 }
229 }
230 }
231 }
232 line_start = i + 1
233 }
234 i = i + 1
235 }
236
237 out_count[0] = count
238 out_used[0] = used
239 return NX_DXNX_OK
240}
241
242// Read one packed import at offset; returns next offset (or -1 if done).
243// Fills out_ptr / out_n with the import path bytes.
244func nx_doc_imports_iter_next(buf: *u8, buf_used: i64, off: i64,
245 out_ptr: *i64, out_n: *i64) -> i64 {
246 if off < 0 { return 0 - 1 }
247 if off >= buf_used { return 0 - 1 }
248 if (out_ptr as i64) == 0 { return 0 - 1 }
249 if (out_n as i64) == 0 { return 0 - 1 }
250 let len: i64 = buf[off] as i64
251 if off + 1 + len > buf_used { return 0 - 1 }
252 out_ptr[0] = (buf as i64) + off + 1
253 out_n[0] = len
254 return off + 1 + len
255}