1
2
3
4
5 package ir
6
7 import (
8 "bufio"
9 "cmd/compile/internal/base"
10 "cmd/compile/internal/types"
11 "cmd/internal/src"
12 "crypto/sha256"
13 "encoding/hex"
14 "fmt"
15 "html"
16 "io"
17 "os"
18 "path/filepath"
19 "reflect"
20 "regexp"
21 "strings"
22 )
23
24
25
26
27 type HTMLWriter struct {
28 HTMLWriterBase
29 Func *Func
30 }
31
32 type HTMLWriterBase struct {
33 w *BufferedWriterCloser
34 canonIdMap map[any]int
35 prevCanonId int
36 path string
37 prevHash []byte
38 pendingPhases []string
39 pendingTitles []string
40 doDump func(string) func()
41 }
42
43 func (h *HTMLWriterBase) Init(out io.WriteCloser, reportPath string, doDump func(string) func()) {
44 h.w = NewBufferedWriterCloser(out)
45 h.canonIdMap = make(map[any]int)
46 h.path = reportPath
47 h.doDump = doDump
48 }
49
50
51
52 type BufferedWriterCloser struct {
53 file io.Closer
54 w *bufio.Writer
55 }
56
57 func (b *BufferedWriterCloser) Write(p []byte) (n int, err error) {
58 return b.w.Write(p)
59 }
60
61 func (b *BufferedWriterCloser) Close() error {
62 b.w.Flush()
63 b.w = nil
64 return b.file.Close()
65 }
66
67 func NewBufferedWriterCloser(f io.WriteCloser) *BufferedWriterCloser {
68 return &BufferedWriterCloser{file: f, w: bufio.NewWriter(f)}
69 }
70
71 func NewHTMLWriter(path string, f *Func, cfgMask string) *HTMLWriter {
72 path = strings.ReplaceAll(path, "/", string(filepath.Separator))
73 out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
74 if err != nil {
75 base.Fatalf("%v", err)
76 }
77 reportPath := path
78 if !filepath.IsAbs(reportPath) {
79 pwd, err := os.Getwd()
80 if err != nil {
81 base.Fatalf("%v", err)
82 }
83 reportPath = filepath.Join(pwd, path)
84 }
85 h := HTMLWriter{
86 Func: f,
87 }
88 h.Init(out, reportPath, h.FuncHTML)
89 h.start()
90 return &h
91 }
92
93 func (h *HTMLWriterBase) Path() string {
94 return h.path
95 }
96
97
98
99
100 func (h *HTMLWriterBase) CanonId(n any) int {
101 if id := h.canonIdMap[n]; id > 0 {
102 return id
103 }
104 h.prevCanonId++
105 h.canonIdMap[n] = h.prevCanonId
106 return h.prevCanonId
107 }
108
109
110 func (w *HTMLWriterBase) Fatalf(msg string, args ...any) {
111 base.FatalfAt(src.NoXPos, msg, args...)
112 }
113
114 const (
115 RightArrow = "►"
116 DownArrow = "▼"
117 )
118
119 func (w *HTMLWriter) start() {
120 if w == nil {
121 return
122 }
123 escName := html.EscapeString(PkgFuncName(w.Func))
124 w.Print("<!DOCTYPE html>")
125 w.Print("<html>")
126 w.Printf(`<head>
127 <meta name="generator" content="AST display for %s">
128 <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
129 %s
130 %s
131 <title>AST display for %s</title>
132 </head>`, escName, CSS, JS("bloop", "loopvar", "escape", "slice", "walk"), escName)
133 w.Print("<body>")
134 w.Print("<h1>")
135 w.Print(html.EscapeString(w.Func.Sym().Name))
136 w.Print("</h1>")
137 w.Print(`
138 <a href="#" onclick="toggle_visibility('help');return false;" id="helplink">help</a>
139 <div id="help">
140
141 <p>
142 Click anywhere on a node (with "cell" cursor) to outline a node and all of its subtrees.
143 </p>
144 <p>
145 Click on a name (with "crosshair" cursor) to highlight every occurrence of a name.
146 (Note that all the name nodes are the same node, so those also all outline together).
147 </p>
148 <p>
149 Click on a file, line, or column (with "crosshair" cursor) to highlight positions
150 in that file, at that file:line, or at that file:line:column, respectively.<br>Inlined
151 locations are not treated as a single location, but as a sequence of locations that
152 can be independently highlighted.
153 </p>
154 <p>
155 Click on a ` + DownArrow + ` to collapse a subtree, or on a ` + RightArrow + ` to expand a subtree.
156 </p>
157
158
159 </div>
160 <label for="dark-mode-button" style="margin-left: 15px; cursor: pointer;">darkmode</label>
161 <input type="checkbox" onclick="toggleDarkMode();" id="dark-mode-button" style="cursor: pointer" />
162 `)
163 w.Print("<table>")
164 w.Print("<tr>")
165 }
166
167 func (w *HTMLWriterBase) Close(format string, args ...any) {
168 if w == nil {
169 return
170 }
171 w.Print("</tr>")
172 w.Print("</table>")
173 w.Print("</body>")
174 w.Print("</html>\n")
175 w.w.Close()
176 fmt.Fprintf(os.Stderr, format, args...)
177 }
178
179
180
181 func (w *HTMLWriterBase) WritePhase(phase, title string) {
182 if w == nil {
183 return
184 }
185 w.pendingPhases = append(w.pendingPhases, phase)
186 w.pendingTitles = append(w.pendingTitles, title)
187 w.flushPhases()
188 }
189
190
191 func (w *HTMLWriterBase) flushPhases() {
192 phaseLen := len(w.pendingPhases)
193 if phaseLen == 0 {
194 return
195 }
196 phases := strings.Join(w.pendingPhases, " + ")
197 w.WriteMultiTitleColumn(
198 phases,
199 w.pendingTitles,
200 "allow-x-scroll",
201 w.doDump(w.pendingPhases[phaseLen-1]),
202 )
203 w.pendingPhases = w.pendingPhases[:0]
204 w.pendingTitles = w.pendingTitles[:0]
205 }
206
207 func (w *HTMLWriterBase) WriteMultiTitleColumn(phase string, titles []string, class string, writeContent func()) {
208 if w == nil {
209 return
210 }
211 id := strings.ReplaceAll(phase, " ", "-")
212
213 w.Printf("<td id=\"%v-col\" class=\"collapsed\"><div>%v</div></td>", id, phase)
214
215 if class == "" {
216 w.Printf("<td id=\"%v-exp\">", id)
217 } else {
218 w.Printf("<td id=\"%v-exp\" class=\"%v\">", id, class)
219 }
220 for _, title := range titles {
221 w.Print("<h2>" + title + "</h2>")
222 }
223 writeContent()
224 w.Print("<div class=\"resizer\"></div>")
225 w.Print("</td>\n")
226 }
227
228 func (w *HTMLWriterBase) Printf(msg string, v ...any) {
229 if _, err := fmt.Fprintf(w.w, msg, v...); err != nil {
230 w.Fatalf("%v", err)
231 }
232 }
233
234 func (w *HTMLWriterBase) Print(s string) {
235 if _, err := fmt.Fprint(w.w, s); err != nil {
236 w.Fatalf("%v", err)
237 }
238 }
239
240 func (w *HTMLWriterBase) indent(n int) {
241 indent(w.w, n)
242 }
243
244 func (w *HTMLWriter) FuncHTML(phase string) func() {
245 return func() {
246 w.Print("<pre>")
247 w.dumpNodesHTML(w.Func.Body, 1)
248 w.Print("</pre>")
249 }
250 }
251
252 func (h *HTMLWriter) dumpNodesHTML(list Nodes, depth int) {
253 if len(list) == 0 {
254 h.Print(" <nil>")
255 return
256 }
257
258 for _, n := range list {
259 h.dumpNodeHTML(n, depth)
260 }
261 }
262
263 const indentString = ". "
264
265
266 func (h *HTMLWriterBase) indentForToggle(depth int, hasChildren bool) {
267 h.Print("\n")
268 if depth == 0 {
269 return
270 }
271 for i := 0; i < depth-1; i++ {
272 h.Print(indentString)
273 }
274 if hasChildren {
275
276
277 h.Print(indentString[:len(indentString)-2])
278 } else {
279 h.Print(indentString)
280 }
281 }
282
283 func (h *HTMLWriter) dumpNodeHTML(n Node, depth int) {
284 hasChildren := nodeHasChildren(n)
285 h.indentForToggle(depth, hasChildren)
286
287 if depth > 40 {
288 h.Print("...")
289 return
290 }
291
292 if n == nil {
293 h.Print("NilIrNode")
294 return
295 }
296
297
298
299
300
301
302
303
304
305
306
307
308 h.Printf("<span class=\"n%d outline-node\">", h.CanonId(n))
309 defer h.Printf("</span>")
310
311 if hasChildren {
312 h.Print(`<span class="toggle" onclick="toggle_node(this)">` + DownArrow + `</span> `)
313 }
314
315 if len(n.Init()) != 0 {
316 h.Print(`<span class="node-body">`)
317 h.Printf("%+v-init", n.Op())
318 h.dumpNodesHTML(n.Init(), depth+1)
319 h.indent(depth)
320 h.Print(`</span>`)
321 }
322
323 switch n.Op() {
324 default:
325 h.Printf("%+v", n.Op())
326 h.dumpNodeHeaderHTML(n)
327
328 case OLITERAL:
329 h.Printf("%+v-%v", n.Op(), html.EscapeString(fmt.Sprintf("%v", n.Val())))
330 h.dumpNodeHeaderHTML(n)
331 return
332
333 case ONAME, ONONAME:
334 if n.Sym() != nil {
335
336
337
338 name := fmt.Sprintf("%v", n.Sym())
339 hash := sha256.Sum256([]byte(name))
340 symID := "sym-" + hex.EncodeToString(hash[:6])
341 h.Printf("%+v-<span class=\"%s variable-name\">%+v</span>", n.Op(), symID, html.EscapeString(name))
342 } else {
343 h.Printf("%+v", n.Op())
344 }
345 h.dumpNodeHeaderHTML(n)
346 return
347
348 case OLINKSYMOFFSET:
349 n := n.(*LinksymOffsetExpr)
350 h.Printf("%+v-%v", n.Op(), html.EscapeString(fmt.Sprintf("%v", n.Linksym)))
351 if n.Offset_ != 0 {
352 h.Printf("%+v", n.Offset_)
353 }
354 h.dumpNodeHeaderHTML(n)
355
356 case OASOP:
357 n := n.(*AssignOpStmt)
358 h.Printf("%+v-%+v", n.Op(), n.AsOp)
359 h.dumpNodeHeaderHTML(n)
360
361 case OTYPE:
362 h.Printf("%+v %+v", n.Op(), html.EscapeString(fmt.Sprintf("%v", n.Sym())))
363 h.dumpNodeHeaderHTML(n)
364 return
365
366 case OCLOSURE:
367 h.Printf("%+v", n.Op())
368 h.dumpNodeHeaderHTML(n)
369
370 case ODCLFUNC:
371 n := n.(*Func)
372 h.Printf("%+v", n.Op())
373 h.dumpNodeHeaderHTML(n)
374 if hasChildren {
375 h.Print(`<span class="node-body">`)
376 defer h.Print(`</span>`)
377 }
378 fn := n
379 if len(fn.Dcl) > 0 {
380 h.indent(depth)
381 h.Printf("%+v-Dcl", n.Op())
382 for _, dcl := range n.Dcl {
383 h.dumpNodeHTML(dcl, depth+1)
384 }
385 }
386 if len(fn.ClosureVars) > 0 {
387 h.indent(depth)
388 h.Printf("%+v-ClosureVars", n.Op())
389 for _, cv := range fn.ClosureVars {
390 h.dumpNodeHTML(cv, depth+1)
391 }
392 }
393 if len(fn.Body) > 0 {
394 h.indent(depth)
395 h.Printf("%+v-body", n.Op())
396 h.dumpNodesHTML(fn.Body, depth+1)
397 }
398 return
399 }
400 if hasChildren {
401 h.Print(`<span class="node-body">`)
402 defer h.Print(`</span>`)
403 }
404
405 v := reflect.ValueOf(n).Elem()
406 t := reflect.TypeOf(n).Elem()
407 nf := t.NumField()
408 for i := 0; i < nf; i++ {
409 tf := t.Field(i)
410 vf := v.Field(i)
411 if tf.PkgPath != "" {
412 continue
413 }
414 switch tf.Type.Kind() {
415 case reflect.Interface, reflect.Ptr, reflect.Slice:
416 if vf.IsNil() {
417 continue
418 }
419 }
420 name := strings.TrimSuffix(tf.Name, "_")
421 switch name {
422 case "X", "Y", "Index", "Chan", "Value", "Call":
423 name = ""
424 }
425 switch val := vf.Interface().(type) {
426 case Node:
427 if name != "" {
428 h.indent(depth)
429 h.Printf("%+v-%s", n.Op(), name)
430 }
431 h.dumpNodeHTML(val, depth+1)
432 case Nodes:
433 if len(val) == 0 {
434 continue
435 }
436 if name != "" {
437 h.indent(depth)
438 h.Printf("%+v-%s", n.Op(), name)
439 }
440 h.dumpNodesHTML(val, depth+1)
441 default:
442 if vf.Kind() == reflect.Slice && vf.Type().Elem().Implements(nodeType) {
443 if vf.Len() == 0 {
444 continue
445 }
446 if name != "" {
447 h.indent(depth)
448 h.Printf("%+v-%s", n.Op(), name)
449 }
450 for i, n := 0, vf.Len(); i < n; i++ {
451 h.dumpNodeHTML(vf.Index(i).Interface().(Node), depth+1)
452 }
453 }
454 }
455 }
456 }
457
458 func nodeHasChildren(n Node) bool {
459 if n == nil {
460 return false
461 }
462 if len(n.Init()) != 0 {
463 return true
464 }
465 switch n.Op() {
466 case OLITERAL, ONAME, ONONAME, OTYPE:
467 return false
468 case ODCLFUNC:
469 n := n.(*Func)
470 return len(n.Dcl) > 0 || len(n.ClosureVars) > 0 || len(n.Body) > 0
471 }
472
473 v := reflect.ValueOf(n).Elem()
474 t := reflect.TypeOf(n).Elem()
475 nf := t.NumField()
476 for i := 0; i < nf; i++ {
477 tf := t.Field(i)
478 vf := v.Field(i)
479 if tf.PkgPath != "" {
480 continue
481 }
482 switch tf.Type.Kind() {
483 case reflect.Interface, reflect.Ptr, reflect.Slice:
484 if vf.IsNil() {
485 continue
486 }
487 }
488 switch val := vf.Interface().(type) {
489 case Node:
490 return true
491 case Nodes:
492 if len(val) > 0 {
493 return true
494 }
495 default:
496 if vf.Kind() == reflect.Slice && vf.Type().Elem().Implements(nodeType) {
497 if vf.Len() > 0 {
498 return true
499 }
500 }
501 }
502 }
503 return false
504 }
505
506 func (h *HTMLWriter) dumpNodeHeaderHTML(n Node) {
507
508 if base.Debug.DumpPtrs != 0 {
509 h.Printf(" p(%p)", n)
510 }
511
512 if base.Debug.DumpPtrs != 0 && n.Name() != nil && n.Name().Defn != nil {
513 h.Printf(" defn(%p)", n.Name().Defn)
514 }
515
516 if base.Debug.DumpPtrs != 0 && n.Name() != nil && n.Name().Curfn != nil {
517 h.Printf(" curfn(%p)", n.Name().Curfn)
518 }
519 if base.Debug.DumpPtrs != 0 && n.Name() != nil && n.Name().Outer != nil {
520 h.Printf(" outer(%p)", n.Name().Outer)
521 }
522
523 if EscFmt != nil {
524 if esc := EscFmt(n); esc != "" {
525 h.Printf(" %s", html.EscapeString(esc))
526 }
527 }
528
529 if n.Sym() != nil && n.Op() != ONAME && n.Op() != ONONAME && n.Op() != OTYPE {
530 h.Printf(" %+v", html.EscapeString(fmt.Sprintf("%v", n.Sym())))
531 }
532
533 v := reflect.ValueOf(n).Elem()
534 t := v.Type()
535 nf := t.NumField()
536 for i := 0; i < nf; i++ {
537 tf := t.Field(i)
538 if tf.PkgPath != "" {
539 continue
540 }
541 k := tf.Type.Kind()
542 if reflect.Bool <= k && k <= reflect.Complex128 {
543 name := strings.TrimSuffix(tf.Name, "_")
544 vf := v.Field(i)
545 vfi := vf.Interface()
546 if name == "Offset" && vfi == types.BADWIDTH || name != "Offset" && vf.IsZero() {
547 continue
548 }
549 if vfi == true {
550 h.Printf(" %s", name)
551 } else {
552 h.Printf(" %s:%+v", name, html.EscapeString(fmt.Sprintf("%v", vf.Interface())))
553 }
554 }
555 }
556
557 v = reflect.ValueOf(n)
558 t = v.Type()
559 nm := t.NumMethod()
560 for i := 0; i < nm; i++ {
561 tm := t.Method(i)
562 if tm.PkgPath != "" {
563 continue
564 }
565 m := v.Method(i)
566 mt := m.Type()
567 if mt.NumIn() == 0 && mt.NumOut() == 1 && mt.Out(0).Kind() == reflect.Bool {
568 func() {
569 defer func() { recover() }()
570 if m.Call(nil)[0].Bool() {
571 name := strings.TrimSuffix(tm.Name, "_")
572 h.Printf(" %s", name)
573 }
574 }()
575 }
576 }
577
578 if n.Op() == OCLOSURE {
579 n := n.(*ClosureExpr)
580 if fn := n.Func; fn != nil && fn.Nname.Sym() != nil {
581 h.Printf(" fnName(%+v)", html.EscapeString(fmt.Sprintf("%v", fn.Nname.Sym())))
582 }
583 }
584
585 if n.Type() != nil {
586 if n.Op() == OTYPE {
587 h.Printf(" type")
588 }
589 h.Printf(" %+v", html.EscapeString(fmt.Sprintf("%v", n.Type())))
590 }
591 if n.Typecheck() != 0 {
592 h.Printf(" tc(%d)", n.Typecheck())
593 }
594
595 if n.Pos().IsKnown() {
596 h.Print(" <span class=\"line-number\">")
597 switch n.Pos().IsStmt() {
598 case src.PosNotStmt:
599 h.Print("_")
600 case src.PosIsStmt:
601 h.Print("+")
602 }
603 sep := ""
604 base.Ctxt.AllPos(n.Pos(), func(pos src.Pos) {
605 h.Print(sep)
606 sep = " "
607
608
609
610
611
612 file := pos.Filename()
613
614 hash := sha256.Sum256([]byte(file))
615 fileID := "loc-" + hex.EncodeToString(hash[:6])
616 lineID := fmt.Sprintf("%s-L%d", fileID, pos.Line())
617 colID := fmt.Sprintf("%s-C%d", lineID, pos.Col())
618
619
620 h.Printf("<span class=\"%s line-number\">%s</span>:", fileID, html.EscapeString(filepath.Base(file)))
621
622 h.Printf("<span class=\"%s %s line-number\">%d</span>:", lineID, fileID, pos.Line())
623
624 h.Printf("<span class=\"%s %s %s line-number\">%d</span>", colID, lineID, fileID, pos.Col())
625 })
626 h.Print("</span>")
627 }
628 }
629
630 const CSS = `<style>
631
632 body {
633 font-size: 14px;
634 font-family: Arial, sans-serif;
635 }
636
637 h1 {
638 font-size: 18px;
639 display: inline-block;
640 margin: 0 1em .5em 0;
641 }
642
643 #helplink {
644 display: inline-block;
645 }
646
647 #help {
648 display: none;
649 }
650
651 table {
652 border: 1px solid black;
653 table-layout: fixed;
654 width: 300px;
655 }
656
657 th, td {
658 border: 1px solid black;
659 overflow: hidden;
660 width: 400px;
661 vertical-align: top;
662 padding: 5px;
663 position: relative;
664 }
665
666 .resizer {
667 display: inline-block;
668 background: transparent;
669 width: 10px;
670 height: 100%;
671 position: absolute;
672 right: 0;
673 top: 0;
674 cursor: col-resize;
675 z-index: 100;
676 }
677
678 td > h2 {
679 cursor: pointer;
680 font-size: 120%;
681 margin: 5px 0px 5px 0px;
682 }
683
684 td.collapsed {
685 font-size: 12px;
686 width: 12px;
687 border: 1px solid white;
688 padding: 2px;
689 cursor: pointer;
690 background: #fafafa;
691 }
692
693 td.collapsed div {
694 text-align: right;
695 transform: rotate(180deg);
696 writing-mode: vertical-lr;
697 white-space: pre;
698 }
699
700 pre {
701 font-family: Menlo, monospace;
702 font-size: 12px;
703 }
704
705 pre {
706 -moz-tab-size: 4;
707 -o-tab-size: 4;
708 tab-size: 4;
709 }
710
711 .allow-x-scroll {
712 overflow-x: scroll;
713 }
714
715 .outline-node {
716 cursor: cell;
717 }
718
719 .variable-name {
720 cursor: crosshair;
721 }
722
723 .line-number {
724 font-size: 11px;
725 cursor: crosshair;
726 }
727
728 body.darkmode {
729 background-color: rgb(21, 21, 21);
730 color: rgb(230, 255, 255);
731 opacity: 100%;
732 }
733
734 td.darkmode {
735 background-color: rgb(21, 21, 21);
736 border: 1px solid gray;
737 }
738
739 body.darkmode table, th {
740 border: 1px solid gray;
741 }
742
743 body.darkmode text {
744 fill: white;
745 }
746
747 .highlight-aquamarine { background-color: aquamarine; color: black; }
748 .highlight-coral { background-color: coral; color: black; }
749 .highlight-lightpink { background-color: lightpink; color: black; }
750 .highlight-lightsteelblue { background-color: lightsteelblue; color: black; }
751 .highlight-palegreen { background-color: palegreen; color: black; }
752 .highlight-skyblue { background-color: skyblue; color: black; }
753 .highlight-lightgray { background-color: lightgray; color: black; }
754 .highlight-yellow { background-color: yellow; color: black; }
755 .highlight-lime { background-color: lime; color: black; }
756 .highlight-khaki { background-color: khaki; color: black; }
757 .highlight-aqua { background-color: aqua; color: black; }
758 .highlight-salmon { background-color: salmon; color: black; }
759
760
761 .outline-blue { outline: #2893ff solid 2px; }
762 .outline-red { outline: red solid 2px; }
763 .outline-blueviolet { outline: blueviolet solid 2px; }
764 .outline-darkolivegreen { outline: darkolivegreen solid 2px; }
765 .outline-fuchsia { outline: fuchsia solid 2px; }
766 .outline-sienna { outline: sienna solid 2px; }
767 .outline-gold { outline: gold solid 2px; }
768 .outline-orangered { outline: orangered solid 2px; }
769 .outline-teal { outline: teal solid 2px; }
770 .outline-maroon { outline: maroon solid 2px; }
771 .outline-black { outline: black solid 2px; }
772
773 /* Capture alternative for outline-black and ellipse.outline-black when in dark mode */
774 body.darkmode .outline-black { outline: gray solid 2px; }
775
776 .toggle {
777 cursor: pointer;
778 display: inline-block;
779 text-align: center;
780 user-select: none;
781 font-size: 12px; // hand-tweaked
782 }
783
784 </style>
785 `
786
787
788
789
790 var safePhaseNameString = regexp.MustCompile("^[a-zA-Z0-9_ .]+$")
791
792 func JS(opened ...string) string {
793 var middle strings.Builder
794
795
796
797
798
799
800
801
802
803
804 for _, s := range opened {
805 if !safePhaseNameString.MatchString(s) {
806 continue
807 }
808 middle.WriteString("\t\"")
809 middle.WriteString(s)
810 middle.WriteString("\",\n")
811 }
812 return JS1 + middle.String() + JS2
813 }
814
815 const (
816 JS1 = `<script type="text/javascript">
817
818 // Contains phase names which are expanded by default. Other columns are collapsed.
819 let expandedDefault = [
820 `
821 JS2 = `];
822 if (history.state === null) {
823 history.pushState({expandedDefault}, "", location.href);
824 }
825
826 // ordered list of all available highlight colors
827 var highlights = [
828 "highlight-aquamarine",
829 "highlight-coral",
830 "highlight-lightpink",
831 "highlight-lightsteelblue",
832 "highlight-palegreen",
833 "highlight-skyblue",
834 "highlight-lightgray",
835 "highlight-yellow",
836 "highlight-lime",
837 "highlight-khaki",
838 "highlight-aqua",
839 "highlight-salmon"
840 ];
841
842 // state: which value is highlighted this color?
843 var highlighted = {};
844 for (var i = 0; i < highlights.length; i++) {
845 highlighted[highlights[i]] = "";
846 }
847
848 // ordered list of all available outline colors
849 var outlines = [
850 "outline-blue",
851 "outline-red",
852 "outline-blueviolet",
853 "outline-darkolivegreen",
854 "outline-fuchsia",
855 "outline-sienna",
856 "outline-gold",
857 "outline-orangered",
858 "outline-teal",
859 "outline-maroon",
860 "outline-black"
861 ];
862
863 // state: which value is outlined this color?
864 var outlined = {};
865 for (var i = 0; i < outlines.length; i++) {
866 outlined[outlines[i]] = "";
867 }
868
869 window.onload = function() {
870 if (history.state !== null) {
871 expandedDefault = history.state.expandedDefault;
872 }
873 if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
874 toggleDarkMode();
875 document.getElementById("dark-mode-button").checked = true;
876 }
877
878 var irElemClicked = function(elem, event, selections, selected) {
879 event.stopPropagation();
880
881 // find all values with the same name
882 var c = elem.classList.item(0);
883 var x = document.getElementsByClassName(c);
884
885 // if selected, remove selections from all of them
886 // otherwise, attempt to add
887
888 var remove = "";
889 for (var i = 0; i < selections.length; i++) {
890 var color = selections[i];
891 if (selected[color] == c) {
892 remove = color;
893 break;
894 }
895 }
896
897 if (remove != "") {
898 for (var i = 0; i < x.length; i++) {
899 x[i].classList.remove(remove);
900 }
901 selected[remove] = "";
902 return;
903 }
904
905 // we're adding a selection
906 // find first available color
907 var avail = "";
908 for (var i = 0; i < selections.length; i++) {
909 var color = selections[i];
910 if (selected[color] == "") {
911 avail = color;
912 break;
913 }
914 }
915 if (avail == "") {
916 alert("out of selection colors; go add more");
917 return;
918 }
919
920 // set that as the selection
921 for (var i = 0; i < x.length; i++) {
922 x[i].classList.add(avail);
923 }
924 selected[avail] = c;
925 };
926
927 var irValueClicked = function(event) {
928 irElemClicked(this, event, highlights, highlighted);
929 };
930
931 var irTreeClicked = function(event) {
932 irElemClicked(this, event, outlines, outlined);
933 };
934
935 var irValues = document.getElementsByClassName("outline-node");
936 for (var i = 0; i < irValues.length; i++) {
937 irValues[i].addEventListener('click', irTreeClicked);
938 }
939
940 var lines = document.getElementsByClassName("line-number");
941 for (var i = 0; i < lines.length; i++) {
942 lines[i].addEventListener('click', irValueClicked);
943 }
944
945 var variableNames = document.getElementsByClassName("variable-name");
946 for (var i = 0; i < variableNames.length; i++) {
947 variableNames[i].addEventListener('click', irValueClicked);
948 }
949
950 function toggler(phase) {
951 return function() {
952 toggle_cell(phase+'-col');
953 toggle_cell(phase+'-exp');
954 const i = expandedDefault.indexOf(phase);
955 if (i !== -1) {
956 expandedDefault.splice(i, 1);
957 } else {
958 expandedDefault.push(phase);
959 }
960 history.pushState({expandedDefault}, "", location.href);
961 };
962 }
963
964 function toggle_cell(id) {
965 var e = document.getElementById(id);
966 if (e.style.display == 'table-cell') {
967 e.style.display = 'none';
968 } else {
969 e.style.display = 'table-cell';
970 }
971 }
972
973 // Go through all columns and collapse needed phases.
974 const td = document.getElementsByTagName("td");
975 for (let i = 0; i < td.length; i++) {
976 const id = td[i].id;
977 const phase = id.substr(0, id.length-4);
978 let show = expandedDefault.indexOf(phase) !== -1
979
980 // If show == false, check to see if this is a combined column (multiple phases).
981 // If combined, check each of the phases to see if they are in our expandedDefaults.
982 // If any are found, that entire combined column gets shown.
983 if (!show) {
984 const combined = phase.split('--+--');
985 const len = combined.length;
986 if (len > 1) {
987 for (let i = 0; i < len; i++) {
988 const num = expandedDefault.indexOf(combined[i]);
989 if (num !== -1) {
990 expandedDefault.splice(num, 1);
991 if (expandedDefault.indexOf(phase) === -1) {
992 expandedDefault.push(phase);
993 show = true;
994 }
995 }
996 }
997 }
998 }
999 if (id.endsWith("-exp")) {
1000 const h2Els = td[i].getElementsByTagName("h2");
1001 const len = h2Els.length;
1002 if (len > 0) {
1003 for (let i = 0; i < len; i++) {
1004 h2Els[i].addEventListener('click', toggler(phase));
1005 }
1006 }
1007 } else {
1008 td[i].addEventListener('click', toggler(phase));
1009 }
1010 if (id.endsWith("-col") && show || id.endsWith("-exp") && !show) {
1011 td[i].style.display = 'none';
1012 continue;
1013 }
1014 td[i].style.display = 'table-cell';
1015 }
1016
1017 var resizers = document.getElementsByClassName("resizer");
1018 for (var i = 0; i < resizers.length; i++) {
1019 var resizer = resizers[i];
1020 resizer.addEventListener('mousedown', initDrag, false);
1021 }
1022 };
1023
1024 var startX, startWidth, resizableCol;
1025
1026 function initDrag(e) {
1027 resizableCol = this.parentElement;
1028 startX = e.clientX;
1029 startWidth = parseInt(document.defaultView.getComputedStyle(resizableCol).width, 10);
1030 document.documentElement.addEventListener('mousemove', doDrag, false);
1031 document.documentElement.addEventListener('mouseup', stopDrag, false);
1032 }
1033
1034 function doDrag(e) {
1035 resizableCol.style.width = (startWidth + e.clientX - startX) + 'px';
1036 }
1037
1038 function stopDrag(e) {
1039 document.documentElement.removeEventListener('mousemove', doDrag, false);
1040 document.documentElement.removeEventListener('mouseup', stopDrag, false);
1041 }
1042
1043 function toggle_visibility(id) {
1044 var e = document.getElementById(id);
1045 if (e.style.display == 'block') {
1046 e.style.display = 'none';
1047 } else {
1048 e.style.display = 'block';
1049 }
1050 }
1051
1052 function toggleDarkMode() {
1053 document.body.classList.toggle('darkmode');
1054
1055 // Collect all of the "collapsed" elements and apply dark mode on each collapsed column
1056 const collapsedEls = document.getElementsByClassName('collapsed');
1057 const len = collapsedEls.length;
1058
1059 for (let i = 0; i < len; i++) {
1060 collapsedEls[i].classList.toggle('darkmode');
1061 }
1062 }
1063
1064 function toggle_node(e) {
1065 event.stopPropagation();
1066 var parent = e.parentNode;
1067 var children = parent.children;
1068 for (var i = 0; i < children.length; i++) {
1069 if (children[i].classList.contains("node-body")) {
1070 if (children[i].style.display == "none") {
1071 children[i].style.display = "";
1072 } else {
1073 children[i].style.display = "none";
1074 }
1075 }
1076 }
1077 if (e.innerText == "` + RightArrow + `") {
1078 e.innerText = "` + DownArrow + `";
1079 } else {
1080 e.innerText = "` + RightArrow + `";
1081 }
1082 }
1083
1084 </script>
1085 `
1086 )
1087
View as plain text