code wiki / (root) / nx_diagram.nx

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}