nx_substrate_diagram.nx source
↩ module page · 242 lines · 9153 B
1// nx_substrate_diagram.nx -- substrate-walker that BUILDS an
2// nx_diagram and emits any of 4 formats (Mermaid / DOT / ASCII / JSON).
3//
4// Refactored 2026-05-13 to consume the nx_diagram primitive instead
5// of hand-rolling Mermaid output. Reduces ~260 LOC to ~170 LOC and
6// gives the substrate-walker 4 output formats for free.
7//
8// DEFAULT EMITTER: Mermaid (backward-compatible with prior tool).
9// To emit other formats, edit the call near the bottom of main() --
10// nx_diagram_emit_dot(d) / nx_diagram_emit_ascii(d) / nx_diagram_emit_json(d).
11//
12// genealogy_id: mermaid_2015_javascript_diagram_text + graphviz_dot
13// + plain_text_diagram_lineage
14// lineage_id: graph_visualization_text_format
15// axioms: NX_AX_REL_TRANSITIVITY (depends-on transitive)
16
17// nx_safety_envelope:
18// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
19// sil_target: SIL1
20// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
21// verdict: NOT_YET_EVALUATED
22
23import "nx_syscalls.nx"
24import "nx_runtime.nx"
25import "nx_axioms.nx"
26import "nx_dirent.nx"
27import "nx_fcntl.nx"
28import "nx_tier.nx"
29import "nx_diagram.nx"
30
31const NX_DIA_MAX_MODULES: nx_idx = 400
32const NX_DIA_MAX_EDGES: nx_idx = 4096
33const NX_DIA_MAX_NAME: nx_size = 96
34const NX_DIA_DIR_BUF: nx_size = 16384
35
36// ===== filename pattern =================================================
37
38func dia_name_matches(name: *u8, name_len: nx_size) -> nx_int {
39 if name_len < 6 { return 0 }
40 if name[0] != 110 { return 0 } // 'n'
41 if name[1] != 120 { return 0 } // 'x'
42 if name[2] != 95 { return 0 } // '_'
43 if name[name_len - 3] != 46 { return 0 } // '.'
44 if name[name_len - 2] != 110 { return 0 } // 'n'
45 if name[name_len - 1] != 120 { return 0 } // 'x'
46 // reject _test.nx
47 if name_len >= 8 {
48 if name[name_len - 8] == 95 {
49 if name[name_len - 7] == 116 {
50 if name[name_len - 6] == 101 {
51 if name[name_len - 5] == 115 {
52 if name[name_len - 4] == 116 {
53 return 0
54 }
55 }
56 }
57 }
58 }
59 }
60 return 1
61}
62
63func dia_module_from_filename(name: *u8, name_len: nx_size, out: *u8) -> nx_size {
64 let mod_len: nx_size = name_len - 3
65 var i: nx_idx = 0
66 while i < mod_len {
67 out[i] = name[i]
68 i = i + 1
69 }
70 out[mod_len] = 0
71 return mod_len
72}
73
74// ===== directory walk -> Diagram nodes ==================================
75
76func dia_collect_into_diagram(dir_path: *u8, d: *Diagram, names_buf: *u8) -> nx_idx {
77 let fd: i64 = nx_openat(NX_AT_FDCWD, dir_path,
78 NX_O_RDONLY | NX_O_DIRECTORY, 0)
79 if fd < 0 { return -1 }
80 let buf: *u8 = sys_mmap(NX_DIA_DIR_BUF)
81 let dr_raw: *u8 = sys_mmap(NX_DIRENT_BYTES)
82 let dr: *NxDirent = dr_raw as *NxDirent
83 var added: nx_idx = 0
84
85 var batch: i64 = nx_dirent_read(fd, buf, NX_DIA_DIR_BUF)
86 while batch > 0 {
87 var off: i64 = 0
88 while off < batch {
89 let next_off: i64 = nx_dirent_iter(buf, off, batch, dr)
90 if next_off <= 0 { off = batch + 1 }
91 if off <= batch {
92 let nm_len: nx_size = nx_dirent_name_len(dr)
93 if dia_name_matches(dr.name, nm_len) == 1 {
94 if added < NX_DIA_MAX_MODULES {
95 let dst_addr: i64 = (names_buf as i64) + added * NX_DIA_MAX_NAME
96 let dst: *u8 = dst_addr as *u8
97 dia_module_from_filename(dr.name, nm_len, dst)
98 nx_diagram_add_node(d, dst, "" as *u8)
99 added = added + 1
100 }
101 }
102 off = next_off
103 }
104 }
105 batch = nx_dirent_read(fd, buf, NX_DIA_DIR_BUF)
106 }
107 sys_close(fd)
108 return added
109}
110
111// ===== import parse -> Diagram edges ====================================
112
113func dia_find_module_idx(d: *Diagram, names_buf: *u8, name: *u8, name_len: nx_size) -> nx_idx {
114 var mi: nx_idx = 0
115 while mi < d.n_nodes {
116 let cand_addr: i64 = (names_buf as i64) + mi * NX_DIA_MAX_NAME
117 let cand: *u8 = cand_addr as *u8
118 let cand_len: nx_size = strlen(cand)
119 if cand_len == name_len {
120 if strneq(cand, name, cand_len) == 1 { return mi }
121 }
122 mi = mi + 1
123 }
124 return -1
125}
126
127func dia_add_edges_for_module(d: *Diagram, names_buf: *u8, from_idx: nx_idx) -> nx_int {
128 let mod_addr: i64 = (names_buf as i64) + from_idx * NX_DIA_MAX_NAME
129 let module: *u8 = mod_addr as *u8
130
131 let path_buf: *u8 = sys_mmap(512)
132 let prefix: *u8 = "nxc2/runtime/" as *u8
133 let plen: nx_size = strlen(prefix)
134 var k: nx_idx = 0
135 while k < plen { path_buf[k] = prefix[k]; k = k + 1 }
136 let mod_len: nx_size = strlen(module)
137 var k2: nx_idx = 0
138 while k2 < mod_len { path_buf[plen + k2] = module[k2]; k2 = k2 + 1 }
139 path_buf[plen + mod_len] = 46
140 path_buf[plen + mod_len + 1] = 110
141 path_buf[plen + mod_len + 2] = 120
142 path_buf[plen + mod_len + 3] = 0
143
144 let out_len: *i64 = (sys_mmap(8)) as *i64
145 out_len[0] = 0
146 let buf: *u8 = sys_read_file(path_buf, out_len)
147 if (buf as i64) == 0 { return 0 }
148 let n: nx_size = out_len[0]
149
150 var pos: nx_idx = 0
151 var emitted: nx_int = 0
152 while pos < n {
153 var eol: nx_idx = pos
154 var done_l: nx_int = 0
155 while done_l == 0 {
156 if eol >= n { done_l = 1 }
157 if done_l == 0 {
158 if buf[eol] == 10 { done_l = 1 }
159 if done_l == 0 { eol = eol + 1 }
160 }
161 }
162 let line_len: nx_size = eol - pos
163
164 if line_len > 10 {
165 if buf[pos] == 105 { if buf[pos+1] == 109 {
166 if buf[pos+2] == 112 { if buf[pos+3] == 111 {
167 if buf[pos+4] == 114 { if buf[pos+5] == 116 {
168 if buf[pos+6] == 32 {
169 var p: nx_idx = pos + 7
170 var done_q: nx_int = 0
171 while done_q == 0 {
172 if p >= eol { done_q = 1 }
173 if done_q == 0 {
174 if buf[p] == 34 { done_q = 1 }
175 if done_q == 0 { p = p + 1 }
176 }
177 }
178 if p < eol {
179 if buf[p] == 34 {
180 p = p + 1
181 let mod_start: nx_idx = p
182 var done_m: nx_int = 0
183 while done_m == 0 {
184 if p >= eol { done_m = 1 }
185 if done_m == 0 {
186 if buf[p] == 34 { done_m = 1 }
187 if done_m == 0 {
188 if buf[p] == 46 { done_m = 1 }
189 if done_m == 0 { p = p + 1 }
190 }
191 }
192 }
193 let mod_end: nx_idx = p
194 let mname_len: nx_size = mod_end - mod_start
195 let mname: *u8 = ((buf as i64) + mod_start) as *u8
196 let to_idx: nx_idx = dia_find_module_idx(d, names_buf, mname, mname_len)
197 if to_idx >= 0 {
198 nx_diagram_add_edge(d, from_idx, to_idx, "" as *u8)
199 emitted = emitted + 1
200 }
201 }
202 }
203 }
204 }}
205 }}
206 }}
207 }
208 pos = eol + 1
209 }
210 return emitted
211}
212
213// ===== entry ============================================================
214
215func main() -> i64 {
216 let names_buf: *u8 = sys_mmap(NX_DIA_MAX_MODULES * NX_DIA_MAX_NAME)
217 let d: *Diagram = nx_diagram_new(NX_DIA_MAX_MODULES, NX_DIA_MAX_EDGES)
218
219 let n: nx_idx = dia_collect_into_diagram("nxc2/runtime" as *u8, d, names_buf)
220 if n < 0 {
221 println("error: cannot open runtime directory" as *u8)
222 return 1
223 }
224
225 var total_edges: nx_int = 0
226 var j: nx_idx = 0
227 while j < d.n_nodes {
228 total_edges = total_edges + dia_add_edges_for_module(d, names_buf, j)
229 j = j + 1
230 }
231
232 // Default emitter: Mermaid (backward-compatible).
233 // Swap to nx_diagram_emit_dot / _ascii / _json for other formats.
234 nx_diagram_emit_mermaid(d)
235
236 print(" %% " as *u8)
237 print_i64(d.n_nodes)
238 print(" modules, " as *u8)
239 print_i64(total_edges)
240 println(" import edges" as *u8)
241 return 0
242}