nx_diagram.nx source
↩ module page · 292 lines · 9302 B
1// nx_diagram.nx -- substrate-native diagram primitive.
2//
3// First-class diagrams in NishiLang. Data model + 4 output emitters
4// from a single in-memory representation.
5//
6// Why this exists (per user 2026-05-13):
7// "can we add mermaid like capabilities to nishi lang"
8//
9// The substrate should NOT depend on external renderers (Mermaid.live,
10// Graphviz CLI, etc.). Per Wolfram-IP-free + sovereign-substrate
11// doctrine: ship our own. Mermaid stays as a COMPATIBILITY output
12// for tools that already render it; ASCII is the sovereign default
13// (terminal-renderable, zero external dep).
14//
15// API:
16// nx_diagram_new(max_nodes, max_edges) -> *Diagram
17// nx_diagram_add_node(d, label, summary) -> i64 (returns node index)
18// nx_diagram_add_edge(d, from_idx, to_idx, label)
19// nx_diagram_emit_mermaid(d) -> writes graph TD to stdout
20// nx_diagram_emit_ascii(d) -> terminal-friendly layered render
21// nx_diagram_emit_dot(d) -> Graphviz DOT (interop)
22// nx_diagram_emit_json(d) -> machine-readable JSONL
23//
24// Plain-English: a substrate primitive that lets any NishiLang program
25// build a diagram in memory then export it in any of 4 formats. The
26// ASCII format runs in any terminal, so the substrate can VISUALIZE
27// itself without needing a graphical tool.
28//
29// genealogy_id: mermaid_2015_js_lib + graphviz_dot_1991 + ascii_art
30// + plain_text_diagram_lineage
31// lineage_id: in_memory_diagram + multi_format_emit
32// axioms: NX_AX_REL_TRANSITIVITY (edges define a relation;
33// diagram emit preserves source structure)
34
35// nx_safety_envelope:
36// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
37// sil_target: SIL1
38// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
39// verdict: NOT_YET_EVALUATED
40
41import "nx_syscalls.nx"
42import "nx_runtime.nx"
43import "nx_axioms.nx"
44import "nx_tier.nx"
45
46const NX_DIAG_MAX_LABEL: nx_size = 96
47
48struct DiagramNode {
49 label: *u8, // null-terminated id
50 summary: *u8, // plain-English summary
51}
52
53struct DiagramEdge {
54 from_idx: nx_idx,
55 to_idx: nx_idx,
56 label: *u8, // optional, may be ""
57}
58
59struct Diagram {
60 nodes: *DiagramNode,
61 n_nodes: nx_idx,
62 max_nodes: nx_idx,
63 edges: *DiagramEdge,
64 n_edges: nx_idx,
65 max_edges: nx_idx,
66}
67
68// Sizes in bytes for struct allocation. Stay i64 (literal byte
69// counts; layout-dependent, not tier-swappable).
70const NX_DIAG_NODE_BYTES: i64 = 16
71const NX_DIAG_EDGE_BYTES: i64 = 24
72
73// ===== Constructors =====================================================
74
75func nx_diagram_new(max_nodes: nx_idx, max_edges: nx_idx) -> *Diagram {
76 let d_raw: *u8 = sys_mmap(56)
77 let d: *Diagram = d_raw as *Diagram
78 d.nodes = (sys_mmap(max_nodes * NX_DIAG_NODE_BYTES)) as *DiagramNode
79 d.n_nodes = 0
80 d.max_nodes = max_nodes
81 d.edges = (sys_mmap(max_edges * NX_DIAG_EDGE_BYTES)) as *DiagramEdge
82 d.n_edges = 0
83 d.max_edges = max_edges
84 return d
85}
86
87func nx_diagram_node_at(d: *Diagram, i: nx_idx) -> *DiagramNode {
88 return ((d.nodes as i64) + i * NX_DIAG_NODE_BYTES) as *DiagramNode
89}
90
91func nx_diagram_edge_at(d: *Diagram, i: nx_idx) -> *DiagramEdge {
92 return ((d.edges as i64) + i * NX_DIAG_EDGE_BYTES) as *DiagramEdge
93}
94
95// ===== add ============================================================
96
97func nx_diagram_add_node(d: *Diagram, label: *u8, summary: *u8) -> nx_idx {
98 if d.n_nodes >= d.max_nodes { return -1 }
99 let n: *DiagramNode = nx_diagram_node_at(d, d.n_nodes)
100 n.label = label
101 n.summary = summary
102 let idx: nx_idx = d.n_nodes
103 d.n_nodes = idx + 1
104 return idx
105}
106
107func nx_diagram_add_edge(d: *Diagram, from_idx: nx_idx, to_idx: nx_idx,
108 label: *u8) -> nx_idx {
109 if d.n_edges >= d.max_edges { return -1 }
110 let e: *DiagramEdge = nx_diagram_edge_at(d, d.n_edges)
111 e.from_idx = from_idx
112 e.to_idx = to_idx
113 e.label = label
114 let idx: nx_idx = d.n_edges
115 d.n_edges = idx + 1
116 return idx
117}
118
119// ===== Mermaid emitter (compatibility for external tools) ==============
120
121func nx_diagram_emit_mermaid(d: *Diagram) -> i64 {
122 println("graph TD" as *u8)
123 var i: nx_idx = 0
124 while i < d.n_nodes {
125 let n: *DiagramNode = nx_diagram_node_at(d, i)
126 print(" " as *u8)
127 print(n.label)
128 if n.summary[0] != 0 {
129 print("[\"" as *u8)
130 print(n.label)
131 print(": " as *u8)
132 print(n.summary)
133 print("\"]" as *u8)
134 }
135 println("" as *u8)
136 i = i + 1
137 }
138 var j: nx_idx = 0
139 while j < d.n_edges {
140 let e: *DiagramEdge = nx_diagram_edge_at(d, j)
141 let nf: *DiagramNode = nx_diagram_node_at(d, e.from_idx)
142 let nt: *DiagramNode = nx_diagram_node_at(d, e.to_idx)
143 print(" " as *u8)
144 print(nf.label)
145 if e.label[0] != 0 {
146 print(" -- \"" as *u8)
147 print(e.label)
148 print("\" --> " as *u8)
149 }
150 if e.label[0] == 0 {
151 print(" --> " as *u8)
152 }
153 print(nt.label)
154 println("" as *u8)
155 j = j + 1
156 }
157 return 0
158}
159
160// ===== Graphviz DOT emitter (interop with dot / xdot / etc.) ===========
161
162func nx_diagram_emit_dot(d: *Diagram) -> i64 {
163 println("digraph substrate {" as *u8)
164 println(" rankdir=TD;" as *u8)
165 println(" node [shape=box];" as *u8)
166 var i: nx_idx = 0
167 while i < d.n_nodes {
168 let n: *DiagramNode = nx_diagram_node_at(d, i)
169 print(" " as *u8)
170 print(n.label)
171 if n.summary[0] != 0 {
172 print(" [label=\"" as *u8)
173 print(n.label)
174 print("\\n" as *u8)
175 print(n.summary)
176 print("\"]" as *u8)
177 }
178 println(";" as *u8)
179 i = i + 1
180 }
181 var j: nx_idx = 0
182 while j < d.n_edges {
183 let e: *DiagramEdge = nx_diagram_edge_at(d, j)
184 let nf: *DiagramNode = nx_diagram_node_at(d, e.from_idx)
185 let nt: *DiagramNode = nx_diagram_node_at(d, e.to_idx)
186 print(" " as *u8)
187 print(nf.label)
188 print(" -> " as *u8)
189 print(nt.label)
190 if e.label[0] != 0 {
191 print(" [label=\"" as *u8)
192 print(e.label)
193 print("\"]" as *u8)
194 }
195 println(";" as *u8)
196 j = j + 1
197 }
198 println("}" as *u8)
199 return 0
200}
201
202// ===== JSON emitter (machine-readable) =================================
203
204func nx_diagram_emit_json(d: *Diagram) -> i64 {
205 print("{\"nodes\":[" as *u8)
206 var i: nx_idx = 0
207 while i < d.n_nodes {
208 let n: *DiagramNode = nx_diagram_node_at(d, i)
209 if i > 0 { print("," as *u8) }
210 print("{\"label\":\"" as *u8)
211 print(n.label)
212 print("\",\"summary\":\"" as *u8)
213 print(n.summary)
214 print("\"}" as *u8)
215 i = i + 1
216 }
217 print("],\"edges\":[" as *u8)
218 var j: nx_idx = 0
219 while j < d.n_edges {
220 let e: *DiagramEdge = nx_diagram_edge_at(d, j)
221 if j > 0 { print("," as *u8) }
222 print("{\"from\":" as *u8)
223 print_i64(e.from_idx)
224 print(",\"to\":" as *u8)
225 print_i64(e.to_idx)
226 print(",\"label\":\"" as *u8)
227 print(e.label)
228 print("\"}" as *u8)
229 j = j + 1
230 }
231 println("]}" as *u8)
232 return 0
233}
234
235// ===== ASCII emitter (sovereign, terminal-renderable) ==================
236//
237// Simple layered layout: assign each node a "depth" = longest path
238// from any root. Render nodes in rows, edges as arrows between them.
239// For very large graphs this is a list-style summary. Compact and
240// readable in any terminal; zero external dependency.
241
242func nx_diagram_emit_ascii(d: *Diagram) -> i64 {
243 println("===== ASCII Diagram (sovereign render) =====" as *u8)
244 print("nodes: " as *u8)
245 print_i64(d.n_nodes)
246 print(" edges: " as *u8)
247 print_i64(d.n_edges)
248 println("" as *u8)
249 println("" as *u8)
250
251 // Flat node listing in registration order (robust to cycles in
252 // the dependency graph, which would infinite-loop a recursive
253 // depth-first layout).
254 println("Nodes:" as *u8)
255 var i: nx_idx = 0
256 while i < d.n_nodes {
257 let n: *DiagramNode = nx_diagram_node_at(d, i)
258 print(" [" as *u8)
259 print(n.label)
260 print("]" as *u8)
261 if n.summary[0] != 0 {
262 print(" -- " as *u8)
263 print(n.summary)
264 }
265 println("" as *u8)
266 i = i + 1
267 }
268 println("" as *u8)
269
270 // Edge list.
271 if d.n_edges > 0 {
272 println("Edges:" as *u8)
273 var j: nx_idx = 0
274 while j < d.n_edges {
275 let e: *DiagramEdge = nx_diagram_edge_at(d, j)
276 let nf: *DiagramNode = nx_diagram_node_at(d, e.from_idx)
277 let nt: *DiagramNode = nx_diagram_node_at(d, e.to_idx)
278 print(" " as *u8)
279 print(nf.label)
280 print(" --> " as *u8)
281 print(nt.label)
282 if e.label[0] != 0 {
283 print(" (" as *u8)
284 print(e.label)
285 print(")" as *u8)
286 }
287 println("" as *u8)
288 j = j + 1
289 }
290 }
291 return 0
292}