code wiki / hub / nx_doc_extractor_nx.nx

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}