Source file src/simd/_gen/unify/html.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package unify
     6  
     7  import (
     8  	"fmt"
     9  	"html"
    10  	"io"
    11  	"strings"
    12  )
    13  
    14  func (t *tracer) writeHTML(w io.Writer) {
    15  	if !t.saveTree {
    16  		panic("writeHTML called without tracer.saveTree")
    17  	}
    18  
    19  	fmt.Fprintf(w, "<html><head><style>%s</style></head>", htmlCSS)
    20  	for _, root := range t.trees {
    21  		dot := newDotEncoder()
    22  		html := htmlTracer{w: w, dot: dot}
    23  		html.writeTree(root)
    24  	}
    25  	fmt.Fprintf(w, "</html>\n")
    26  }
    27  
    28  const htmlCSS = `
    29  .unify {
    30  	display: grid;
    31  	grid-auto-columns: min-content;
    32  	text-align: center;
    33  }
    34  
    35  .header {
    36  	grid-row: 1;
    37  	font-weight: bold;
    38  	padding: 0.25em;
    39  	position: sticky;
    40  	top: 0;
    41  	background: white;
    42  }
    43  
    44  .envFactor {
    45  	display: grid;
    46  	grid-auto-rows: min-content;
    47  	grid-template-columns: subgrid;
    48  	text-align: center;
    49  }
    50  `
    51  
    52  type htmlTracer struct {
    53  	w    io.Writer
    54  	dot  *dotEncoder
    55  	svgs map[any]string
    56  }
    57  
    58  func (t *htmlTracer) writeTree(node *traceTree) {
    59  	// TODO: This could be really nice.
    60  	//
    61  	// - Put nodes that were unified on the same rank with {rank=same; a; b}
    62  	//
    63  	// - On hover, highlight nodes that node was unified with and the result. If
    64  	// it's a variable, highlight it in the environment, too.
    65  	//
    66  	// - On click, show the details of unifying that node.
    67  	//
    68  	// This could be the only way to navigate, without necessarily needing the
    69  	// whole nest of <detail> nodes.
    70  
    71  	// TODO: It might be possible to write this out on the fly.
    72  
    73  	t.emit([]*Value{node.v, node.w}, []string{"v", "w"}, node.envIn)
    74  
    75  	// Render children.
    76  	for i, child := range node.children {
    77  		if i >= 10 {
    78  			fmt.Fprintf(t.w, `<div style="margin-left: 4em">...</div>`)
    79  			break
    80  		}
    81  		fmt.Fprintf(t.w, `<details style="margin-left: 4em"><summary>%s</summary>`, html.EscapeString(child.label))
    82  		t.writeTree(child)
    83  		fmt.Fprintf(t.w, "</details>\n")
    84  	}
    85  
    86  	// Render result.
    87  	if node.err != nil {
    88  		fmt.Fprintf(t.w, "Error: %s\n", html.EscapeString(node.err.Error()))
    89  	} else {
    90  		t.emit([]*Value{node.res}, []string{"res"}, node.env)
    91  	}
    92  }
    93  
    94  func htmlSVG[Key comparable](t *htmlTracer, f func(Key), arg Key) string {
    95  	if s, ok := t.svgs[arg]; ok {
    96  		return s
    97  	}
    98  	var buf strings.Builder
    99  	f(arg)
   100  	t.dot.writeSvg(&buf)
   101  	t.dot.clear()
   102  	svg := buf.String()
   103  	if t.svgs == nil {
   104  		t.svgs = make(map[any]string)
   105  	}
   106  	t.svgs[arg] = svg
   107  	buf.Reset()
   108  	return svg
   109  }
   110  
   111  func (t *htmlTracer) emit(vs []*Value, labels []string, env envSet) {
   112  	fmt.Fprintf(t.w, `<div class="unify">`)
   113  	for i, v := range vs {
   114  		fmt.Fprintf(t.w, `<div class="header" style="grid-column: %d">%s</div>`, i+1, html.EscapeString(labels[i]))
   115  		fmt.Fprintf(t.w, `<div style="grid-area: 2 / %d">%s</div>`, i+1, htmlSVG(t, t.dot.valueSubgraph, v))
   116  	}
   117  	col := len(vs)
   118  
   119  	fmt.Fprintf(t.w, `<div class="header" style="grid-column: %d">in</div>`, col+1)
   120  	fmt.Fprintf(t.w, `<div style="grid-area: 2 / %d">%s</div>`, col+1, htmlSVG(t, t.dot.envSubgraph, env))
   121  
   122  	fmt.Fprintf(t.w, `</div>`)
   123  }
   124  

View as plain text