@@ -27,45 +27,59 @@ import (
27
27
"cuelang.org/go/internal"
28
28
)
29
29
30
- // DebugPrint writes a multi-line Go-like representation of a syntax tree node,
30
+ // AppendDebug writes a multi-line Go-like representation of a syntax tree node,
31
31
// including node position information and any relevant Go types.
32
- //
33
- // Note that since this is an internal debugging API, [io.Writer] errors are ignored,
34
- // as it is assumed that the caller is using a [bytes.Buffer] or directly
35
- // writing to standard output.
36
- func DebugPrint (w io.Writer , node ast.Node ) {
37
- d := & debugPrinter {w : w }
38
- d .value (reflect .ValueOf (node ), nil )
39
- d .newline ()
32
+ func AppendDebug (dst []byte , node ast.Node , config DebugConfig ) []byte {
33
+ d := & debugPrinter {cfg : config }
34
+ dst = d .value (dst , reflect .ValueOf (node ), nil )
35
+ dst = d .newline (dst )
36
+ return dst
37
+ }
38
+
39
+ // DebugConfig configures the behavior of [AppendDebug].
40
+ type DebugConfig struct {
41
+ // Filter is called before each value in a syntax tree.
42
+ // Values for which the function returns false are omitted.
43
+ Filter func (reflect.Value ) bool
44
+
45
+ // OmitEmpty causes empty strings, empty structs, empty lists,
46
+ // nil pointers, invalid positions, and missing tokens to be omitted.
47
+ OmitEmpty bool
40
48
}
41
49
42
50
type debugPrinter struct {
43
51
w io.Writer
52
+ cfg DebugConfig
44
53
level int
45
54
}
46
55
47
- func (d * debugPrinter ) printf (format string , args ... any ) {
48
- fmt .Fprintf ( d . w , format , args ... )
56
+ func (d * debugPrinter ) printf (dst [] byte , format string , args ... any ) [] byte {
57
+ return fmt .Appendf ( dst , format , args ... )
49
58
}
50
59
51
- func (d * debugPrinter ) newline () {
52
- fmt .Fprintf ( d . w , "\n %s" , strings .Repeat ("\t " , d .level ))
60
+ func (d * debugPrinter ) newline (dst [] byte ) [] byte {
61
+ return fmt .Appendf ( dst , "\n %s" , strings .Repeat ("\t " , d .level ))
53
62
}
54
63
55
64
var (
56
65
typeTokenPos = reflect .TypeFor [token.Pos ]()
57
66
typeTokenToken = reflect .TypeFor [token.Token ]()
58
67
)
59
68
60
- func (d * debugPrinter ) value (v reflect.Value , impliedType reflect.Type ) {
69
+ func (d * debugPrinter ) value (dst []byte , v reflect.Value , impliedType reflect.Type ) []byte {
70
+ if d .cfg .Filter != nil && ! d .cfg .Filter (v ) {
71
+ return dst
72
+ }
61
73
// Skip over interface types.
62
74
if v .Kind () == reflect .Interface {
63
75
v = v .Elem ()
64
76
}
65
77
// Indirecting a nil interface gives a zero value.
66
78
if ! v .IsValid () {
67
- d .printf ("nil" )
68
- return
79
+ if ! d .cfg .OmitEmpty {
80
+ dst = d .printf (dst , "nil" )
81
+ }
82
+ return dst
69
83
}
70
84
71
85
// We print the original pointer type if there was one.
@@ -74,54 +88,72 @@ func (d *debugPrinter) value(v reflect.Value, impliedType reflect.Type) {
74
88
v = reflect .Indirect (v )
75
89
// Indirecting a nil pointer gives a zero value.
76
90
if ! v .IsValid () {
77
- d .printf ("nil" )
78
- return
91
+ if ! d .cfg .OmitEmpty {
92
+ dst = d .printf (dst , "nil" )
93
+ }
94
+ return dst
95
+ }
96
+
97
+ if d .cfg .OmitEmpty && v .IsZero () {
98
+ return dst
79
99
}
80
100
81
101
t := v .Type ()
82
102
switch t {
83
103
// Simple types which can stringify themselves.
84
104
case typeTokenPos , typeTokenToken :
85
- d .printf ("%s(%q)" , t , v )
86
- return
105
+ dst = d .printf (dst , "%s(%q)" , t , v )
106
+ return dst
87
107
}
88
108
109
+ undoValue := len (dst )
89
110
switch t .Kind () {
90
111
default :
91
112
// We assume all other kinds are basic in practice, like string or bool.
92
113
if t .PkgPath () != "" {
93
114
// Mention defined and non-predeclared types, for clarity.
94
- d .printf ("%s(%#v)" , t , v )
115
+ dst = d .printf (dst , "%s(%#v)" , t , v )
95
116
} else {
96
- d .printf ("%#v" , v )
117
+ dst = d .printf (dst , "%#v" , v )
97
118
}
98
119
99
120
case reflect .Slice :
100
121
if origType != impliedType {
101
- d .printf ("%s" , origType )
102
- }
103
- d .printf ("{" )
104
- if v .Len () > 0 {
105
- d .level ++
106
- for i := 0 ; i < v .Len (); i ++ {
107
- d .newline ()
108
- ev := v .Index (i )
109
- // Note: a slice literal implies the type of its elements
110
- // so we can avoid mentioning the type
111
- // of each element if it matches.
112
- d .value (ev , t .Elem ())
122
+ dst = d .printf (dst , "%s" , origType )
123
+ }
124
+ dst = d .printf (dst , "{" )
125
+ d .level ++
126
+ anyElems := false
127
+ for i := 0 ; i < v .Len (); i ++ {
128
+ ev := v .Index (i )
129
+ undoElem := len (dst )
130
+ dst = d .newline (dst )
131
+ // Note: a slice literal implies the type of its elements
132
+ // so we can avoid mentioning the type
133
+ // of each element if it matches.
134
+ if dst2 := d .value (dst , ev , t .Elem ()); len (dst2 ) == len (dst ) {
135
+ dst = dst [:undoElem ]
136
+ } else {
137
+ dst = dst2
138
+ anyElems = true
113
139
}
114
- d .level --
115
- d .newline ()
116
140
}
117
- d .printf ("}" )
141
+ d .level --
142
+ if ! anyElems && d .cfg .OmitEmpty {
143
+ dst = dst [:undoValue ]
144
+ } else {
145
+ if anyElems {
146
+ dst = d .newline (dst )
147
+ }
148
+ dst = d .printf (dst , "}" )
149
+ }
118
150
119
151
case reflect .Struct :
120
152
if origType != impliedType {
121
- d .printf ("%s" , origType )
153
+ dst = d .printf (dst , "%s" , origType )
122
154
}
123
- d .printf ("{" )
124
- printed := false
155
+ dst = d .printf (dst , "{" )
156
+ anyElems := false
125
157
d .level ++
126
158
for i := 0 ; i < v .NumField (); i ++ {
127
159
f := t .Field (i )
@@ -133,28 +165,38 @@ func (d *debugPrinter) value(v reflect.Value, impliedType reflect.Type) {
133
165
case "Scope" , "Node" , "Unresolved" :
134
166
continue
135
167
}
136
- printed = true
137
- d .newline ()
138
- d .printf ("%s: " , f .Name )
139
- d .value (v .Field (i ), nil )
168
+ undoElem := len (dst )
169
+ dst = d .newline (dst )
170
+ dst = d .printf (dst , "%s: " , f .Name )
171
+ if dst2 := d .value (dst , v .Field (i ), nil ); len (dst2 ) == len (dst ) {
172
+ dst = dst [:undoElem ]
173
+ } else {
174
+ dst = dst2
175
+ anyElems = true
176
+ }
140
177
}
141
178
val := v .Addr ().Interface ()
142
179
if val , ok := val .(ast.Node ); ok {
143
180
// Comments attached to a node aren't a regular field, but are still useful.
144
181
// The majority of nodes won't have comments, so skip them when empty.
145
182
if comments := ast .Comments (val ); len (comments ) > 0 {
146
- printed = true
147
- d .newline ()
148
- d .printf ("Comments: " )
149
- d .value (reflect .ValueOf (comments ), nil )
183
+ anyElems = true
184
+ dst = d .newline (dst )
185
+ dst = d .printf (dst , "Comments: " )
186
+ dst = d .value (dst , reflect .ValueOf (comments ), nil )
150
187
}
151
188
}
152
189
d .level --
153
- if printed {
154
- d .newline ()
190
+ if ! anyElems && d .cfg .OmitEmpty {
191
+ dst = dst [:undoValue ]
192
+ } else {
193
+ if anyElems {
194
+ dst = d .newline (dst )
195
+ }
196
+ dst = d .printf (dst , "}" )
155
197
}
156
- d .printf ("}" )
157
198
}
199
+ return dst
158
200
}
159
201
160
202
func DebugStr (x interface {}) (out string ) {
0 commit comments