nx_build_doc.nx source
↩ module page · 222 lines · 7227 B
1// nx_build_doc.nx -- render a single .md document into final HTML.
2//
3// The atomic build unit for nx_build_site (the bits-up replacement
4// for nishi-pages/build/build.py). Composes:
5//
6// nx_frontmatter -- split "---YAML---body"
7// nx_yaml_subset -- parse the frontmatter YAML metadata
8// nx_md_render -- markdown body -> HTML
9// nx_http_template -- {{ var }} + {{ var | raw }} substitution
10//
11// Pipeline for one .md doc:
12// 1. Read source bytes
13// 2. Split frontmatter -> (yaml_bytes, body_bytes)
14// 3. Parse yaml metadata into NxYamlDoc (title, date, layout, etc.)
15// 4. Render markdown body -> body_html
16// 5. Apply inner template (post.html or page.html) substituting
17// page.title, page.date, page.author, body (raw)
18// 6. Apply outer layout (layout.html) substituting site.*, page.*,
19// content (raw) where content = inner template output
20// 7. Caller writes the final HTML wherever it wants
21//
22// nx_safety_envelope:
23// intended_use: render one md doc end-to-end for nx_build_site
24// sil_target: SIL2
25// evidence: bench/nx_build_doc_smoke.nx round-trips a known
26// fixture and asserts the output bytes contain the
27// expected substituted values
28// verdict: NOT_YET_EVALUATED -- pending smoke
29
30import "nx_syscalls_x86_64.nx"
31import "nx_yaml_subset.nx"
32import "nx_frontmatter.nx"
33import "nx_http_template.nx"
34
35// ---- Sealed verdicts ----
36
37const NX_BUILD_DOC_OK: i64 = 0
38const NX_BUILD_DOC_BAD_INPUT: i64 = 1
39const NX_BUILD_DOC_FRONTMATTER_FAIL: i64 = 2
40const NX_BUILD_DOC_YAML_FAIL: i64 = 3
41const NX_BUILD_DOC_INNER_TMPL_FAIL: i64 = 4
42const NX_BUILD_DOC_OUTER_TMPL_FAIL: i64 = 5
43const NX_BUILD_DOC_OUT_TOO_SMALL: i64 = 6
44const NX_BUILD_DOC_VERDICT_N: i64 = 7
45
46// ---- Helpers ----
47
48func _bd_bcopy(dst: *u8, src: *u8, n: i64) -> i64 {
49 var i: i64 = 0
50 while i < n { dst[i] = src[i]; i = i + 1 }
51 return 0
52}
53
54func _bd_strlen(s: *u8, cap: i64) -> i64 {
55 var i: i64 = 0
56 while i < cap {
57 if s[i] == 0 { return i }
58 i = i + 1
59 }
60 return cap
61}
62
63// Append bytes to a buffer with bounds check. Returns new offset.
64func _bd_append(dst: *u8, off: i64, cap: i64,
65 src: *u8, src_n: i64) -> i64 {
66 if off + src_n > cap { return -1 }
67 var i: i64 = 0
68 while i < src_n {
69 dst[off + i] = src[i]
70 i = i + 1
71 }
72 return off + src_n
73}
74
75// ---- Build a template var table from the frontmatter YAML + body html ----
76//
77// Vars used by post.html / page.html / layout.html:
78// site.title, site.tagline, site.author, site.description
79// page.title, page.description, page.date, page.author
80// body (the rendered markdown HTML, used inside post.html / page.html)
81// content (the inner template's output, used inside layout.html)
82//
83// nx_http_template uses flat var names; "site.title" is the literal
84// var name, NOT a hierarchical lookup. Caller assembles flat names
85// in the names arena.
86
87const NX_BD_MAX_VARS: i64 = 16
88const NX_BD_NAMES_CAP: i64 = 512
89const NX_BD_VALUES_CAP: i64 = 16384 // 16K for body html
90
91struct NxBuildVars {
92 tv: *NxTemplateVars,
93 names: *u8,
94 name_offs: *i64,
95 name_lens: *i64,
96 values: *u8,
97 value_offs: *i64,
98 value_lens: *i64,
99 capacity: i64,
100 count: i64,
101}
102
103const NX_BUILD_VARS_BYTES: i64 = 72
104
105// Pre-allocate var-table arenas. Caller's responsibility.
106func nx_build_vars_init(bv: *NxBuildVars) -> i64 {
107 bv.tv = sys_mmap(NX_TEMPLATE_VARS_BYTES) as *NxTemplateVars
108 bv.names = sys_mmap(NX_BD_NAMES_CAP)
109 bv.name_offs = sys_mmap(NX_BD_MAX_VARS * 8) as *i64
110 bv.name_lens = sys_mmap(NX_BD_MAX_VARS * 8) as *i64
111 bv.values = sys_mmap(NX_BD_VALUES_CAP)
112 bv.value_offs = sys_mmap(NX_BD_MAX_VARS * 8) as *i64
113 bv.value_lens = sys_mmap(NX_BD_MAX_VARS * 8) as *i64
114 bv.capacity = NX_BD_MAX_VARS
115 bv.count = 0
116 return 0
117}
118
119// Add a (name, value) pair. Names + values arenas grow append-only.
120// Returns 0 on OK, -1 on overflow.
121func nx_build_vars_add(bv: *NxBuildVars,
122 name: *u8, name_n: i64,
123 value: *u8, value_n: i64) -> i64 {
124 if bv.count >= bv.capacity { return -1 }
125
126 // Append name.
127 var names_off: i64 = 0
128 var i: i64 = 0
129 while i < bv.count {
130 names_off = names_off + bv.name_lens[i]
131 i = i + 1
132 }
133 if names_off + name_n > NX_BD_NAMES_CAP { return -1 }
134 var j: i64 = 0
135 while j < name_n {
136 bv.names[names_off + j] = name[j]
137 j = j + 1
138 }
139 bv.name_offs[bv.count] = names_off
140 bv.name_lens[bv.count] = name_n
141
142 // Append value.
143 var values_off: i64 = 0
144 var k: i64 = 0
145 while k < bv.count {
146 values_off = values_off + bv.value_lens[k]
147 k = k + 1
148 }
149 if values_off + value_n > NX_BD_VALUES_CAP { return -1 }
150 var m: i64 = 0
151 while m < value_n {
152 bv.values[values_off + m] = value[m]
153 m = m + 1
154 }
155 bv.value_offs[bv.count] = values_off
156 bv.value_lens[bv.count] = value_n
157
158 bv.count = bv.count + 1
159 return 0
160}
161
162// Wire the underlying NxTemplateVars to the freshly-built arenas.
163func nx_build_vars_seal(bv: *NxBuildVars) -> i64 {
164 return nx_template_vars_init(bv.tv,
165 bv.names, bv.name_offs, bv.name_lens,
166 bv.values, bv.value_offs, bv.value_lens,
167 bv.count)
168}
169
170// ---- Per-doc add: lift yaml frontmatter entries into the var table ----
171//
172// Each YAML entry "title: foo" becomes "page.title -> foo" in the
173// template var table. Caller passes the page. prefix and the source
174// YAML doc.
175
176func nx_build_vars_add_yaml(bv: *NxBuildVars,
177 prefix: *u8, prefix_n: i64,
178 yaml: *NxYamlDoc) -> i64 {
179 var i: i64 = 0
180 while i < yaml.n_entries {
181 let key_off: *i64 = sys_mmap(16) as *i64
182 let key_len: *i64 = sys_mmap(16) as *i64
183 let val_off: *i64 = sys_mmap(16) as *i64
184 let val_len: *i64 = sys_mmap(16) as *i64
185 nx_yaml_key_at(yaml, i, key_off, key_len)
186 nx_yaml_value_at(yaml, i, val_off, val_len)
187
188 // Build full name = prefix + key bytes.
189 let full_name: *u8 = sys_mmap(NX_YAML_MAX_KEY_LEN + 32)
190 var p: i64 = 0
191 var j: i64 = 0
192 while j < prefix_n {
193 full_name[p] = prefix[j]
194 p = p + 1
195 j = j + 1
196 }
197 var k: i64 = 0
198 while k < key_len[0] {
199 full_name[p] = yaml.keys[key_off[0] + k]
200 p = p + 1
201 k = k + 1
202 }
203
204 let val_ptr: *u8 = (yaml.values as i64 + val_off[0]) as *u8
205 if nx_build_vars_add(bv, full_name, p, val_ptr, val_len[0]) != 0 {
206 return -1
207 }
208 i = i + 1
209 }
210 return 0
211}
212
213// ---- Apply a template ----
214
215func nx_build_apply_template(tmpl: *u8, tmpl_n: i64,
216 bv: *NxBuildVars,
217 out: *u8, out_cap: i64,
218 out_n_slot: *i64) -> i64 {
219 nx_build_vars_seal(bv)
220 return nx_http_template_render(tmpl, tmpl_n, bv.tv,
221 out, out_cap, out_n_slot)
222}