code wiki / _hdl_build / nx_inbook_search.nx

nx_inbook_search.nx source

↩ module page · 145 lines · 7687 B

1// nx_inbook_search.nx -- SC21: sovereign IN-BOOK full-text search (find-in-book) over a reader book's 2// extracted chapters. Companion to SC20 nx_book_search (library-level): this searches WITHIN one book, 3// ranks its chapters by match count, and returns the best chapter + a context SNIPPET around the first hit. 4// Reuses nx_research_extract (re_count / re_find). Case-insensitive (lowercased mirror). Runs over the 5// in-repo extracted chapters (reader/<slug>/chapN.txt) -> NO live-NAS / sqlite / server dependency, no 6// collision with the ark-beat loop. Self-gated by KATs over the real "Dissolution" book text. 7// license_tier: ORIGINAL 8import "nx_research_extract.nx" // re_count / re_find 9import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 10import "nx_syscalls.nx" 11const IB_MAGIC_262144: i64 = 262144 12const IB_MAGIC_8192: i64 = 8192 13 14const IB_DIR: *u8 = "knowledge/staging/media/reader/dissolution_wotsq_b1/" 15const IB_LOG: *u8 = "knowledge/status/inbook_search.log" 16const IB_MAXCH: i64 = 256 17 18func ib_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 19// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 20// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 21// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 22// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 23func ib_n(v: i64) -> i64 { nxi_out(v); return 0 } 24func ib_fp(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 } 25// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 26// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 27// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 28// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 29func ib_fn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 30func ib_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c } 31 32func ib_read(path: *u8, buf: *u8, cap: i64) -> i64 { 33 let fd: i64 = sys_openat_rd(path) 34 if fd < 0 { return 0 - 1 } 35 var n: i64 = 0; var go: i64 = 1 36 while go == 1 { let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap - 1 - n); if r <= 0 { go = 0 } else { n = n + r } if n >= cap - 1 { go = 0 } } 37 sys_close(fd) 38 return n 39} 40 41// copy NUL-terminated src into dst at offset `at`; return new offset. (src indexed as a PARAM -- avoids 42// the const-*u8 inline-index miscompile landmine.) 43func ib_cpstr(dst: *u8, at: i64, src: *u8) -> i64 { var w: i64 = at; var d: i64 = 0; while src[d] != (0 as u8) { dst[w] = src[d]; w = w + 1; d = d + 1 } return w } 44 45// build "<IB_DIR>chap<i>.txt" into out; return length. 46func ib_chap_path(i: i64, out: *u8) -> i64 { 47 var w: i64 = ib_cpstr(out, 0, IB_DIR) 48 out[w] = 99 as u8; w = w + 1 // c 49 out[w] = 104 as u8; w = w + 1 // h 50 out[w] = 97 as u8; w = w + 1 // a 51 out[w] = 112 as u8; w = w + 1 // p 52 // decimal i 53 let tmp: *u8 = sys_mmap(28); var m: i64 = i; var k: i64 = 0 54 if m == 0 { tmp[0] = 48 as u8; k = 1 } 55 while m > 0 { tmp[k] = (48 + (m%10)) as u8; m = m/10; k = k + 1 } 56 var j: i64 = 0; while j < k { out[w] = tmp[k-1-j]; w = w + 1; j = j + 1 } 57 out[w] = 46 as u8; w = w + 1 // . 58 out[w] = 116 as u8; w = w + 1 // t 59 out[w] = 120 as u8; w = w + 1 // x 60 out[w] = 116 as u8; w = w + 1 // t 61 out[w] = 0 as u8 62 return w 63} 64 65// search a single lowercased term across all chapters; sets bestch/bestcount/firstoff via out array 66// out[0]=best chapter idx, out[1]=match count in it, out[2]=byte offset of first hit in it, out[3]=total hits. 67func ib_search(buf: *u8, lc: *u8, pathbuf: *u8, term: *u8, out: *i64) -> i64 { 68 var bestch: i64 = 0 - 1; var bestcnt: i64 = 0; var bestoff: i64 = 0; var totalhits: i64 = 0 69 var i: i64 = 0 70 while i < IB_MAXCH { 71 ib_chap_path(i, pathbuf) 72 let n: i64 = ib_read(pathbuf, buf, IB_MAGIC_262144) 73 if n < 0 { i = IB_MAXCH } // no more chapters -> stop probing 74 else { 75 // lowercase mirror 76 var j: i64 = 0 77 while j < n { lc[j] = ib_lc(buf[j] as i64) as u8; j = j + 1 } 78 let c: i64 = re_count(lc, n, term) 79 if c > 0 { 80 totalhits = totalhits + c 81 if c > bestcnt { bestcnt = c; bestch = i; bestoff = re_find(lc, n, term) } 82 } 83 i = i + 1 84 } 85 } 86 out[0] = bestch; out[1] = bestcnt; out[2] = bestoff; out[3] = totalhits 87 return bestch 88} 89 90// print a context snippet from the ORIGINAL (cased) chapter text around offset off. 91func ib_snippet( chidx: i64, off: i64, buf: *u8, lc: *u8, pathbuf: *u8 ) -> i64 { 92 ib_chap_path(chidx, pathbuf) 93 let n: i64 = ib_read(pathbuf, buf, IB_MAGIC_262144) 94 if n <= 0 { return 0 } 95 var a: i64 = off - 24; if a < 0 { a = 0 } 96 var b: i64 = off + 70; if b > n { b = n } 97 // collapse newlines to spaces for a clean one-line snippet 98 var k: i64 = a 99 while k < b { if buf[k] == (10 as u8) { ib_p(" " as *u8) } else { sys_write(1, (buf as i64 + k) as *u8, 1) } k = k + 1 } 100 return 0 101} 102 103func main() -> i64 { 104 ib_p("=== nx_inbook_search: SC21 find-in-book over extracted chapters (Dissolution) ===\n" as *u8) 105 let buf: *u8 = sys_mmap(IB_MAGIC_262144) 106 let lc: *u8 = sys_mmap(IB_MAGIC_262144) 107 let pathbuf: *u8 = sys_mmap(IB_MAGIC_8192) 108 let out: *i64 = sys_mmap(64) as *i64 109 110 var pass: i64 = 0 111 112 // KAT 1: "gromph" (the archmage protagonist) -- must hit 113 ib_search(buf, lc, pathbuf, "gromph" as *u8, out) 114 ib_p(" q=gromph -> chap" as *u8); ib_n(out[0]); ib_p(" hits=" as *u8); ib_n(out[3]); ib_p(" snippet: ..." as *u8) 115 if out[0] >= 0 { ib_snippet(out[0], out[2], buf, lc, pathbuf) } ib_p("...\n" as *u8) 116 if out[0] >= 0 { if out[3] > 0 { pass = pass + 1 } } 117 118 // KAT 2: "menzoberranzan" (the drow city) -- must hit 119 ib_search(buf, lc, pathbuf, "menzoberranzan" as *u8, out) 120 ib_p(" q=menzoberranzan-> chap" as *u8); ib_n(out[0]); ib_p(" hits=" as *u8); ib_n(out[3]); ib_p(" snippet: ..." as *u8) 121 if out[0] >= 0 { ib_snippet(out[0], out[2], buf, lc, pathbuf) } ib_p("...\n" as *u8) 122 if out[0] >= 0 { if out[3] > 0 { pass = pass + 1 } } 123 124 // KAT 3: "drow" -- must hit, many times 125 ib_search(buf, lc, pathbuf, "drow" as *u8, out) 126 ib_p(" q=drow -> chap" as *u8); ib_n(out[0]); ib_p(" hits=" as *u8); ib_n(out[3]); ib_p("\n" as *u8) 127 if out[0] >= 0 { if out[3] > 0 { pass = pass + 1 } } 128 129 // KAT 4 (negative control): a nonsense string -- must NOT hit 130 ib_search(buf, lc, pathbuf, "zzqxqwk" as *u8, out) 131 ib_p(" q=zzqxqwk (neg) -> " as *u8); if out[0] < 0 { ib_p("(none = correct)\n" as *u8); pass = pass + 1 } else { ib_p("FALSE HIT chap" as *u8); ib_n(out[0]); ib_p("\n" as *u8) } 132 133 ib_p(" gate: pass=" as *u8); ib_n(pass); ib_p("/4 (3 positive find-in-book + 1 negative control)\n" as *u8) 134 135 let lf: i64 = sys_openat_append(IB_LOG, 0x1a4) 136 if pass == 4 { 137 if lf >= 0 { ib_fp(lf, "INBOOK-SEARCH verdict=GREEN pass=4/4 epoch=" as *u8); ib_fn(lf, sys_now_realtime_sec()); ib_fp(lf, "\n" as *u8); sys_close(lf) } 138 ib_p("INBOOK-SEARCH verdict=GREEN (sovereign find-in-book ranks chapters + returns snippets; SC21 engine)\n" as *u8) 139 sys_exit(0); return 0 140 } 141 if lf >= 0 { ib_fp(lf, "INBOOK-SEARCH verdict=RED pass=" as *u8); ib_fn(lf, pass); ib_fp(lf, "/4\n" as *u8); sys_close(lf) } 142 ib_p("INBOOK-SEARCH verdict=RED (a KAT failed)\n" as *u8) 143 sys_exit(1) 144 return 1 145}