code wiki / hub / nx_doc_extractor_md.nx

nx_doc_extractor_md.nx source

↩ module page · 153 lines · 5572 B

1// nx_doc_extractor_md.nx -- V2.0 P-7: deep .md artifact parser. 2// 3// Reads .md body bytes; extracts heading_count (## §N) + code_block_count 4// (fenced blocks) for populating substrate signal richer than the V2.0 5// composes_count alone. 6// 7// COMPOSES: 8// nx_html_extract.nx_he_match_ci (re-used by P-6) 9// 10// COMPOSED BY: 11// wiki/nx_pipeline_walker (V3+ swap; V2.0 calls separately via main) 12// 13// V2.0 P-7 SCOPE: 14// - Count "## " or "### " or higher-depth heading line-starts -> heading_count 15// - Count code-fence pairs (```lang ... ```) -> code_block_count 16// - Bounded; line-walker; pure-function 17// 18// V3+ SCOPE (TODO): 19// - Identify [N]/[I]/[R] paragraph tags per NISHIDOC_STANDARD §3 20// - Extract section TOC (§N + title) 21// - Detect S-class doc structure (frontmatter + numbered sections) 22// - Count footnotes + references 23// 24// Status: V2.0 P-7. 2026-05-27. 25 26import "nx_syscalls.nx" 27import "nx_html_extract.nx" 28 29// ===== Sealed verdict surface (codes 3030-3034) ================================================= 30const NX_DXMD_OK: i64 = 0 31const NX_DXMD_BAD_INPUT: i64 = 3030 32const NX_DXMD_LOOP_BUDGET: i64 = 3031 33 34// ===== Named constants (M7) ================================================= 35const NX_DXMD_MAX_BODY: i64 = 4194304 // 4 MB cap (some specs are large) 36const NX_DXMD_LOOP_BUDGET_CAP: i64 = 10000000 37 38const NX_DXMD_ASCII_NL: i64 = 0x0A 39const NX_DXMD_ASCII_HASH: i64 = 0x23 40const NX_DXMD_ASCII_BACKTICK: i64 = 0x60 41 42// ===== Line-start: is this a Markdown heading? ================================================= 43// 44// Returns 1 if line at line_off (after optional leading whitespace) 45// starts with one or more '#' followed by a space. 46 47func nx_dxmd_line_is_heading(body: *u8, line_off: i64, body_n: i64) -> i64 { 48 if line_off < 0 { return 0 } 49 if line_off >= body_n { return 0 } 50 // Skip whitespace 51 var p: i64 = line_off 52 while p < body_n { 53 let c: i64 = body[p] as i64 54 if c == 0x20 { p = p + 1 } 55 if c == 0x09 { p = p + 1 } 56 if c != 0x20 { if c != 0x09 { p = body_n + 1 } } 57 } 58 if p > body_n { p = body_n } 59 if p >= body_n { return 0 } 60 if (body[p] as i64) != NX_DXMD_ASCII_HASH { return 0 } 61 // Consume any number of '#' chars 62 var q: i64 = p 63 while q < body_n { 64 if (body[q] as i64) == NX_DXMD_ASCII_HASH { q = q + 1 } 65 if q < body_n { 66 if (body[q] as i64) != NX_DXMD_ASCII_HASH { q = body_n + 1 } 67 } 68 } 69 if q > body_n { q = body_n } 70 if q >= body_n { return 0 } 71 // After # chars, expect a space 72 if (body[q] as i64) == 0x20 { return 1 } 73 return 0 74} 75 76// ===== Line-start: is this a code fence (```)? ================================================= 77// 78// Returns 1 if line begins with ``` (after optional leading whitespace). 79 80func nx_dxmd_line_is_fence(body: *u8, line_off: i64, body_n: i64) -> i64 { 81 if line_off < 0 { return 0 } 82 if line_off >= body_n { return 0 } 83 var p: i64 = line_off 84 while p < body_n { 85 let c: i64 = body[p] as i64 86 if c == 0x20 { p = p + 1 } 87 if c == 0x09 { p = p + 1 } 88 if c != 0x20 { if c != 0x09 { p = body_n + 1 } } 89 } 90 if p > body_n { p = body_n } 91 if p + 2 >= body_n { return 0 } 92 if (body[p] as i64) == NX_DXMD_ASCII_BACKTICK { 93 if (body[p + 1] as i64) == NX_DXMD_ASCII_BACKTICK { 94 if (body[p + 2] as i64) == NX_DXMD_ASCII_BACKTICK { return 1 } 95 } 96 } 97 return 0 98} 99 100// ===== Walk body counting headings + code blocks ================================================= 101// 102// heading_count = total # of "## ..." / "### ..." / etc lines 103// code_block_count = # of ```...``` PAIRS (open + close); /2 the fence count 104 105func nx_doc_extract_md_stats(body: *u8, body_n: i64, 106 out_headings: *i64, out_code_blocks: *i64) -> i64 { 107 if (body as i64) == 0 { return 0 - NX_DXMD_BAD_INPUT } 108 if (out_headings as i64) == 0 { return 0 - NX_DXMD_BAD_INPUT } 109 if (out_code_blocks as i64) == 0 { return 0 - NX_DXMD_BAD_INPUT } 110 if body_n < 0 { return 0 - NX_DXMD_BAD_INPUT } 111 if body_n > NX_DXMD_MAX_BODY { return 0 - NX_DXMD_BAD_INPUT } 112 out_headings[0] = 0 113 out_code_blocks[0] = 0 114 if body_n < 1 { return NX_DXMD_OK } 115 116 var headings: i64 = 0 117 var fences: i64 = 0 118 var inside_code: i64 = 0 119 120 var i: i64 = 0 121 var iter: i64 = 0 122 var line_start: i64 = 0 123 while i <= body_n { 124 if iter >= NX_DXMD_LOOP_BUDGET_CAP { return 0 - NX_DXMD_LOOP_BUDGET } 125 iter = iter + 1 126 let at_end: i64 = if i == body_n then 1 else 0 127 var is_eol: i64 = at_end 128 if at_end == 0 { 129 if (body[i] as i64) == NX_DXMD_ASCII_NL { is_eol = 1 } 130 } 131 if is_eol == 1 { 132 // Fence (track open/close toggle so we don't count headings inside code) 133 if nx_dxmd_line_is_fence(body, line_start, body_n) == 1 { 134 fences = fences + 1 135 if inside_code == 0 { inside_code = 1 } 136 if inside_code == 1 { if (fences % 2) == 0 { inside_code = 0 } } 137 } 138 // Heading (only outside code blocks) 139 if inside_code == 0 { 140 if nx_dxmd_line_is_heading(body, line_start, body_n) == 1 { 141 headings = headings + 1 142 } 143 } 144 line_start = i + 1 145 } 146 i = i + 1 147 } 148 149 out_headings[0] = headings 150 // Code block count = fences / 2 (pair open + close) 151 out_code_blocks[0] = fences / 2 152 return NX_DXMD_OK 153}