1// Copyright 2021 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package astinternal
16
17import (
18 "fmt"
19 gotoken "go/token"
20 "reflect"
21 "strconv"
22 "strings"
23
24 "cuelang.org/go/cue/ast"
25 "cuelang.org/go/cue/token"
26)
27
28// AppendDebug writes a multi-line Go-like representation of a syntax tree node,
29// including node position information and any relevant Go types.
30func AppendDebug(dst []byte, node ast.Node, config DebugConfig) []byte {
31 d := &debugPrinter{
32 cfg: config,
33 buf: dst,
34 }
35 if config.IncludeNodeRefs {
36 d.nodeRefs = make(map[ast.Node]int)
37 d.addNodeRefs(reflect.ValueOf(node))
38 }
39 if d.value(reflect.ValueOf(node), nil) {
40 d.newline()
41 }
42 return d.buf
43}
44
45// DebugConfig configures the behavior of [AppendDebug].
46type DebugConfig struct {
47 // Filter is called before each value in a syntax tree.
48 // Values for which the function returns false are omitted.
49 Filter func(reflect.Value) bool
50
51 // OmitEmpty causes empty strings, empty structs, empty lists,
52 // nil pointers, invalid positions, and missing tokens to be omitted.
53 OmitEmpty bool
54
55 // IncludeNodeRefs causes a Node reference in an identifier
56 // to indicate which (if any) ast.Node it refers to.
57 IncludeNodeRefs bool
58
59 // IncludePointers causes all nodes to be printed with their pointer
60 // values; setting this also implies [DebugConfig.IncludeNodeRefs]
61 // and references will be printed as pointers.
62 IncludePointers bool
63
64 // AllPositions causes all [ast.Node] implementions to emit their start
65 // and end positions.
66 AllPositions bool
67}
68
69type debugPrinter struct {
70 buf []byte
71 cfg DebugConfig
72 level int
73 nodeRefs map[ast.Node]int
74 refID int
75}
76
77// value produces the given value, omitting type information if
78// its type is the same as implied type. It reports whether
79// anything was produced.
80func (d *debugPrinter) value(v reflect.Value, impliedType reflect.Type) bool {
81 start := d.pos()
82 d.value0(v, impliedType)
83 return d.pos() > start
84}
85
86func (d *debugPrinter) value0(v reflect.Value, impliedType reflect.Type) {
87 if d.cfg.Filter != nil && !d.cfg.Filter(v) {
88 return
89 }
90 // Skip over interfaces and pointers, stopping early if nil.
91 concreteType := v.Type()
92 refName := ""
93 var startPos, endPos token.Pos
94 ptrVal := uintptr(0)
95 for {
96 k := v.Kind()
97 if k != reflect.Interface && k != reflect.Pointer {
98 break
99 }
100 if v.IsNil() {
101 if !d.cfg.OmitEmpty {
102 d.printf("nil")
103 }
104 return
105 }
106 if k == reflect.Pointer {
107 if n, ok := v.Interface().(ast.Node); ok {
108 ptrVal = v.Pointer()
109 if id, ok := d.nodeRefs[n]; ok {
110 refName = refIDToName(id)
111 }
112 startPos, endPos = n.Pos(), n.End()
113 }
114 }
115 v = v.Elem()
116 if k == reflect.Interface {
117 // For example, *ast.Ident can be the concrete type behind an ast.Expr.
118 concreteType = v.Type()
119 }
120 }
121
122 if d.cfg.OmitEmpty && v.IsZero() {
123 return
124 }
125
126 t := v.Type()
127 switch v := v.Interface().(type) {
128 // Simple types which can stringify themselves.
129 case token.Pos:
130 d.printf("%s(%q", t, v)
131 // Show relative positions too, if there are any, as they affect formatting.
132 if v.HasRelPos() {
133 d.printf(", %v", v.RelPos())
134 }
135 d.printf(")")
136 return
137 case token.Token:
138 d.printf("%s(%q)", t, v)
139 return
140 }
141
142 switch t.Kind() {
143 default:
144 // We assume all other kinds are basic in practice, like string or bool.
145 if t.PkgPath() != "" {
146 // Mention defined and non-predeclared types, for clarity.
147 d.printf("%s(%#v)", t, v)
148 } else {
149 d.printf("%#v", v)
150 }
151
152 case reflect.Slice, reflect.Struct:
153 valueStart := d.pos()
154 // We print the concrete type when it differs from an implied type.
155 if concreteType != impliedType {
156 d.printf("%s", concreteType)
157 }
158 if d.cfg.IncludePointers {
159 if ptrVal != 0 {
160 d.printf("@%#x", ptrVal)
161 }
162 } else if refName != "" {
163 d.printf("@%s", refName)
164 }
165 if d.cfg.AllPositions && startPos.IsValid() {
166 d.printf("[%v]", positionRange(startPos, endPos))
167 }
168 d.printf("{")
169 d.level++
170 var anyElems bool
171 if t.Kind() == reflect.Slice {
172 anyElems = d.sliceElems(v, t.Elem())
173 } else {
174 anyElems = d.structFields(v)
175 }
176 d.level--
177 if !anyElems && d.cfg.OmitEmpty {
178 d.truncate(valueStart)
179 } else {
180 if anyElems {
181 d.newline()
182 }
183 d.printf("}")
184 }
185 }
186}
187
188// positionRange returns a string representing the range
189// of positions between p0 and p1, as returned
190// by [ast.Node.Pos] and [ast.Node.End] respectively.
191func positionRange(p0, p1 token.Pos) string {
192 if !p1.IsValid() {
193 return p0.String()
194 }
195 pos0, pos1 := p0.Position(), p1.Position()
196 if pos1.Filename != pos0.Filename {
197 return fmt.Sprintf("%v,%v", pos0, pos1)
198 }
199 var buf strings.Builder
200 if len(pos0.Filename) != 0 {
201 buf.WriteString(pos0.Filename)
202 buf.WriteString(":")
203 }
204 fmt.Fprintf(&buf, "%d:%d,%d:%d", pos0.Line, pos0.Column, pos1.Line, pos1.Column)
205 return buf.String()
206}
207
208func (d *debugPrinter) sliceElems(v reflect.Value, elemType reflect.Type) (anyElems bool) {
209 for i := 0; i < v.Len(); i++ {
210 ev := v.Index(i)
211 elemStart := d.pos()
212 d.newline()
213 // Note: a slice literal implies the type of its elements
214 // so we can avoid mentioning the type
215 // of each element if it matches.
216 if d.value(ev, elemType) {
217 anyElems = true
218 } else {
219 d.truncate(elemStart)
220 }
221 }
222 return anyElems
223}
224
225func (d *debugPrinter) structFields(v reflect.Value) (anyElems bool) {
226 t := v.Type()
227 for i := range v.NumField() {
228 f := t.Field(i)
229 if !gotoken.IsExported(f.Name) {
230 continue
231 }
232 if f.Name == "Node" {
233 nodeVal := v.Field(i)
234 if (!d.cfg.IncludeNodeRefs && !d.cfg.IncludePointers) || nodeVal.IsNil() {
235 continue
236 }
237 d.newline()
238 if d.cfg.IncludePointers {
239 if nodeVal.Kind() == reflect.Interface {
240 nodeVal = nodeVal.Elem()
241 }
242 d.printf("Node: @%#v (%v)", nodeVal.Pointer(), nodeVal.Elem().Type())
243 } else {
244 d.printf("Node: @%s (%v)", refIDToName(d.nodeRefs[nodeVal.Interface().(ast.Node)]), nodeVal.Elem().Type())
245 }
246 continue
247 }
248 switch f.Name {
249 // These fields are cyclic, and they don't represent the syntax anyway.
250 case "Scope", "Unresolved":
251 continue
252 }
253 elemStart := d.pos()
254 d.newline()
255 d.printf("%s: ", f.Name)
256 if d.value(v.Field(i), nil) {
257 anyElems = true
258 } else {
259 d.truncate(elemStart)
260 }
261 }
262 val := v.Addr().Interface()
263 if val, ok := val.(ast.Node); ok {
264 // Comments attached to a node aren't a regular field, but are still useful.
265 // The majority of nodes won't have comments, so skip them when empty.
266 if comments := ast.Comments(val); len(comments) > 0 {
267 anyElems = true
268 d.newline()
269 d.printf("Comments: ")
270 d.value(reflect.ValueOf(comments), nil)
271 }
272 }
273 return anyElems
274}
275
276func (d *debugPrinter) printf(format string, args ...any) {
277 d.buf = fmt.Appendf(d.buf, format, args...)
278}
279
280func (d *debugPrinter) newline() {
281 d.buf = fmt.Appendf(d.buf, "\n%s", strings.Repeat("\t", d.level))
282}
283
284func (d *debugPrinter) pos() int {
285 return len(d.buf)
286}
287
288func (d *debugPrinter) truncate(pos int) {
289 d.buf = d.buf[:pos]
290}
291
292// addNodeRefs does a first pass over the value looking for
293// [ast.Ident] nodes that refer to other nodes.
294// This means when we find such a node, we can include
295// an anchor name for it
296func (d *debugPrinter) addNodeRefs(v reflect.Value) {
297 // Skip over interfaces and pointers, stopping early if nil.
298 for ; v.Kind() == reflect.Interface || v.Kind() == reflect.Pointer; v = v.Elem() {
299 if v.IsNil() {
300 return
301 }
302 }
303
304 t := v.Type()
305 switch v := v.Interface().(type) {
306 case token.Pos, token.Token:
307 // Simple types which can't contain an ast.Node.
308 return
309 case ast.Ident:
310 if v.Node != nil {
311 if _, ok := d.nodeRefs[v.Node]; !ok {
312 d.refID++
313 d.nodeRefs[v.Node] = d.refID
314 }
315 }
316 return
317 }
318
319 switch t.Kind() {
320 case reflect.Slice:
321 for i := 0; i < v.Len(); i++ {
322 d.addNodeRefs(v.Index(i))
323 }
324 case reflect.Struct:
325 t := v.Type()
326 for i := range v.NumField() {
327 f := t.Field(i)
328 if !gotoken.IsExported(f.Name) {
329 continue
330 }
331 switch f.Name {
332 // These fields don't point to any nodes that Node can refer to.
333 case "Scope", "Node", "Unresolved":
334 continue
335 }
336 d.addNodeRefs(v.Field(i))
337 }
338 }
339}
340
341func refIDToName(id int) string {
342 if id == 0 {
343 return "unknown"
344 }
345 return fmt.Sprintf("ref%03d", id)
346}
347
348func DebugStr(x interface{}) (out string) {
349 if n, ok := x.(ast.Node); ok {
350 comments := ""
351 for _, g := range ast.Comments(n) {
352 comments += DebugStr(g)
353 }
354 if comments != "" {
355 defer func() { out = "<" + comments + out + ">" }()
356 }
357 }
358 switch v := x.(type) {
359 case *ast.File:
360 out := ""
361 out += DebugStr(v.Decls)
362 return out
363
364 case *ast.Package:
365 out := "package "
366 out += DebugStr(v.Name)
367 return out
368
369 case *ast.LetClause:
370 out := "let "
371 out += DebugStr(v.Ident)
372 out += "="
373 out += DebugStr(v.Expr)
374 return out
375
376 case *ast.Alias:
377 out := DebugStr(v.Ident)
378 out += "="
379 out += DebugStr(v.Expr)
380 return out
381
382 case *ast.BottomLit:
383 return "_|_"
384
385 case *ast.BasicLit:
386 return v.Value
387
388 case *ast.Interpolation:
389 for _, e := range v.Elts {
390 out += DebugStr(e)
391 }
392 return out
393
394 case *ast.EmbedDecl:
395 out += DebugStr(v.Expr)
396 return out
397
398 case *ast.ImportDecl:
399 out := "import "
400 if v.Lparen.IsValid() {
401 out += "( "
402 out += DebugStr(v.Specs)
403 out += " )"
404 } else {
405 out += DebugStr(v.Specs)
406 }
407 return out
408
409 case *ast.Comprehension:
410 out := DebugStr(v.Clauses)
411 out += DebugStr(v.Value)
412 return out
413
414 case *ast.StructLit:
415 out := "{"
416 out += DebugStr(v.Elts)
417 out += "}"
418 return out
419
420 case *ast.ListLit:
421 out := "["
422 out += DebugStr(v.Elts)
423 out += "]"
424 return out
425
426 case *ast.Ellipsis:
427 out := "..."
428 if v.Type != nil {
429 out += DebugStr(v.Type)
430 }
431 return out
432
433 case *ast.ForClause:
434 out := "for "
435 if v.Key != nil {
436 out += DebugStr(v.Key)
437 out += ": "
438 }
439 out += DebugStr(v.Value)
440 out += " in "
441 out += DebugStr(v.Source)
442 return out
443
444 case *ast.IfClause:
445 out := "if "
446 out += DebugStr(v.Condition)
447 return out
448
449 case *ast.Field:
450 out := DebugStr(v.Label)
451 if v.Alias != nil {
452 out += "~"
453 if v.Alias.Label != nil {
454 // Dual form
455 out += "("
456 out += DebugStr(v.Alias.Label)
457 out += ","
458 out += DebugStr(v.Alias.Field)
459 out += ")"
460 } else {
461 // Simple form
462 out += DebugStr(v.Alias.Field)
463 }
464 }
465 if t := v.Constraint; t != token.ILLEGAL {
466 out += t.String()
467 }
468 if v.Value != nil {
469 out += ": "
470 out += DebugStr(v.Value)
471 for _, a := range v.Attrs {
472 out += " "
473 out += DebugStr(a)
474 }
475 }
476 return out
477
478 case *ast.Attribute:
479 return v.Text
480
481 case *ast.Ident:
482 return v.Name
483
484 case *ast.SelectorExpr:
485 return DebugStr(v.X) + "." + DebugStr(v.Sel)
486
487 case *ast.CallExpr:
488 out := DebugStr(v.Fun)
489 out += "("
490 out += DebugStr(v.Args)
491 out += ")"
492 return out
493
494 case *ast.ParenExpr:
495 out := "("
496 out += DebugStr(v.X)
497 out += ")"
498 return out
499
500 case *ast.UnaryExpr:
501 return v.Op.String() + DebugStr(v.X)
502
503 case *ast.BinaryExpr:
504 out := DebugStr(v.X)
505 op := v.Op.String()
506 if 'a' <= op[0] && op[0] <= 'z' {
507 op = fmt.Sprintf(" %s ", op)
508 }
509 out += op
510 out += DebugStr(v.Y)
511 return out
512
513 case *ast.PostfixExpr:
514 return DebugStr(v.X) + v.Op.String()
515
516 case []*ast.CommentGroup:
517 var a []string
518 for _, c := range v {
519 a = append(a, DebugStr(c))
520 }
521 return strings.Join(a, "\n")
522
523 case *ast.CommentGroup:
524 str := "["
525 if v.Doc {
526 str += "d"
527 }
528 if v.Line {
529 str += "l"
530 }
531 str += strconv.Itoa(int(v.Position))
532 var a = []string{}
533 for _, c := range v.List {
534 a = append(a, c.Text)
535 }
536 return str + strings.Join(a, " ") + "] "
537
538 case *ast.IndexExpr:
539 out := DebugStr(v.X)
540 out += "["
541 out += DebugStr(v.Index)
542 out += "]"
543 return out
544
545 case *ast.SliceExpr:
546 out := DebugStr(v.X)
547 out += "["
548 out += DebugStr(v.Low)
549 out += ":"
550 out += DebugStr(v.High)
551 out += "]"
552 return out
553
554 case *ast.ImportSpec:
555 out := ""
556 if v.Name != nil {
557 out += DebugStr(v.Name)
558 out += " "
559 }
560 out += DebugStr(v.Path)
561 return out
562
563 case *ast.Func:
564 return fmt.Sprintf("func(%v): %v", DebugStr(v.Args), DebugStr(v.Ret))
565
566 case []ast.Decl:
567 if len(v) == 0 {
568 return ""
569 }
570 out := ""
571 for _, d := range v {
572 out += DebugStr(d)
573 out += sep
574 }
575 return out[:len(out)-len(sep)]
576
577 case []ast.Clause:
578 if len(v) == 0 {
579 return ""
580 }
581 out := ""
582 for _, c := range v {
583 out += DebugStr(c)
584 out += " "
585 }
586 return out
587
588 case []ast.Expr:
589 if len(v) == 0 {
590 return ""
591 }
592 out := ""
593 for _, d := range v {
594 out += DebugStr(d)
595 out += sep
596 }
597 return out[:len(out)-len(sep)]
598
599 case []*ast.ImportSpec:
600 if len(v) == 0 {
601 return ""
602 }
603 out := ""
604 for _, d := range v {
605 out += DebugStr(d)
606 out += sep
607 }
608 return out[:len(out)-len(sep)]
609
610 default:
611 if v == nil {
612 return ""
613 }
614 return fmt.Sprintf("<%T>", x)
615 }
616}
617
618const sep = ", "