mirror of https://github.com/golang/go.git
213 lines
4.5 KiB
Go
213 lines
4.5 KiB
Go
// Copyright 2016 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// This file implements printing of syntax tree structures.
|
|
|
|
package syntax
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Fdump dumps the structure of the syntax tree rooted at n to w.
|
|
// It is intended for debugging purposes; no specific output format
|
|
// is guaranteed.
|
|
func Fdump(w io.Writer, n Node) (err error) {
|
|
p := dumper{
|
|
output: w,
|
|
ptrmap: make(map[Node]int),
|
|
last: '\n', // force printing of line number on first line
|
|
}
|
|
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
err = e.(writeError).err // re-panics if it's not a writeError
|
|
}
|
|
}()
|
|
|
|
if n == nil {
|
|
p.printf("nil\n")
|
|
return
|
|
}
|
|
p.dump(reflect.ValueOf(n), n)
|
|
p.printf("\n")
|
|
|
|
return
|
|
}
|
|
|
|
type dumper struct {
|
|
output io.Writer
|
|
ptrmap map[Node]int // node -> dump line number
|
|
indent int // current indentation level
|
|
last byte // last byte processed by Write
|
|
line int // current line number
|
|
}
|
|
|
|
var indentBytes = []byte(". ")
|
|
|
|
func (p *dumper) Write(data []byte) (n int, err error) {
|
|
var m int
|
|
for i, b := range data {
|
|
// invariant: data[0:n] has been written
|
|
if b == '\n' {
|
|
m, err = p.output.Write(data[n : i+1])
|
|
n += m
|
|
if err != nil {
|
|
return
|
|
}
|
|
} else if p.last == '\n' {
|
|
p.line++
|
|
_, err = fmt.Fprintf(p.output, "%6d ", p.line)
|
|
if err != nil {
|
|
return
|
|
}
|
|
for j := p.indent; j > 0; j-- {
|
|
_, err = p.output.Write(indentBytes)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
p.last = b
|
|
}
|
|
if len(data) > n {
|
|
m, err = p.output.Write(data[n:])
|
|
n += m
|
|
}
|
|
return
|
|
}
|
|
|
|
// writeError wraps locally caught write errors so we can distinguish
|
|
// them from genuine panics which we don't want to return as errors.
|
|
type writeError struct {
|
|
err error
|
|
}
|
|
|
|
// printf is a convenience wrapper that takes care of print errors.
|
|
func (p *dumper) printf(format string, args ...interface{}) {
|
|
if _, err := fmt.Fprintf(p, format, args...); err != nil {
|
|
panic(writeError{err})
|
|
}
|
|
}
|
|
|
|
// dump prints the contents of x.
|
|
// If x is the reflect.Value of a struct s, where &s
|
|
// implements Node, then &s should be passed for n -
|
|
// this permits printing of the unexported span and
|
|
// comments fields of the embedded isNode field by
|
|
// calling the Span() and Comment() instead of using
|
|
// reflection.
|
|
func (p *dumper) dump(x reflect.Value, n Node) {
|
|
switch x.Kind() {
|
|
case reflect.Interface:
|
|
if x.IsNil() {
|
|
p.printf("nil")
|
|
return
|
|
}
|
|
p.dump(x.Elem(), nil)
|
|
|
|
case reflect.Ptr:
|
|
if x.IsNil() {
|
|
p.printf("nil")
|
|
return
|
|
}
|
|
|
|
// special cases for identifiers w/o attached comments (common case)
|
|
if x, ok := x.Interface().(*Name); ok {
|
|
p.printf("%s @ %v", x.Value, x.Pos())
|
|
return
|
|
}
|
|
|
|
p.printf("*")
|
|
// Fields may share type expressions, and declarations
|
|
// may share the same group - use ptrmap to keep track
|
|
// of nodes that have been printed already.
|
|
if ptr, ok := x.Interface().(Node); ok {
|
|
if line, exists := p.ptrmap[ptr]; exists {
|
|
p.printf("(Node @ %d)", line)
|
|
return
|
|
}
|
|
p.ptrmap[ptr] = p.line
|
|
n = ptr
|
|
}
|
|
p.dump(x.Elem(), n)
|
|
|
|
case reflect.Slice:
|
|
if x.IsNil() {
|
|
p.printf("nil")
|
|
return
|
|
}
|
|
p.printf("%s (%d entries) {", x.Type(), x.Len())
|
|
if x.Len() > 0 {
|
|
p.indent++
|
|
p.printf("\n")
|
|
for i, n := 0, x.Len(); i < n; i++ {
|
|
p.printf("%d: ", i)
|
|
p.dump(x.Index(i), nil)
|
|
p.printf("\n")
|
|
}
|
|
p.indent--
|
|
}
|
|
p.printf("}")
|
|
|
|
case reflect.Struct:
|
|
typ := x.Type()
|
|
|
|
// if span, ok := x.Interface().(lexical.Span); ok {
|
|
// p.printf("%s", &span)
|
|
// return
|
|
// }
|
|
|
|
p.printf("%s {", typ)
|
|
p.indent++
|
|
|
|
first := true
|
|
if n != nil {
|
|
p.printf("\n")
|
|
first = false
|
|
// p.printf("Span: %s\n", n.Span())
|
|
// if c := *n.Comments(); c != nil {
|
|
// p.printf("Comments: ")
|
|
// p.dump(reflect.ValueOf(c), nil) // a Comment is not a Node
|
|
// p.printf("\n")
|
|
// }
|
|
}
|
|
|
|
for i, n := 0, typ.NumField(); i < n; i++ {
|
|
// Exclude non-exported fields because their
|
|
// values cannot be accessed via reflection.
|
|
if name := typ.Field(i).Name; isExported(name) {
|
|
if first {
|
|
p.printf("\n")
|
|
first = false
|
|
}
|
|
p.printf("%s: ", name)
|
|
p.dump(x.Field(i), nil)
|
|
p.printf("\n")
|
|
}
|
|
}
|
|
|
|
p.indent--
|
|
p.printf("}")
|
|
|
|
default:
|
|
switch x := x.Interface().(type) {
|
|
case string:
|
|
// print strings in quotes
|
|
p.printf("%q", x)
|
|
default:
|
|
p.printf("%v", x)
|
|
}
|
|
}
|
|
}
|
|
|
|
func isExported(name string) bool {
|
|
ch, _ := utf8.DecodeRuneInString(name)
|
|
return unicode.IsUpper(ch)
|
|
}
|