code wiki / (root) / nx_docpub_render.nx

nx_docpub_render.nx source

↩ module page · 336 lines · 14298 B

1// nx_docpub_render.nx -- the DOC RENDER CORE for nx_docpub_site / nx_docpub_tree: turn a .nx source file 2// into a zero-JS HTML reference page, plus the byte/file/escape helpers both publishers need. 3// 4// WHY IT IS A SEPARATE MODULE. Both publishers imported nx_docgen_lib.nx expecting these ten symbols; 5// that library is a DIFFERENT organ -- legal clause templating (dg_clause_put / dg_tmpl_put / dg_merge / 6// dg_assemble) -- and never carried them. Verified genuinely UNWRITTEN before writing: `func dg_render` 7// has ZERO matches across all 19,985 files INCLUDING every .dupe-reconciled / .bak-* artifact, so this is 8// new code and not a re-implementation of something a dedupe deleted (the mistake made on nx_bom earlier). 9// The dg_ prefix is heavily overloaded -- ~30 unrelated organs define private dg_puts/dg_cat/dg_num -- so 10// these live in their own file rather than being bolted onto any of them. 11// 12// ZERO-JS BY CONSTRUCTION: nothing here ever emits a <script tag, and every byte taken from source goes 13// through dg_catb_esc. nx_docpub_tree T3 asserts exactly that (page contains `<div class=fn ` and does 14// NOT contain `<script`), so the guarantee is checked, not promised. 15// license_tier: ORIGINAL No hw writes (Rule 26). 16import "nx_syscalls.nx" 17 18const DPR_TAGCAP: i64 = 2048 19const DPR_MODE: i64 = 420 20 21func dgw(s: *u8) -> i64 { 22 var n: i64 = 0 23 while s[n] != (0 as u8) { n = n + 1 } 24 sys_write(1, s, n) 25 return 0 26} 27 28func dgn(v: i64) -> i64 { 29 let b: *u8 = sys_mmap(32) 30 let t: *u8 = sys_mmap(32) 31 var m: i64 = v 32 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 33 var k: i64 = 0 34 if m == 0 { t[0] = 48 as u8; k = 1 } 35 while m > 0 { t[k] = (48 + (m - (m / 10) * 10)) as u8; m = m / 10; k = k + 1 } 36 var i: i64 = 0 37 while i < k { b[i] = t[k - 1 - i]; i = i + 1 } 38 sys_write(1, b, k) 39 return 0 40} 41 42func dg_cat(dst: *u8, off: i64, s: *u8) -> i64 { 43 var o: i64 = off 44 var i: i64 = 0 45 while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 } 46 return o 47} 48 49func dg_catn(dst: *u8, off: i64, v: i64) -> i64 { 50 var o: i64 = off 51 var m: i64 = v 52 if m < 0 { dst[o] = 45 as u8; o = o + 1; m = 0 - m } 53 let t: *u8 = sys_mmap(32) 54 var k: i64 = 0 55 if m == 0 { t[0] = 48 as u8; k = 1 } 56 while m > 0 { t[k] = (48 + (m - (m / 10) * 10)) as u8; m = m / 10; k = k + 1 } 57 var i: i64 = 0 58 while i < k { dst[o] = t[k - 1 - i]; o = o + 1; i = i + 1 } 59 return o 60} 61 62// HTML-escape src[from..to) into dst at off. & < > " are the four that decide whether source text can 63// break out of the document -- a .nx file is FULL of angle brackets and quotes, so this is the guard that 64// makes emitting raw source safe rather than a stored-injection hole. 65func dg_catb_esc(dst: *u8, off: i64, src: *u8, from: i64, to: i64) -> i64 { 66 var o: i64 = off 67 var i: i64 = from 68 while i < to { 69 let c: i64 = src[i] as i64 70 if c == 38 { o = dg_cat(dst, o, "&amp;" as *u8) } else { 71 if c == 60 { o = dg_cat(dst, o, "&lt;" as *u8) } else { 72 if c == 62 { o = dg_cat(dst, o, "&gt;" as *u8) } else { 73 if c == 34 { o = dg_cat(dst, o, "&quot;" as *u8) } else { 74 dst[o] = src[i] 75 o = o + 1 76 } } } } 77 i = i + 1 78 } 79 return o 80} 81 82// Read up to cap bytes. Loops because one sys_read is not a file read; returns 0-1 when the path cannot 83// be opened so a caller can tell ABSENT from EMPTY. 84func dg_rdfile(path: *u8, buf: *u8, cap: i64) -> i64 { 85 let fd: i64 = sys_openat_rd(path) 86 if fd < 0 { return 0 - 1 } 87 var total: i64 = 0 88 var go: i64 = 1 89 while go == 1 { 90 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, cap - total) 91 if r <= 0 { go = 0 } else { 92 total = total + r 93 if total >= cap { go = 0 } 94 } 95 } 96 sys_close(fd) 97 return total 98} 99 100func dg_writefile(path: *u8, buf: *u8, len: i64) -> i64 { 101 let fd: i64 = sys_openat_wr(path, DPR_MODE) 102 if fd < 0 { return 0 - 1 } 103 var w: i64 = 0 104 while w < len { 105 let r: i64 = sys_write(fd, ((buf as i64) + w) as *u8, len - w) 106 if r <= 0 { w = len } else { w = w + r } 107 } 108 sys_close(fd) 109 return len 110} 111 112func dg_contains(hay: *u8, n: i64, needle: *u8) -> i64 { 113 var nl: i64 = 0 114 while needle[nl] != (0 as u8) { nl = nl + 1 } 115 if nl == 0 { return 1 } 116 if nl > n { return 0 } 117 var i: i64 = 0 118 while i <= n - nl { 119 var eq: i64 = 1 120 var k: i64 = 0 121 while k < nl { 122 if hay[i + k] != needle[k] { eq = 0; k = nl } else { k = k + 1 } 123 } 124 if eq == 1 { return 1 } 125 i = i + 1 126 } 127 return 0 128} 129 130// The first line of the leading `//` header comment, marker and padding stripped -- the one-line 131// description an index card shows. Returns 0 when the file opens with anything else, so a missing header 132// yields no card text rather than a misleading first line of code. 133func dg_first_doc_line(src: *u8, n: i64, out: *u8) -> i64 { 134 if n < 3 { return 0 } 135 if src[0] != (47 as u8) { return 0 } 136 if src[1] != (47 as u8) { return 0 } 137 var i: i64 = 2 138 var st: i64 = 0 139 while st == 0 { 140 if i >= n { st = 1 } else { 141 if src[i] == (32 as u8) { i = i + 1 } else { st = 1 } 142 } 143 } 144 var o: i64 = 0 145 var st2: i64 = 0 146 while st2 == 0 { 147 if i >= n { st2 = 1 } else { 148 if src[i] == (10 as u8) { st2 = 1 } else { 149 if src[i] == (13 as u8) { st2 = 1 } else { 150 if o < DPR_TAGCAP - 1 { out[o] = src[i]; o = o + 1 } 151 i = i + 1 152 } 153 } 154 } 155 } 156 out[o] = 0 as u8 157 return o 158} 159 160func dpr_isidc(c: i64) -> i64 { 161 if c == 95 { return 1 } 162 if c >= 97 { if c <= 122 { return 1 } } 163 if c >= 65 { if c <= 90 { return 1 } } 164 if c >= 48 { if c <= 57 { return 1 } } 165 return 0 166} 167 168// A declaration only counts at COLUMN ZERO. Matching `func ` anywhere would pick up the word inside 169// comments and strings and inflate the function count the index card prints. 170func dpr_atline(src: *u8, i: i64) -> i64 { 171 if i == 0 { return 1 } 172 if src[i - 1] == (10 as u8) { return 1 } 173 return 0 174} 175 176func dpr_matches(src: *u8, n: i64, i: i64, lit: *u8) -> i64 { 177 var k: i64 = 0 178 while lit[k] != (0 as u8) { 179 if i + k >= n { return 0 } 180 if src[i + k] != lit[k] { return 0 } 181 k = k + 1 182 } 183 return 1 184} 185 186// Render ONE .nx source into a complete zero-JS reference page: header comment as the lead, the import 187// list, and one card per top-level function carrying its real signature. cnt[0] receives the function 188// count (the index card shows it). Returns the output length. 189// 190// Everything from source passes through dg_catb_esc, and no branch can emit a <script tag, so the page 191// is safe by construction rather than by review -- nx_docpub_tree T3 checks both halves. 192func dg_render(src: *u8, n: i64, base: *u8, out: *u8, cnt: *i64) -> i64 { 193 cnt[0] = 0 194 var o: i64 = 0 195 o = dg_cat(out, o, "<!doctype html><html lang=en><head><meta charset=utf-8><meta name=viewport content='width=device-width,initial-scale=1'><title>" as *u8) 196 o = dg_cat(out, o, base) 197 o = dg_cat(out, o, " &mdash; reference</title><style>*{box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;margin:0;color:#1a2330;background:#fbfcfe;line-height:1.55}header.top{background:#0b2545;color:#fff;padding:11px 24px;font-size:.92rem;font-weight:600}header.top a{color:#fff;text-decoration:none}main{max-width:880px;margin:0 auto;padding:30px 24px 70px}h1{font-size:1.9rem;margin:.1em 0;letter-spacing:-.015em}p.lead{color:#475468;font-size:1.06rem;margin:.3em 0 1.4em}h2{font-size:1.05rem;color:#0b2545;margin:1.8em 0 .6em;border-bottom:1px solid #e6ebf3;padding-bottom:.3em}div.fn{background:#fff;border:1px solid #e6ebf3;border-radius:6px;padding:9px 12px;margin:7px 0}div.fn code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.86rem;color:#0b2545;white-space:pre-wrap;word-break:break-word}span.kw{color:#a3306e;font-weight:600}div.fndoc{color:#46566b;font-size:.85rem;margin:6px 0 0;line-height:1.45}ul.imp{list-style:none;padding:0;margin:0}ul.imp li{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.82rem;color:#46566b;padding:2px 0}@media(max-width:600px){main{padding:20px 16px 50px}h1{font-size:1.5rem}}</style></head><body>" as *u8) 198 o = dg_cat(out, o, "<header class=top><a href=wiki_index.html>&#9776; Nishi Docs</a> <span>/ " as *u8) 199 o = dg_cat(out, o, base) 200 o = dg_cat(out, o, "</span></header><main><h1>" as *u8) 201 o = dg_cat(out, o, base) 202 o = dg_cat(out, o, "</h1>" as *u8) 203 204 let lead: *u8 = sys_mmap(DPR_TAGCAP) 205 let ll: i64 = dg_first_doc_line(src, n, lead) 206 // The lead is the file's own first sentence -- UNLESS that line is a build directive. license_tier / 207 // expect_exit are instructions to the toolchain, not prose for a reader, and publishing them makes a 208 // generated page look dumped rather than written. 209 if ll > 0 { 210 if dg_contains(lead, ll, "license_tier" as *u8) == 0 { 211 if dg_contains(lead, ll, "expect_exit" as *u8) == 0 { 212 o = dg_cat(out, o, "<p class=lead>" as *u8) 213 o = dg_catb_esc(out, o, lead, 0, ll) 214 o = dg_cat(out, o, "</p>" as *u8) 215 } } 216 } 217 218 var nimp: i64 = 0 219 var i: i64 = 0 220 while i < n { 221 var ih: i64 = 0 222 if dpr_atline(src, i) == 1 { 223 if dpr_matches(src, n, i, "import " as *u8) == 1 { ih = 1 } 224 } 225 if ih == 1 { 226 var e: i64 = i 227 var st: i64 = 0 228 while st == 0 { 229 if e >= n { st = 1 } else { 230 if src[e] == (10 as u8) { st = 1 } else { e = e + 1 } 231 } 232 } 233 // CROSS-LINK each import to that module's own reference page. An import list that is plain 234 // text is a dead end; making it a link is what turns a pile of pages into a SITE. 235 if nimp == 0 { o = dg_cat(out, o, "<h2>Imports</h2><ul class=imp>" as *u8) } 236 var q1: i64 = i 237 var fq: i64 = 0 238 while q1 < e { if src[q1] == (34 as u8) { if fq == 0 { fq = q1 } } q1 = q1 + 1 } 239 if fq > 0 { 240 var q2: i64 = fq + 1 241 var s5: i64 = 0 242 while s5 == 0 { 243 if q2 >= e { s5 = 1 } else { 244 if src[q2] == (34 as u8) { s5 = 1 } else { q2 = q2 + 1 } 245 } 246 } 247 var mend: i64 = q2 248 if mend - fq > 4 { 249 if src[mend-3] == (46 as u8) { if src[mend-2] == (110 as u8) { if src[mend-1] == (120 as u8) { mend = mend - 3 } } } 250 } 251 o = dg_cat(out, o, "<li><a href=" as *u8) 252 o = dg_catb_esc(out, o, src, fq + 1, mend) 253 o = dg_cat(out, o, ".html>" as *u8) 254 o = dg_catb_esc(out, o, src, fq + 1, mend) 255 o = dg_cat(out, o, "</a></li>" as *u8) 256 } else { 257 o = dg_cat(out, o, "<li>" as *u8) 258 o = dg_catb_esc(out, o, src, i, e) 259 o = dg_cat(out, o, "</li>" as *u8) 260 } 261 nimp = nimp + 1 262 i = e 263 } 264 i = i + 1 265 } 266 if nimp > 0 { o = dg_cat(out, o, "</ul>" as *u8) } 267 268 var j: i64 = 0 269 while j < n { 270 var fh: i64 = 0 271 if dpr_atline(src, j) == 1 { 272 if dpr_matches(src, n, j, "func " as *u8) == 1 { fh = 1 } 273 } 274 if fh == 1 { 275 if cnt[0] == 0 { o = dg_cat(out, o, "<h2>Functions</h2>" as *u8) } 276 var ns: i64 = j + 5 277 var ne: i64 = ns 278 var s2: i64 = 0 279 while s2 == 0 { 280 if ne >= n { s2 = 1 } else { 281 if dpr_isidc(src[ne] as i64) == 1 { ne = ne + 1 } else { s2 = 1 } 282 } 283 } 284 var e2: i64 = j 285 var s3: i64 = 0 286 while s3 == 0 { 287 if e2 >= n { s3 = 1 } else { 288 if src[e2] == (10 as u8) { s3 = 1 } else { 289 if src[e2] == (123 as u8) { s3 = 1 } else { e2 = e2 + 1 } 290 } 291 } 292 } 293 // PER-FUNCTION DOC = the `//` comment line immediately above it. Emitted as class=fndoc so a 294 // reader gets the author's own sentence, not just a signature. BOILERPLATE IS FILTERED: a 295 // header carrying license_tier / expect_exit is a BUILD DIRECTIVE, not documentation, and 296 // leaking it into a published page is noise that makes the page look machine-dumped. 297 var dstart: i64 = 0 - 1 298 var dend: i64 = 0 - 1 299 if j >= 3 { 300 var ps: i64 = j - 1 301 var sb: i64 = 0 302 while sb == 0 { 303 if ps <= 0 { sb = 1 } else { 304 if src[ps - 1] == (10 as u8) { sb = 1 } else { ps = ps - 1 } 305 } 306 } 307 if ps + 1 < j { 308 if src[ps] == (47 as u8) { if src[ps + 1] == (47 as u8) { dstart = ps + 2; dend = j - 1 } } 309 } 310 } 311 o = dg_cat(out, o, "<div class=fn id=" as *u8) 312 o = dg_catb_esc(out, o, src, ns, ne) 313 o = dg_cat(out, o, "><code><span class=kw>func</span>" as *u8) 314 o = dg_catb_esc(out, o, src, j + 4, e2) 315 o = dg_cat(out, o, "</code>" as *u8) 316 if dstart > 0 { 317 let dl: i64 = dend - dstart 318 if dl > 0 { 319 let dbuf: *u8 = ((src as i64) + dstart) as *u8 320 if dg_contains(dbuf, dl, "license_tier" as *u8) == 0 { 321 if dg_contains(dbuf, dl, "expect_exit" as *u8) == 0 { 322 o = dg_cat(out, o, "<div class=fndoc>" as *u8) 323 o = dg_catb_esc(out, o, src, dstart, dend) 324 o = dg_cat(out, o, "</div>" as *u8) 325 } } 326 } 327 } 328 o = dg_cat(out, o, "</div>" as *u8) 329 cnt[0] = cnt[0] + 1 330 j = e2 331 } else { j = j + 1 } 332 } 333 334 o = dg_cat(out, o, "</main></body></html>" as *u8) 335 return o 336}