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