]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: add node collapse/expand to html ast output
authorDavid Chase <drchase@google.com>
Fri, 30 Jan 2026 20:37:59 +0000 (22:37 +0200)
committerDavid Chase <drchase@google.com>
Wed, 4 Feb 2026 04:24:21 +0000 (20:24 -0800)
AI-generated code, plus a lot of reviewing and tweaking
of arrows and fonts.

This adds subtree collapse/expand triangles.

Change-Id: I2dd322abdf7ef956b1435946d79f864a6150f976
Reviewed-on: https://go-review.googlesource.com/c/go/+/740481
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/cmd/compile/internal/ir/html.go

index c17d4655eca14bb65e599c9a6da120704f6c31a4..8a111c1dd471b2b464bb8718a7f1b93cb9fb5f98 100644 (file)
@@ -96,6 +96,11 @@ func (w *HTMLWriter) Fatalf(msg string, args ...any) {
        base.FatalfAt(src.NoXPos, msg, args...)
 }
 
+const (
+       RIGHT_ARROW = "\u25BA" // click-to-open (is closed)
+       DOWN_ARROW  = "\u25BC" // click-to-close (is open)
+)
+
 func (w *HTMLWriter) start() {
        if w == nil {
                return
@@ -131,6 +136,10 @@ in that file, at that file:line, or at that file:line:column, respectively.<br>I
 locations are not treated as a single location, but as a sequence of locations that
 can be independently highlighted.
 </p>
+<p>
+Click on a ` + DOWN_ARROW + ` to collapse a subtree, or on a ` + RIGHT_ARROW + ` to expand a subtree.
+</p>
+
 
 </div>
 <label for="dark-mode-button" style="margin-left: 15px; cursor: pointer;">darkmode</label>
@@ -236,8 +245,26 @@ func (h *HTMLWriter) dumpNodesHTML(list Nodes, depth int) {
        }
 }
 
+// indent prints indentation to w.
+func (h *HTMLWriter) indentForToggle(depth int, hasChildren bool) {
+       h.Print("\n")
+       if depth == 0 {
+               return
+       }
+       for i := 0; i < depth-1; i++ {
+               h.Print(".   ")
+       }
+       if hasChildren {
+               h.Print(". ")
+       } else {
+               h.Print(".   ")
+       }
+}
+
 func (h *HTMLWriter) dumpNodeHTML(n Node, depth int) {
-       indent(h.w, depth)
+       hasChildren := nodeHasChildren(n)
+       h.indentForToggle(depth, hasChildren)
+
        if depth > 40 {
                h.Print("...")
                return
@@ -262,10 +289,16 @@ func (h *HTMLWriter) dumpNodeHTML(n Node, depth int) {
        h.Printf("<span class=\"n%d ir-node\">", h.canonId(n))
        defer h.Printf("</span>")
 
+       if hasChildren {
+               h.Print(`<span class="toggle" onclick="toggle_node(this)">` + DOWN_ARROW + `</span> `) // NOTE TRAILING SPACE after </span>!
+       }
+
        if len(n.Init()) != 0 {
+               h.Print(`<span class="node-body">`)
                h.Printf("%+v-init", n.Op())
                h.dumpNodesHTML(n.Init(), depth+1)
                h.indent(depth)
+               h.Print(`</span>`)
        }
 
        switch n.Op() {
@@ -319,6 +352,10 @@ func (h *HTMLWriter) dumpNodeHTML(n Node, depth int) {
                n := n.(*Func)
                h.Printf("%+v", n.Op())
                h.dumpNodeHeaderHTML(n)
+               if hasChildren {
+                       h.Print(`<span class="node-body">`)
+                       defer h.Print(`</span>`)
+               }
                fn := n
                if len(fn.Dcl) > 0 {
                        h.indent(depth)
@@ -341,6 +378,10 @@ func (h *HTMLWriter) dumpNodeHTML(n Node, depth int) {
                }
                return
        }
+       if hasChildren {
+               h.Print(`<span class="node-body">`)
+               defer h.Print(`</span>`)
+       }
 
        v := reflect.ValueOf(n).Elem()
        t := reflect.TypeOf(n).Elem()
@@ -395,6 +436,54 @@ func (h *HTMLWriter) dumpNodeHTML(n Node, depth int) {
        }
 }
 
+func nodeHasChildren(n Node) bool {
+       if n == nil {
+               return false
+       }
+       if len(n.Init()) != 0 {
+               return true
+       }
+       switch n.Op() {
+       case OLITERAL, ONAME, ONONAME, OTYPE:
+               return false
+       case ODCLFUNC:
+               n := n.(*Func)
+               return len(n.Dcl) > 0 || len(n.ClosureVars) > 0 || len(n.Body) > 0
+       }
+
+       v := reflect.ValueOf(n).Elem()
+       t := reflect.TypeOf(n).Elem()
+       nf := t.NumField()
+       for i := 0; i < nf; i++ {
+               tf := t.Field(i)
+               vf := v.Field(i)
+               if tf.PkgPath != "" {
+                       continue
+               }
+               switch tf.Type.Kind() {
+               case reflect.Interface, reflect.Ptr, reflect.Slice:
+                       if vf.IsNil() {
+                               continue
+                       }
+               }
+               switch val := vf.Interface().(type) {
+               case Node:
+                       return true
+               case Nodes:
+                       if len(val) > 0 {
+                               return true
+                       }
+               default:
+                       if vf.Kind() == reflect.Slice && vf.Type().Elem().Implements(nodeType) {
+                               if vf.Len() > 0 {
+                                       return true
+                               }
+                       }
+               }
+       }
+       return false
+}
+
 func (h *HTMLWriter) dumpNodeHeaderHTML(n Node) {
        // print pointer to be able to see identical nodes
        if base.Debug.DumpPtrs != 0 {
@@ -666,6 +755,14 @@ body.darkmode text {
 /* Capture alternative for outline-black and ellipse.outline-black when in dark mode */
 body.darkmode .outline-black        { outline: gray solid 2px; }
 
+.toggle {
+    cursor: pointer;
+    display: inline-block;
+    text-align: center;
+    user-select: none;
+    font-size: 12px; // hand-tweaked
+}
+
 </style>
 `
 
@@ -921,6 +1018,26 @@ function toggleDarkMode() {
     }
 }
 
+function toggle_node(e) {
+    event.stopPropagation();
+    var parent = e.parentNode;
+    var children = parent.children;
+    for (var i = 0; i < children.length; i++) {
+        if (children[i].classList.contains("node-body")) {
+            if (children[i].style.display == "none") {
+                children[i].style.display = "";
+            } else {
+                children[i].style.display = "none";
+            }
+        }
+    }
+    if (e.innerText == "` + RIGHT_ARROW + `") {
+        e.innerText = "` + DOWN_ARROW + `";
+    } else {
+        e.innerText = "` + RIGHT_ARROW + `";
+    }
+}
+
 </script>
 `
 )