1// Copyright 2016 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 implements printing of syntax tree structures.
6
7package syntax
8
9import (
10	"fmt"
11	"io"
12	"reflect"
13	"unicode"
14	"unicode/utf8"
15)
16
17// Fdump dumps the structure of the syntax tree rooted at n to w.
18// It is intended for debugging purposes; no specific output format
19// is guaranteed.
20func Fdump(w io.Writer, n Node) (err error) {
21	p := dumper{
22		output: w,
23		ptrmap: make(map[Node]int),
24		last:   '\n', // force printing of line number on first line
25	}
26
27	defer func() {
28		if e := recover(); e != nil {
29			err = e.(writeError).err // re-panics if it's not a writeError
30		}
31	}()
32
33	if n == nil {
34		p.printf("nil\n")
35		return
36	}
37	p.dump(reflect.ValueOf(n), n)
38	p.printf("\n")
39
40	return
41}
42
43type dumper struct {
44	output io.Writer
45	ptrmap map[Node]int // node -> dump line number
46	indent int          // current indentation level
47	last   byte         // last byte processed by Write
48	line   int          // current line number
49}
50
51var indentBytes = []byte(".  ")
52
53func (p *dumper) Write(data []byte) (n int, err error) {
54	var m int
55	for i, b := range data {
56		// invariant: data[0:n] has been written
57		if b == '\n' {
58			m, err = p.output.Write(data[n : i+1])
59			n += m
60			if err != nil {
61				return
62			}
63		} else if p.last == '\n' {
64			p.line++
65			_, err = fmt.Fprintf(p.output, "%6d  ", p.line)
66			if err != nil {
67				return
68			}
69			for j := p.indent; j > 0; j-- {
70				_, err = p.output.Write(indentBytes)
71				if err != nil {
72					return
73				}
74			}
75		}
76		p.last = b
77	}
78	if len(data) > n {
79		m, err = p.output.Write(data[n:])
80		n += m
81	}
82	return
83}
84
85// writeError wraps locally caught write errors so we can distinguish
86// them from genuine panics which we don't want to return as errors.
87type writeError struct {
88	err error
89}
90
91// printf is a convenience wrapper that takes care of print errors.
92func (p *dumper) printf(format string, args ...interface{}) {
93	if _, err := fmt.Fprintf(p, format, args...); err != nil {
94		panic(writeError{err})
95	}
96}
97
98// dump prints the contents of x.
99// If x is the reflect.Value of a struct s, where &s
100// implements Node, then &s should be passed for n -
101// this permits printing of the unexported span and
102// comments fields of the embedded isNode field by
103// calling the Span() and Comment() instead of using
104// reflection.
105func (p *dumper) dump(x reflect.Value, n Node) {
106	switch x.Kind() {
107	case reflect.Interface:
108		if x.IsNil() {
109			p.printf("nil")
110			return
111		}
112		p.dump(x.Elem(), nil)
113
114	case reflect.Ptr:
115		if x.IsNil() {
116			p.printf("nil")
117			return
118		}
119
120		// special cases for identifiers w/o attached comments (common case)
121		if x, ok := x.Interface().(*Name); ok {
122			p.printf("%s @ %v", x.Value, x.Pos())
123			return
124		}
125
126		p.printf("*")
127		// Fields may share type expressions, and declarations
128		// may share the same group - use ptrmap to keep track
129		// of nodes that have been printed already.
130		if ptr, ok := x.Interface().(Node); ok {
131			if line, exists := p.ptrmap[ptr]; exists {
132				p.printf("(Node @ %d)", line)
133				return
134			}
135			p.ptrmap[ptr] = p.line
136			n = ptr
137		}
138		p.dump(x.Elem(), n)
139
140	case reflect.Slice:
141		if x.IsNil() {
142			p.printf("nil")
143			return
144		}
145		p.printf("%s (%d entries) {", x.Type(), x.Len())
146		if x.Len() > 0 {
147			p.indent++
148			p.printf("\n")
149			for i, n := 0, x.Len(); i < n; i++ {
150				p.printf("%d: ", i)
151				p.dump(x.Index(i), nil)
152				p.printf("\n")
153			}
154			p.indent--
155		}
156		p.printf("}")
157
158	case reflect.Struct:
159		typ := x.Type()
160
161		// if span, ok := x.Interface().(lexical.Span); ok {
162		// 	p.printf("%s", &span)
163		// 	return
164		// }
165
166		p.printf("%s {", typ)
167		p.indent++
168
169		first := true
170		if n != nil {
171			p.printf("\n")
172			first = false
173			// p.printf("Span: %s\n", n.Span())
174			// if c := *n.Comments(); c != nil {
175			// 	p.printf("Comments: ")
176			// 	p.dump(reflect.ValueOf(c), nil) // a Comment is not a Node
177			// 	p.printf("\n")
178			// }
179		}
180
181		for i, n := 0, typ.NumField(); i < n; i++ {
182			// Exclude non-exported fields because their
183			// values cannot be accessed via reflection.
184			if name := typ.Field(i).Name; isExported(name) {
185				if first {
186					p.printf("\n")
187					first = false
188				}
189				p.printf("%s: ", name)
190				p.dump(x.Field(i), nil)
191				p.printf("\n")
192			}
193		}
194
195		p.indent--
196		p.printf("}")
197
198	default:
199		switch x := x.Interface().(type) {
200		case string:
201			// print strings in quotes
202			p.printf("%q", x)
203		default:
204			p.printf("%v", x)
205		}
206	}
207}
208
209func isExported(name string) bool {
210	ch, _ := utf8.DecodeRuneInString(name)
211	return unicode.IsUpper(ch)
212}
213