1
2
3
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73 t.emit([]*Value{node.v, node.w}, []string{"v", "w"}, node.envIn)
74
75
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
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