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}