Skip to content

Commit 32865bc

Browse files
committed
internal/astinternal: simpler internal API
As suggested in a too-tardy review here (https://review.gerrithub.io/c/cue-lang/cue/+/1200204) the explicit append API is kinda verbose and the undo logic is hard to follow. By putting the buffer into the debugPrinter type, everything becomes a bit cleaner. We also make share the same wrapping logic between slices and structs, making the commonality clearer. While we're about it, deal with nil interfaces and nil concrete types in the same place, making the logic a little simpler again. Signed-off-by: Roger Peppe <[email protected]> Change-Id: Iba81a933754eef172b59c6f71b535cf21948afd1 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1200225 TryBot-Result: CUEcueckoo <[email protected]> Unity-Result: CUE porcuepine <[email protected]> Reviewed-by: Daniel Martí <[email protected]>
1 parent ecbcf34 commit 32865bc

File tree

1 file changed

+107
-97
lines changed

1 file changed

+107
-97
lines changed

internal/astinternal/debug.go

+107-97
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,14 @@ import (
3030
// AppendDebug writes a multi-line Go-like representation of a syntax tree node,
3131
// including node position information and any relevant Go types.
3232
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
33+
d := &debugPrinter{
34+
cfg: config,
35+
buf: dst,
36+
}
37+
if d.value(reflect.ValueOf(node), nil) {
38+
d.newline()
39+
}
40+
return d.buf
3741
}
3842

3943
// DebugConfig configures the behavior of [AppendDebug].
@@ -49,161 +53,167 @@ type DebugConfig struct {
4953

5054
type debugPrinter struct {
5155
w io.Writer
56+
buf []byte
5257
cfg DebugConfig
5358
level int
5459
}
5560

56-
func (d *debugPrinter) printf(dst []byte, format string, args ...any) []byte {
57-
return fmt.Appendf(dst, format, args...)
58-
}
59-
60-
func (d *debugPrinter) newline(dst []byte) []byte {
61-
return fmt.Appendf(dst, "\n%s", strings.Repeat("\t", d.level))
62-
}
63-
6461
var (
6562
typeTokenPos = reflect.TypeFor[token.Pos]()
6663
typeTokenToken = reflect.TypeFor[token.Token]()
6764
)
6865

69-
func (d *debugPrinter) value(dst []byte, v reflect.Value, impliedType reflect.Type) []byte {
66+
// value produces the given value, omitting type information if
67+
// its type is the same as implied type. It reports whether
68+
// anything was produced.
69+
func (d *debugPrinter) value(v reflect.Value, impliedType reflect.Type) bool {
70+
start := d.pos()
71+
d.value0(v, impliedType)
72+
return d.pos() > start
73+
}
74+
75+
func (d *debugPrinter) value0(v reflect.Value, impliedType reflect.Type) {
7076
if d.cfg.Filter != nil && !d.cfg.Filter(v) {
71-
return dst
77+
return
7278
}
7379
// Skip over interface types.
7480
if v.Kind() == reflect.Interface {
7581
v = v.Elem()
7682
}
77-
// Indirecting a nil interface gives a zero value.
78-
if !v.IsValid() {
83+
// Indirecting a nil interface gives a zero value. We
84+
// can also have a nil pointer inside a non-nil interface.
85+
if !v.IsValid() || (v.Kind() == reflect.Pointer && v.IsNil()) {
7986
if !d.cfg.OmitEmpty {
80-
dst = d.printf(dst, "nil")
87+
d.printf("nil")
8188
}
82-
return dst
89+
return
8390
}
8491

8592
// We print the original pointer type if there was one.
8693
origType := v.Type()
8794

8895
v = reflect.Indirect(v)
89-
// Indirecting a nil pointer gives a zero value.
90-
if !v.IsValid() {
91-
if !d.cfg.OmitEmpty {
92-
dst = d.printf(dst, "nil")
93-
}
94-
return dst
95-
}
9696

9797
if d.cfg.OmitEmpty && v.IsZero() {
98-
return dst
98+
return
9999
}
100100

101101
t := v.Type()
102102
switch t {
103103
// Simple types which can stringify themselves.
104104
case typeTokenPos, typeTokenToken:
105-
dst = d.printf(dst, "%s(%q)", t, v)
105+
d.printf("%s(%q)", t, v)
106106
// Show relative positions too, if there are any, as they affect formatting.
107107
if t == typeTokenPos {
108108
pos := v.Interface().(token.Pos)
109109
if pos.HasRelPos() {
110-
dst = d.printf(dst, ".WithRel(%q)", pos.RelPos())
110+
d.printf(".WithRel(%q)", pos.RelPos())
111111
}
112112
}
113-
return dst
113+
return
114114
}
115115

116-
undoValue := len(dst)
117116
switch t.Kind() {
118117
default:
119118
// We assume all other kinds are basic in practice, like string or bool.
120119
if t.PkgPath() != "" {
121120
// Mention defined and non-predeclared types, for clarity.
122-
dst = d.printf(dst, "%s(%#v)", t, v)
121+
d.printf("%s(%#v)", t, v)
123122
} else {
124-
dst = d.printf(dst, "%#v", v)
123+
d.printf("%#v", v)
125124
}
126125

127-
case reflect.Slice:
126+
case reflect.Slice, reflect.Struct:
127+
valueStart := d.pos()
128128
if origType != impliedType {
129-
dst = d.printf(dst, "%s", origType)
129+
d.printf("%s", origType)
130130
}
131-
dst = d.printf(dst, "{")
131+
d.printf("{")
132132
d.level++
133-
anyElems := false
134-
for i := 0; i < v.Len(); i++ {
135-
ev := v.Index(i)
136-
undoElem := len(dst)
137-
dst = d.newline(dst)
138-
// Note: a slice literal implies the type of its elements
139-
// so we can avoid mentioning the type
140-
// of each element if it matches.
141-
if dst2 := d.value(dst, ev, t.Elem()); len(dst2) == len(dst) {
142-
dst = dst[:undoElem]
143-
} else {
144-
dst = dst2
145-
anyElems = true
146-
}
133+
var anyElems bool
134+
if t.Kind() == reflect.Slice {
135+
anyElems = d.sliceElems(v, t.Elem())
136+
} else {
137+
anyElems = d.structFields(v)
147138
}
148139
d.level--
149140
if !anyElems && d.cfg.OmitEmpty {
150-
dst = dst[:undoValue]
141+
d.truncate(valueStart)
151142
} else {
152143
if anyElems {
153-
dst = d.newline(dst)
144+
d.newline()
154145
}
155-
dst = d.printf(dst, "}")
146+
d.printf("}")
156147
}
148+
}
149+
}
157150

158-
case reflect.Struct:
159-
if origType != impliedType {
160-
dst = d.printf(dst, "%s", origType)
161-
}
162-
dst = d.printf(dst, "{")
163-
anyElems := false
164-
d.level++
165-
for i := 0; i < v.NumField(); i++ {
166-
f := t.Field(i)
167-
if !gotoken.IsExported(f.Name) {
168-
continue
169-
}
170-
switch f.Name {
171-
// These fields are cyclic, and they don't represent the syntax anyway.
172-
case "Scope", "Node", "Unresolved":
173-
continue
174-
}
175-
undoElem := len(dst)
176-
dst = d.newline(dst)
177-
dst = d.printf(dst, "%s: ", f.Name)
178-
if dst2 := d.value(dst, v.Field(i), nil); len(dst2) == len(dst) {
179-
dst = dst[:undoElem]
180-
} else {
181-
dst = dst2
182-
anyElems = true
183-
}
184-
}
185-
val := v.Addr().Interface()
186-
if val, ok := val.(ast.Node); ok {
187-
// Comments attached to a node aren't a regular field, but are still useful.
188-
// The majority of nodes won't have comments, so skip them when empty.
189-
if comments := ast.Comments(val); len(comments) > 0 {
190-
anyElems = true
191-
dst = d.newline(dst)
192-
dst = d.printf(dst, "Comments: ")
193-
dst = d.value(dst, reflect.ValueOf(comments), nil)
194-
}
151+
func (d *debugPrinter) sliceElems(v reflect.Value, elemType reflect.Type) (anyElems bool) {
152+
for i := 0; i < v.Len(); i++ {
153+
ev := v.Index(i)
154+
elemStart := d.pos()
155+
d.newline()
156+
// Note: a slice literal implies the type of its elements
157+
// so we can avoid mentioning the type
158+
// of each element if it matches.
159+
if d.value(ev, elemType) {
160+
anyElems = true
161+
} else {
162+
d.truncate(elemStart)
195163
}
196-
d.level--
197-
if !anyElems && d.cfg.OmitEmpty {
198-
dst = dst[:undoValue]
164+
}
165+
return anyElems
166+
}
167+
168+
func (d *debugPrinter) structFields(v reflect.Value) (anyElems bool) {
169+
t := v.Type()
170+
for i := 0; i < v.NumField(); i++ {
171+
f := t.Field(i)
172+
if !gotoken.IsExported(f.Name) {
173+
continue
174+
}
175+
switch f.Name {
176+
// These fields are cyclic, and they don't represent the syntax anyway.
177+
case "Scope", "Node", "Unresolved":
178+
continue
179+
}
180+
elemStart := d.pos()
181+
d.newline()
182+
d.printf("%s: ", f.Name)
183+
if d.value(v.Field(i), nil) {
184+
anyElems = true
199185
} else {
200-
if anyElems {
201-
dst = d.newline(dst)
202-
}
203-
dst = d.printf(dst, "}")
186+
d.truncate(elemStart)
204187
}
205188
}
206-
return dst
189+
val := v.Addr().Interface()
190+
if val, ok := val.(ast.Node); ok {
191+
// Comments attached to a node aren't a regular field, but are still useful.
192+
// The majority of nodes won't have comments, so skip them when empty.
193+
if comments := ast.Comments(val); len(comments) > 0 {
194+
anyElems = true
195+
d.newline()
196+
d.printf("Comments: ")
197+
d.value(reflect.ValueOf(comments), nil)
198+
}
199+
}
200+
return anyElems
201+
}
202+
203+
func (d *debugPrinter) printf(format string, args ...any) {
204+
d.buf = fmt.Appendf(d.buf, format, args...)
205+
}
206+
207+
func (d *debugPrinter) newline() {
208+
d.buf = fmt.Appendf(d.buf, "\n%s", strings.Repeat("\t", d.level))
209+
}
210+
211+
func (d *debugPrinter) pos() int {
212+
return len(d.buf)
213+
}
214+
215+
func (d *debugPrinter) truncate(pos int) {
216+
d.buf = d.buf[:pos]
207217
}
208218

209219
func DebugStr(x interface{}) (out string) {

0 commit comments

Comments
 (0)