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, "&" as *u8) } else {
71 if c == 60 { o = dg_cat(dst, o, "<" as *u8) } else {
72 if c == 62 { o = dg_cat(dst, o, ">" as *u8) } else {
73 if c == 34 { o = dg_cat(dst, o, """ 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, " — 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>☰ 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}