summaryrefslogtreecommitdiff
path: root/chapter25
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-09-21 21:48:15 -0700
committerCliff Click <cliffc@acm.org>2026-09-21 21:48:15 -0700
commite48d59474230869ba81c1712128c01fd822946f5 (patch)
treedba17d167c2af4b6ca603ee329eb2b3baa8e5617 /chapter25
parente587a11049129866bf3fd6db0a0f72d44ad6b39f (diff)
downloadSimple-e48d59474230869ba81c1712128c01fd822946f5.tar.gz
Simple-e48d59474230869ba81c1712128c01fd822946f5.zip
Switch over to the ELK renderer
Diffstat (limited to 'chapter25')
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java341
1 files changed, 4 insertions, 337 deletions
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java
index a9f6bfd4..42f007ea 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java
@@ -3,18 +3,12 @@ package com.seaofnodes.simple.print;
import com.seaofnodes.graph.GraphJson;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
import java.io.IOException;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.nio.file.Path;
import java.net.URI;
-import java.util.Collection;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Stack;
import java.util.UUID;
import static com.seaofnodes.simple.codegen.CodeGen.CODE;
@@ -98,7 +92,7 @@ public class JSViewer implements AutoCloseable {
if( CODE._phase==null || CODE._phase.ordinal() < CodeGen.Phase.Parse.ordinal() )
return;
boolean midParse = CODE._phase == CodeGen.Phase.Parse;
- Stack<ScopeNode> xScopes = midParse ? CODE.P._xScopes : null;
+ var xScopes = midParse ? CODE.P._xScopes : null;
var roots = new ArrayList<Node>();
roots.add(CODE._stop);
if( midParse ) {
@@ -109,337 +103,10 @@ public class JSViewer implements AutoCloseable {
var snap = GRAPH.snap(COMP, step, roots.toArray(Node[]::new));
int pos = midParse ? CODE.P.pos() : -1;
- Collection<Node> all = GraphVisualizer.findAll(xScopes, CODE._stop, midParse ? CODE.P._scope: null);
- SB sb = new SB();
- sb.p("digraph view_").p(step).p(" {\n").ii();
- if( midParse )
- sb.i().p("// POS: ").p(CODE.P.pos()).nl();
-
- // To keep the Scopes below the graph and pointing up into the graph we
- // need to group the Nodes in a subgraph cluster, and the scopes into a
- // different subgraph cluster. THEN we can draw edges between the
- // scopes and nodes. If we try to cross subgraph cluster borders while
- // still making the subgraphs DOT gets confused.
- sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes
-
- // CNC - turned off Apr/8/2024, gives more flex in the layout and
- // removes some of the more ludicrous layout choices.
- // Preserve node input order
- //sb.append("ordering=\"in\";\n");
-
- // Merge multiple edges hitting the same node. Makes common shared
- // nodes much prettier to look at.
- sb.i().p("concentrate=\"true\";\n");
-
- // Force nested scopes to order
- sb.i().p("compound=\"true\";\n");
-
- // Just the Nodes first, in a cluster no edges
- nodes(sb, all);
-
- // Now the scopes, in a cluster no edges
- if( midParse )
- for( ScopeNode xscope : xScopes )
- if( !xscope.isDead() )
- scope( sb, xscope );
-
- // Walk the Node edges
- nodeEdges(sb, midParse && !xScopes.empty() ? xScopes.peek() : null, all);
-
- // Walk the active Scope edges
- if( midParse )
- for( ScopeNode xscope : xScopes )
- if( !xscope.isDead() )
- scopeEdges( sb, xscope );
-
-
- sb.p("}\n").di();
- // Send the model and its current DOT rendering as one playback frame.
- String dot = sb.toString();
try {
- SERVER.put(GraphJson.frame(snap, pos, dot));
+ SERVER.put(GraphJson.frame(snap, pos));
} catch( IOException ioe ) {
- try { SERVER.close();} catch( IOException ignored ){}
- }
- }
-
-
- private static void nodes(SB sb, Collection<Node> all) {
- // Just the Nodes first, in a cluster no edges
- sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name
- for( Node n : all ) {
- if( n instanceof ProjNode || n instanceof CProjNode )
- continue; // Do not emit, rolled into MultiNode or Scope cluster already
- sb.i().p(nodeName(n)).p(" [ ");
- if( n instanceof MultiNode ) {
- // Make a box with the MultiNode on top, and all the projections on the bottom
- sb. p("shape=plaintext label=<\n").ii();
- sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n");
- // Row over single cell for node label
- cell(sb.i().p("<TR>"), n.glabel(), n, null).p("</TR>\n");
- // Row over single cell, for nested table
- sb.i().p("<TR><TD>\n").ii();
- sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").p("\n");
- sb.i().p("<TR>");
- Node[] outputs = n._outputs.asAry();
- Arrays.sort(outputs,(x,y) -> Integer.compare(idx(x),idx(y)));
- boolean empty_row=true;
- for( Node use : outputs ) {
- int idx = idx(use);
- if( idx != -1 ) {
- cell(sb,use.glabel(),use,"p"+idx);
- empty_row=false;
- }
- }
- // At least one cell on row
- if( empty_row ) sb.p("<TD></TD>");
- sb. p("</TR>").p("\n");
- sb.i().p("</TABLE>").p("\n").di();
- sb.i().p("</TD></TR>\n");
- sb.i().p("</TABLE>>\n").di();
- sb.i().p("];\n");
-
- } else {
- // control nodes have box shape
- // other nodes are ellipses, i.e. default shape
- //else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow ");
- node(sb,n.glabel(),n);
- sb.p("];\n");
- }
- }
- // Force Region & Phis to line up
- for (Node n : all) {
- if (n instanceof RegionNode region) {
- sb.i().p("{ rank=same; ");
- sb.p(nodeName(region)).p(";");
- for (Node phi : region._outputs)
- if (phi instanceof PhiNode) sb.p(nodeName(phi)).p(";");
- sb.p("}\n");
- }
- }
- sb.di().i().p("}").nl(); // End Node cluster
- }
-
- // Build a nested scope display, walking the _prev edge
- private static void scope( SB sb, ScopeNode scope ) {
- sb.i().p("node [shape=plaintext];\n");
- int last = scope.nIns();
- int max = scope.depth()+1; // One more than lexsize
- for( int i = 0; i < max; i++ ) {
- int level = max-i-1;
- String scopeName = makeScopeName(scope, level);
- sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name
- sb.i().p(scopeName).p(" [label=<\n").ii();
- sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
- int lexStart = level==0 ? 0 : scope._kinds.at(level-1)._lexSize;
-
- // Special for memory ScopeMinNode
- Node n = scope.nIns()>1 ? scope.mem() : null;
- if( level==0 && n instanceof MemMergeNode merge && n.nIns()>2 ) {
- sb.i().p("<TR>");
- for( int m=2; m<n.nIns(); m++ )
- cell(sb,"#"+m,merge.alias(m),"m"+m);
- sb.p("</TR>\n"); // End scope level
- }
-
- // Scope variables, empty if none
- if( lexStart<last ) {
- sb.i().p("<TR>\n");
- for( int j=lexStart; j<last; j++ ) {
- if( scope.in(j)==null ) continue;
- var v = scope._vars.at(j);
- cell(sb.i(),v._name,scope.in(j),makePortName(scopeName, v._name)).nl();
- }
- last = lexStart;
- sb.i().p("</TR>\n");
- }
- sb.i().p("</TABLE>>];\n").di();
- // Scope label
- sb.i().p(scopeName).p("A [label=\"").p(level).p("\" shape=doublecircle style=filled fillcolor=aqua fontsize=10 margin=0 width=0.2 ];\n");
- }
- // Scope clusters nest, so the graphics shows the nested scopes, so
- // they are not closed as they are printed; so they just keep nesting.
- // We close them all at once here.
- for( int i=0; i<max; i++ )
- sb.di().i().p("}\n");
- }
-
- // Append a cell, with color
- private static SB cell(SB sb, String text, Node n, String port) {
- sb.p("<TD");
- String color = color(n);
- sb.p(" BGCOLOR=\"").p(color).p("\"");
- if( port!=null ) sb.p(" PORT=\"").p(port).p("\"");
- return colorcell(sb.p(">"),text,n,dark(color)).p("</TD>");
- }
- private static SB node(SB sb, String text, Node n) {
- String color = color(n);
- sb.p("style=filled fillcolor=").p(color).p(" ");
- String shape = switch( n ) {
- case FunNode fun -> "shape=box peripheries=2";
- case CFGNode cfg -> "shape=box";
- default -> "";
- };
- sb.p(shape).p(" ");
- return colorcell(sb.p("label=<"),text,n,dark(color)).p(">");
- }
-
- private static String color(Node n) {
- return switch( n ) {
- case CFGNode cfg -> "yellow";
- case NewNode nnn -> "lightgreen";
- default -> typeColor(n._type);
- };
- }
- private static String typeColor(Type t) {
- return t==null ? "white" : switch( t ) {
- case TypeMem mem -> "blue";
- case TypeMemPtr tmp -> "green";
- case TypeFunPtr tfp -> "orange";
- case TypeRPC rpc -> "antiquewhite";
- case TypeInteger ti -> "lightblue";
- case TypeFloat tf -> "aqua";
- case Type tn -> tn==Type.NIL ? "lightgray" : "red";
- };
- }
- private static final String[] DARKS = new String[]{"blue","green","black"};
- private static boolean dark(String color) { return Utils.find(DARKS,color) != -1; }
-
-
-
- // Called with an open div
- private static SB colorcell(SB sb, String text, Node n, boolean dark) {
- if( dark ) sb.p("<font color=\"white\">");
- sb.p(htmlText(text));
- if( n._type!=null ) {
- sb.p("<br /><font point-size=\"10\">");
- sb.p(htmlText(n._type.gprint()));
- sb.p("</font>");
- }
- if( dark ) sb.p("</font>");
- return sb;
- }
-
-
- // DOT identity must not depend on labels (module/function names contain punctuation).
- private static String nodeName(Node n) { return "n" + n._nid; }
-
- // glabel/gprint already use entities such as &lt; and &rarr;.
- private static String htmlText(String text) {
- return text.replaceAll("&(?!#[0-9]+;|#x[0-9a-fA-F]+;|[a-zA-Z]+;)", "&amp;")
- .replace("<", "&lt;").replace(">", "&gt;");
- }
-
- private static String makeScopeName(ScopeNode sn, int level) { return nodeName(sn) + "_" + level; }
- private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; }
-
- // Walk the node edges
- private static void nodeEdges(SB sb, ScopeNode scope, Collection<Node> all) {
- // All them edge labels
- String scopeName = scope!=null ? makeScopeName(scope, 0) : null;
- sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n");
- for( Node n : all ) {
- // Do not display the Constant->Start edge;
- if( n instanceof ConstantNode ||
- n instanceof XCtrlNode ||
- // ProjNodes handled by Multi;
- n instanceof ProjNode ||
- n instanceof CProjNode ||
- // ScopeNodes are done separately
- n instanceof ScopeNode
- )
- continue;
- if( n.isDead() )
- throw Utils.TODO();
- // Normal edges
- for( int i=0; i<n.nIns(); i++ ) {
- Node def = n.in(i);
- if( def==null ) continue;
- sb.i().p(nodeName(n)).p(" -> ");
- if( n instanceof PhiNode && def instanceof RegionNode ) {
- // Draw a dotted use->def edge from Phi to Region.
- sb.p(nodeName(def));
- sb.p(" [style=dotted taillabel=").p(i).p("];\n");
- } else {
- // Most edges land here use->def
- if( def instanceof CProjNode proj ) {
- String mname = nodeName(proj.ctrl());
- sb.p(mname).p(":p").p(proj._idx);
- } else if( def instanceof ProjNode proj ) {
- String mname = nodeName(proj.in(0));
- sb.p(mname).p(":p").p(proj._idx);
- } else sb.p(nodeName(def));
- // Number edges, so we can see how they track
- sb.p("[taillabel=").p(i);
- // Color the edge
- nodeEdgeColor(sb,def._type);
- // Backedges do not add a ranking constraint
- if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) )
- sb.p(" constraint=false");
- sb.p("];\n");
- }
- }
-
- // Bonus edge if hooked by parser
- if( (n.iskeep() || n.isUnused()) && scopeName != null ) {
- sb.i().p(scopeName).p(" -> ").p(nodeName(n)).p(" [ style=dashed color=grey];\n");
- }
- // Force functions above return
- if( n instanceof ReturnNode ret && !ret.fun().isDead() ) {
- defPort(sb.i(),ret.ctrl()).p(" -> ");
- defPort(sb,ret.fun()).p("[style=invis]\n");
- }
- }
- }
-
- private static void nodeEdgeColor(SB sb, Type t) {
- sb.p(" color=").p(typeColor(t));
- }
-
- // Walk the scope edges
- private static void scopeEdges( SB sb, ScopeNode scope ) {
- sb.i().p("edge [style=dashed color=cornflowerblue];\n");
- int level=0;
- for( var v : scope._vars ) {
- if( v._idx==1 ) continue; // Memory done separately
- Node def = scope.in(v._idx);
- while( def instanceof ScopeNode lazy )
- def = lazy.in(v._idx);
- if( def==null ) continue;
- while( level < scope.depth() && v._idx >= scope._kinds.at(level)._lexSize )
- level++;
- String scopeName = makeScopeName(scope, level);
- sb.i()
- .p(scopeName).p(":")
- .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted
- .p(" -> ");
- defPort(sb,def).p(";\n");
+ try { SERVER.close(); } catch( IOException ignored ) {}
}
-
- // Memory
- if( scope.nIns()>1 && scope.mem() instanceof MemMergeNode n ) {
- for( int i=2; i<n.nIns(); i++ ) {
- Node def = n.in(i);
- while( def instanceof ScopeNode lazy )
- def = lazy.in(i);
- if( def==null ) continue;
- String scopeName = makeScopeName(scope, 0);
- sb.i().p(scopeName).p(":m").p(i).p(" -> ");
- defPort(sb,def).p(";\n");
- }
- }
-
- }
-
- // (C)Projection index or -1
- private static int idx(Node n) {
- return n instanceof ProjNode p ? p._idx : (n instanceof CProjNode cp ? cp._idx : -1);
- }
-
- private static SB defPort(SB sb, Node def) {
- int idx = idx(def);
- return idx != -1
- ? sb.p(nodeName(def.in(0))).p(":p").p(idx)
- : sb.p(nodeName(def));
}
-}
+} \ No newline at end of file