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