// tools/hako_check/render/graphviz.hako - GraphvizRenderBox (MVP) // Render minimal DOT graph from one or more Analysis IRs. using selfhost.shared.common.string_helpers as Str static box GraphvizRenderBox { render_multi(irs) { // irs: ArrayBox of IR Map print("digraph Hako {") // Internal key names for node/edge key lists (avoid collisions with user names) local NK_KEY = "__graphviz_nodes__" local EK_KEY = "__graphviz_edges__" // optional graph attributes (kept minimal) // print(" rankdir=LR;") // Node and edge sets to avoid duplicates local nodes = new MapBox() local edges = new MapBox() if irs != null { local gi = 0 while gi < irs.size() { local ir = irs.get(gi) me._render_ir(ir, nodes, edges) gi = gi + 1 } } // Build clusters by box: group nodes whose name looks like Box.method/arity local groups = new MapBox() local group_keys = new ArrayBox() local nk = nodes.get(NK_KEY) if nk == null { nk = new ArrayBox() } if nk != null { local i = 0 while i < nk.size() { local name = nk.get(i) local dot = name.indexOf(".") if dot > 0 { local box_name = name.substring(0, dot) local gkey = "cluster_" + box_name local arr = groups.get(gkey) if arr == null { arr = new ArrayBox(); groups.set(gkey, arr); group_keys.push(gkey) } // dedup in group local seen = 0; local j=0; while j < arr.size() { if arr.get(j) == name { seen = 1; break } j = j + 1 } if seen == 0 { arr.push(name) } } i = i + 1 } } // Emit clusters local gi = 0 while gi < group_keys.size() { local gk = group_keys.get(gi) print(" subgraph \"" + gk + "\" {") // label = box name (strip "cluster_") local label = gk.substring("cluster_".length()) print(" label=\"" + label + "\";") local arr = groups.get(gk) local j = 0; while j < arr.size() { print(" \"" + arr.get(j) + "\";"); j = j + 1 } print(" }") gi = gi + 1 } // Emit edges // Emit edges if edges != null { // edges map key = from + "\t" + to // naive iteration by trying to get keys from a stored list // We kept an ArrayBox under edges.get("__keys__") for listing local ks = edges.get(EK_KEY) if ks == null { ks = new ArrayBox() } if ks != null { local ei = 0 while ei < ks.size() { local key = ks.get(ei) local tab = key.indexOf("\t") if tab > 0 { local src = key.substring(0, tab) local dst = key.substring(tab+1) print(" \"" + src + "\" -> \"" + dst + "\";") // also register nodes (in case they weren't explicitly collected) nodes.set(src, 1) nodes.set(dst, 1) } ei = ei + 1 } } } // Emit standalone nodes not covered by clusters if nk != null { local ni = 0 while ni < nk.size() { local name = nk.get(ni) local dot = name.indexOf(".") if dot < 0 { print(" \"" + name + "\";") } ni = ni + 1 } } print("}") return 0 } _render_ir(ir, nodes, edges) { if ir == null { return } // methods local ms = ir.get("methods") if ms != null { local mi = 0 while mi < ms.size() { me._add_node(nodes, ms.get(mi)) mi = mi + 1 } } // calls local cs = ir.get("calls") if cs != null { local ci = 0 while ci < cs.size() { local c = cs.get(ci) local f = c.get("from") local t = c.get("to") me._add_edge(edges, f, t) ci = ci + 1 } } } _add_node(nodes, name) { if name == null { return } nodes.set(name, 1) // also store a list of keys for emitting (since Map has no key iterator) local arr = nodes.get("__graphviz_nodes__"); if arr == null { arr = new ArrayBox(); nodes.set("__graphviz_nodes__", arr) } // avoid duplicates local seen = 0 local i = 0; while i < arr.size() { if arr.get(i) == name { seen = 1; break } i = i + 1 } if seen == 0 { arr.push(name) } } _add_edge(edges, src, dst) { if src == null || dst == null { return } local key = src + "\t" + dst if edges.get(key) == null { edges.set(key, 1) } local arr = edges.get("__graphviz_edges__"); if arr == null { arr = new ArrayBox(); edges.set("__graphviz_edges__", arr) } // avoid duplicates local seen = 0 local i = 0; while i < arr.size() { if arr.get(i) == key { seen = 1; break } i = i + 1 } if seen == 0 { arr.push(key) } } } static box GraphvizRenderMain { method main(args) { return 0 } }