1
2
3
4
5 package noder
6
7 import (
8 "cmd/compile/internal/base"
9 "cmd/compile/internal/ir"
10 "cmd/compile/internal/syntax"
11 "cmd/compile/internal/types2"
12 "crypto/sha256"
13 "encoding/hex"
14 "fmt"
15 "html"
16 "os"
17 "path/filepath"
18 "reflect"
19 "strings"
20 )
21
22
23
24 type HTMLWriter struct {
25 ir.HTMLWriterBase
26
27 Decl *syntax.FuncDecl
28 pkg *types2.Package
29 file *syntax.File
30 info *types2.Info
31 }
32
33 func NewHTMLWriter(pkg *types2.Package, file *syntax.File, info *types2.Info, path string, decl *syntax.FuncDecl, cfgMask string) *HTMLWriter {
34 path = strings.ReplaceAll(path, "/", string(filepath.Separator))
35 out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
36 if err != nil {
37 panic(err)
38 }
39 reportPath := path
40 if !filepath.IsAbs(reportPath) {
41 pwd, err := os.Getwd()
42 if err != nil {
43 panic(err)
44 }
45 reportPath = filepath.Join(pwd, path)
46 }
47 h := HTMLWriter{
48 pkg: pkg,
49 file: file,
50 info: info,
51 Decl: decl,
52 }
53 h.Init(out, reportPath, h.DeclHTML)
54 h.start()
55 return &h
56 }
57
58 func (w *HTMLWriter) pkgFuncName() string {
59 p := w.pkg.Path()
60 if p == "" {
61 p = base.Ctxt.Pkgpath
62 }
63 return p + "." + w.Decl.Name.Value
64 }
65
66 func (w *HTMLWriter) start() {
67 if w == nil {
68 return
69 }
70 escName := html.EscapeString(w.pkgFuncName())
71 w.Print("<!DOCTYPE html>")
72 w.Print("<html>")
73 w.Printf(`<head>
74 <meta name="generator" content="AST display for %s">
75 <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
76 %s
77 %s
78 <title>AST display for %s</title>
79 </head>`, escName, ir.CSS, ir.JS("checked", "rangefunc"), escName)
80 w.Print("<body>")
81 w.Print("<h1>")
82 w.Print(escName)
83 w.Print("</h1>")
84 w.Print(`
85 <a href="#" onclick="toggle_visibility('help');return false;" id="helplink">help</a>
86 <div id="help">
87
88 <p>
89 Click anywhere on a node (with "cell" cursor) to outline a node and all of its subtrees.
90 </p>
91 <p>
92 Click on a name (with "crosshair" cursor) to highlight every occurrence of a name.
93 (Note that all the name nodes are the same node, so those also all outline together).
94 </p>
95 <p>
96 Click on a file, line, or column (with "crosshair" cursor) to highlight positions
97 in that file, at that file:line, or at that file:line:column, respectively.<br>Inlined
98 locations are not treated as a single location, but as a sequence of locations that
99 can be independently highlighted.
100 </p>
101 <p>
102 Click on a ` + ir.DownArrow + ` to collapse a subtree, or on a ` + ir.RightArrow + ` to expand a subtree.
103 </p>
104 <p>
105 Non-tree attributes, like scope and type lookups, are displayed in italics. Those may
106 also be clicked to highlight identity relationships within and between phases.
107 </p>
108
109 </div>
110 <label for="dark-mode-button" style="margin-left: 15px; cursor: pointer;">darkmode</label>
111 <input type="checkbox" onclick="toggleDarkMode();" id="dark-mode-button" style="cursor: pointer" />
112 `)
113 w.Print("<table>")
114 w.Print("<tr>")
115 }
116
117 func (w *HTMLWriter) DeclHTML(phase string) func() {
118 return func() {
119 w.Print("<pre>")
120 w.dumpScopeHTML(w.pkg.Scope(), 1, false)
121 w.dumpScopeHTML(w.info.Scopes[w.file], 1, false)
122 w.dumpNodeHTML(w.Decl, 1, "")
123 w.Print("</pre>")
124 }
125 }
126
127 func (h *HTMLWriter) dumpNodesHTML(list []syntax.Node, depth int) {
128 if len(list) == 0 {
129 h.Print(" <nil>")
130 return
131 }
132
133 for _, n := range list {
134 h.dumpNodeHTML(n, depth, "")
135 }
136 }
137
138 func isValid(t types2.Type) bool {
139 return t != nil && types2.Unalias(t) != types2.Typ[types2.Invalid]
140 }
141
142 const indentString = ". "
143
144 func (w *HTMLWriter) indent(n int) {
145 w.Print("\n")
146 for range n {
147 w.Print(indentString)
148 }
149 }
150
151
152 func (h *HTMLWriter) indentForToggle(depth int, hasChildren bool) {
153 h.Print("\n")
154 if depth == 0 {
155 return
156 }
157 for range depth - 1 {
158 h.Print(indentString)
159 }
160 if hasChildren {
161
162
163 h.Print(indentString[:len(indentString)-2])
164 } else {
165 h.Print(indentString)
166 }
167 }
168
169
170
171
172
173
174 func (h *HTMLWriter) dumpScopeHTML(s *types2.Scope, depth int, recur bool) {
175 hasChildren := true
176 h.indentForToggle(depth, hasChildren)
177 if hasChildren {
178 h.Printf("<span class=\"n%d scope\">", h.CanonId(s))
179 defer h.Printf("</span>")
180
181 h.Print(`<span class="toggle" onclick="toggle_node(this)">` + ir.DownArrow + `</span> `)
182 }
183 h.Printf("scope %s %p", html.EscapeString(s.Comment()), s)
184
185 if hasChildren {
186 h.Print(`<span class="node-body">`)
187 defer h.Print(`</span>`)
188
189 for _, name := range s.Names() {
190 obj := s.Lookup(name)
191 h.dumpOutlineNodeHTML(depth+1, "", obj)
192 }
193
194 if recur {
195 for i := range s.NumChildren() {
196 c := s.Child(i)
197 h.dumpScopeHTML(c, depth+1, recur)
198 }
199 }
200 }
201 }
202
203 func (h *HTMLWriter) dumpOutlineNodeHTML(depth int, pfx string, obj fmt.Stringer) {
204 h.indentForToggle(depth, false)
205 h.Printf("<span class=\"n%d outline-node\" style=\"font-style: italic;\">%s%s</span>",
206 h.CanonId(obj), pfx, html.EscapeString(obj.String()))
207 }
208
209 func (h *HTMLWriter) dumpNodeHTML(n syntax.Node, depth int, prefix string) {
210 hasChildren := h.nodeHasChildren(n)
211 h.indentForToggle(depth, hasChildren)
212
213 if depth > 40 {
214 h.Print("...")
215 return
216 }
217
218 if n == nil {
219 h.Print("NilSyntaxNode")
220 return
221 }
222
223 h.Printf("<span class=\"n%d outline-node\">", h.CanonId(n))
224 defer h.Printf("</span>")
225
226 if hasChildren {
227
228 h.Print(`<span class="toggle" onclick="toggle_node(this)">` + ir.DownArrow + `</span> `)
229 }
230
231 opName := strings.TrimPrefix(fmt.Sprintf("%T", n), "*syntax.")
232
233 if prefix != "" {
234 h.Printf("%s", html.EscapeString(prefix))
235 }
236
237 switch n := n.(type) {
238 case *syntax.BasicLit:
239 h.Printf("%s-%v", opName, html.EscapeString(n.Value))
240 h.dumpNodeHeaderHTML(n)
241 return
242
243 case *syntax.Name:
244 name := n.Value
245 hash := sha256.Sum256([]byte(name))
246 symID := "sym-" + hex.EncodeToString(hash[:6])
247 h.Printf("%s-<span class=\"%s variable-name\">%s</span>", opName, symID, html.EscapeString(name))
248 h.dumpNodeHeaderHTML(n)
249 if hasChildren {
250 h.Print(`<span class="node-body">`)
251 defer h.Print(`</span>`)
252
253 if obj := h.info.ObjectOf(n); obj != nil {
254 h.dumpOutlineNodeHTML(depth+1, "objectOf=", obj)
255 }
256 if typ := h.info.TypeOf(n); isValid(typ) {
257 h.dumpOutlineNodeHTML(depth+1, "typeOf=", typ)
258 }
259 }
260 return
261
262 case syntax.Expr:
263 h.Printf("%s", opName)
264 h.dumpNodeHeaderHTML(n)
265 if hasChildren {
266 h.Print(`<span class="node-body">`)
267 defer h.Print(`</span>`)
268
269 if typ := h.info.TypeOf(n); isValid(typ) {
270 h.dumpOutlineNodeHTML(depth+1, "typeOf=", typ)
271 }
272 }
273
274 default:
275 h.Printf("%s", opName)
276 h.dumpNodeHeaderHTML(n)
277 if hasChildren {
278 h.Print(`<span class="node-body">`)
279 defer h.Print(`</span>`)
280 }
281 }
282
283 if s := h.info.Scopes[n]; s != nil && s.Len() > 0 {
284 h.dumpScopeHTML(s, depth+1, false)
285 }
286
287 v := reflect.ValueOf(n).Elem()
288 t := v.Type()
289 nf := t.NumField()
290 for i := 0; i < nf; i++ {
291 tf := t.Field(i)
292 vf := v.Field(i)
293 if tf.PkgPath != "" {
294 continue
295 }
296 switch tf.Type.Kind() {
297 case reflect.Interface, reflect.Ptr, reflect.Slice:
298 if vf.IsNil() {
299 continue
300 }
301 }
302 name := strings.TrimSuffix(tf.Name, "_")
303
304 switch val := vf.Interface().(type) {
305 case syntax.Node:
306 if name != "" {
307 h.dumpNodeHTML(val, depth+1, name+": ")
308 } else {
309 h.dumpNodeHTML(val, depth+1, "")
310 }
311 default:
312 if vf.Kind() == reflect.Slice && vf.Type().Elem().Implements(nodeType) {
313 if vf.Len() == 0 {
314 continue
315 }
316 if name != "" {
317 for i := range vf.Len() {
318 h.dumpNodeHTML(vf.Index(i).Interface().(syntax.Node), depth+1,
319 fmt.Sprintf("%s[%d]: ", name, i))
320 }
321 } else {
322 for i := range vf.Len() {
323 h.dumpNodeHTML(vf.Index(i).Interface().(syntax.Node), depth+1, "")
324 }
325 }
326 }
327 }
328 }
329 }
330
331 var nodeType = reflect.TypeFor[syntax.Node]()
332
333 func (h *HTMLWriter) nodeHasChildren(n syntax.Node) bool {
334 if n == nil {
335 return false
336 }
337 switch x := n.(type) {
338 case *syntax.BasicLit:
339 return false
340 case *syntax.Name:
341 return h.info.ObjectOf(x) != nil || isValid(h.info.TypeOf(x))
342 case syntax.Expr:
343 if isValid(h.info.TypeOf(x)) {
344 return true
345 }
346 }
347
348 v := reflect.ValueOf(n).Elem()
349 t := reflect.TypeOf(n).Elem()
350 nf := t.NumField()
351 for i := 0; i < nf; i++ {
352 tf := t.Field(i)
353 vf := v.Field(i)
354 if tf.PkgPath != "" {
355 continue
356 }
357 switch tf.Type.Kind() {
358 case reflect.Interface, reflect.Ptr, reflect.Slice:
359 if vf.IsNil() {
360 continue
361 }
362 }
363 switch vf.Interface().(type) {
364 case syntax.Node:
365 return true
366 default:
367 if vf.Kind() == reflect.Slice && vf.Type().Elem().Implements(nodeType) && vf.Len() > 0 {
368 return true
369 }
370 }
371 }
372 return false
373 }
374
375 func (h *HTMLWriter) dumpNodeHeaderHTML(n syntax.Node) {
376 v := reflect.ValueOf(n).Elem()
377 t := v.Type()
378 nf := t.NumField()
379 for i := 0; i < nf; i++ {
380 tf := t.Field(i)
381 if tf.PkgPath != "" {
382 continue
383 }
384 k := tf.Type.Kind()
385 if reflect.Bool <= k && k <= reflect.Complex128 || k == reflect.String {
386 name := strings.TrimSuffix(tf.Name, "_")
387 if name == "Value" {
388 continue
389 }
390 vf := v.Field(i)
391 vfi := vf.Interface()
392 if vf.IsZero() {
393 continue
394 }
395 if vfi == true {
396 h.Printf(" %s", name)
397 } else {
398 h.Printf(" %s:%+v", name, html.EscapeString(fmt.Sprint(vf.Interface())))
399 }
400 }
401 }
402
403 if n.Pos().IsKnown() {
404 h.Print(" <span class=\"line-number\">")
405 file := n.Pos().Base().Filename()
406 if file != "" {
407 hash := sha256.Sum256([]byte(file))
408 fileID := "loc-" + hex.EncodeToString(hash[:6])
409 lineID := fmt.Sprintf("%s-L%d", fileID, n.Pos().Line())
410 colID := fmt.Sprintf("%s-C%d", lineID, n.Pos().Col())
411
412 h.Printf("<span class=\"%s line-number\">%s</span>:", fileID, html.EscapeString(filepath.Base(file)))
413 h.Printf("<span class=\"%s %s line-number\">%d</span>:", lineID, fileID, n.Pos().Line())
414 h.Printf("<span class=\"%s %s %s line-number\">%d</span>", colID, lineID, fileID, n.Pos().Col())
415 } else {
416 h.Printf("%v", html.EscapeString(n.Pos().String()))
417 }
418 h.Print("</span>")
419 }
420 }
421
View as plain text