1// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// This file contains printing support for ASTs.
6
7package ast
8
9import (
10	"fmt"
11	"go/token"
12	"io"
13	"os"
14	"reflect"
15)
16
17// A FieldFilter may be provided to Fprint to control the output.
18type FieldFilter func(name string, value reflect.Value) bool
19
20// NotNilFilter returns true for field values that are not nil;
21// it returns false otherwise.
22func NotNilFilter(_ string, v reflect.Value) bool {
23	switch v.Kind() {
24	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
25		return !v.IsNil()
26	}
27	return true
28}
29
30// Fprint prints the (sub-)tree starting at AST node x to w.
31// If fset != nil, position information is interpreted relative
32// to that file set. Otherwise positions are printed as integer
33// values (file set specific offsets).
34//
35// A non-nil FieldFilter f may be provided to control the output:
36// struct fields for which f(fieldname, fieldvalue) is true are
37// printed; all others are filtered from the output. Unexported
38// struct fields are never printed.
39func Fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) error {
40	return fprint(w, fset, x, f)
41}
42
43func fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (err error) {
44	// setup printer
45	p := printer{
46		output: w,
47		fset:   fset,
48		filter: f,
49		ptrmap: make(map[interface{}]int),
50		last:   '\n', // force printing of line number on first line
51	}
52
53	// install error handler
54	defer func() {
55		if e := recover(); e != nil {
56			err = e.(localError).err // re-panics if it's not a localError
57		}
58	}()
59
60	// print x
61	if x == nil {
62		p.printf("nil\n")
63		return
64	}
65	p.print(reflect.ValueOf(x))
66	p.printf("\n")
67
68	return
69}
70
71// Print prints x to standard output, skipping nil fields.
72// Print(fset, x) is the same as Fprint(os.Stdout, fset, x, NotNilFilter).
73func Print(fset *token.FileSet, x interface{}) error {
74	return Fprint(os.Stdout, fset, x, NotNilFilter)
75}
76
77type printer struct {
78	output io.Writer
79	fset   *token.FileSet
80	filter FieldFilter
81	ptrmap map[interface{}]int // *T -> line number
82	indent int                 // current indentation level
83	last   byte                // the last byte processed by Write
84	line   int                 // current line number
85}
86
87var indent = []byte(".  ")
88
89func (p *printer) Write(data []byte) (n int, err error) {
90	var m int
91	for i, b := range data {
92		// invariant: data[0:n] has been written
93		if b == '\n' {
94			m, err = p.output.Write(data[n : i+1])
95			n += m
96			if err != nil {
97				return
98			}
99			p.line++
100		} else if p.last == '\n' {
101			_, err = fmt.Fprintf(p.output, "%6d  ", p.line)
102			if err != nil {
103				return
104			}
105			for j := p.indent; j > 0; j-- {
106				_, err = p.output.Write(indent)
107				if err != nil {
108					return
109				}
110			}
111		}
112		p.last = b
113	}
114	if len(data) > n {
115		m, err = p.output.Write(data[n:])
116		n += m
117	}
118	return
119}
120
121// localError wraps locally caught errors so we can distinguish
122// them from genuine panics which we don't want to return as errors.
123type localError struct {
124	err error
125}
126
127// printf is a convenience wrapper that takes care of print errors.
128func (p *printer) printf(format string, args ...interface{}) {
129	if _, err := fmt.Fprintf(p, format, args...); err != nil {
130		panic(localError{err})
131	}
132}
133
134// Implementation note: Print is written for AST nodes but could be
135// used to print arbitrary data structures; such a version should
136// probably be in a different package.
137//
138// Note: This code detects (some) cycles created via pointers but
139// not cycles that are created via slices or maps containing the
140// same slice or map. Code for general data structures probably
141// should catch those as well.
142
143func (p *printer) print(x reflect.Value) {
144	if !NotNilFilter("", x) {
145		p.printf("nil")
146		return
147	}
148
149	switch x.Kind() {
150	case reflect.Interface:
151		p.print(x.Elem())
152
153	case reflect.Map:
154		p.printf("%s (len = %d) {", x.Type(), x.Len())
155		if x.Len() > 0 {
156			p.indent++
157			p.printf("\n")
158			for _, key := range x.MapKeys() {
159				p.print(key)
160				p.printf(": ")
161				p.print(x.MapIndex(key))
162				p.printf("\n")
163			}
164			p.indent--
165		}
166		p.printf("}")
167
168	case reflect.Ptr:
169		p.printf("*")
170		// type-checked ASTs may contain cycles - use ptrmap
171		// to keep track of objects that have been printed
172		// already and print the respective line number instead
173		ptr := x.Interface()
174		if line, exists := p.ptrmap[ptr]; exists {
175			p.printf("(obj @ %d)", line)
176		} else {
177			p.ptrmap[ptr] = p.line
178			p.print(x.Elem())
179		}
180
181	case reflect.Array:
182		p.printf("%s {", x.Type())
183		if x.Len() > 0 {
184			p.indent++
185			p.printf("\n")
186			for i, n := 0, x.Len(); i < n; i++ {
187				p.printf("%d: ", i)
188				p.print(x.Index(i))
189				p.printf("\n")
190			}
191			p.indent--
192		}
193		p.printf("}")
194
195	case reflect.Slice:
196		if s, ok := x.Interface().([]byte); ok {
197			p.printf("%#q", s)
198			return
199		}
200		p.printf("%s (len = %d) {", x.Type(), x.Len())
201		if x.Len() > 0 {
202			p.indent++
203			p.printf("\n")
204			for i, n := 0, x.Len(); i < n; i++ {
205				p.printf("%d: ", i)
206				p.print(x.Index(i))
207				p.printf("\n")
208			}
209			p.indent--
210		}
211		p.printf("}")
212
213	case reflect.Struct:
214		t := x.Type()
215		p.printf("%s {", t)
216		p.indent++
217		first := true
218		for i, n := 0, t.NumField(); i < n; i++ {
219			// exclude non-exported fields because their
220			// values cannot be accessed via reflection
221			if name := t.Field(i).Name; IsExported(name) {
222				value := x.Field(i)
223				if p.filter == nil || p.filter(name, value) {
224					if first {
225						p.printf("\n")
226						first = false
227					}
228					p.printf("%s: ", name)
229					p.print(value)
230					p.printf("\n")
231				}
232			}
233		}
234		p.indent--
235		p.printf("}")
236
237	default:
238		v := x.Interface()
239		switch v := v.(type) {
240		case string:
241			// print strings in quotes
242			p.printf("%q", v)
243			return
244		case token.Pos:
245			// position values can be printed nicely if we have a file set
246			if p.fset != nil {
247				p.printf("%s", p.fset.Position(v))
248				return
249			}
250		}
251		// default
252		p.printf("%v", v)
253	}
254}
255