// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// This file contains printing suppport for ASTs.
+// This file contains printing support for ASTs.
package ast
"reflect"
)
-
// A FieldFilter may be provided to Fprint to control the output.
type FieldFilter func(name string, value reflect.Value) bool
-
// NotNilFilter returns true for field values that are not nil;
// it returns false otherwise.
-func NotNilFilter(_ string, value reflect.Value) bool {
- v, ok := value.(interface {
- IsNil() bool
- })
- return !ok || !v.IsNil()
+func NotNilFilter(_ string, v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
+ return !v.IsNil()
+ }
+ return true
}
-
// Fprint prints the (sub-)tree starting at AST node x to w.
// If fset != nil, position information is interpreted relative
// to that file set. Otherwise positions are printed as integer
//
// A non-nil FieldFilter f may be provided to control the output:
// struct fields for which f(fieldname, fieldvalue) is true are
-// are printed; all others are filtered from the output.
+// are printed; all others are filtered from the output. Unexported
+// struct fields are never printed.
//
-func Fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (n int, err os.Error) {
+func Fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (err error) {
// setup printer
p := printer{
output: w,
// install error handler
defer func() {
- n = p.written
if e := recover(); e != nil {
err = e.(localError).err // re-panics if it's not a localError
}
p.printf("nil\n")
return
}
- p.print(reflect.NewValue(x))
+ p.print(reflect.ValueOf(x))
p.printf("\n")
return
}
-
// Print prints x to standard output, skipping nil fields.
// Print(fset, x) is the same as Fprint(os.Stdout, fset, x, NotNilFilter).
-func Print(fset *token.FileSet, x interface{}) (int, os.Error) {
+func Print(fset *token.FileSet, x interface{}) error {
return Fprint(os.Stdout, fset, x, NotNilFilter)
}
-
type printer struct {
- output io.Writer
- fset *token.FileSet
- filter FieldFilter
- ptrmap map[interface{}]int // *reflect.PtrValue -> line number
- written int // number of bytes written to output
- indent int // current indentation level
- last byte // the last byte processed by Write
- line int // current line number
+ output io.Writer
+ fset *token.FileSet
+ filter FieldFilter
+ ptrmap map[interface{}]int // *T -> line number
+ indent int // current indentation level
+ last byte // the last byte processed by Write
+ line int // current line number
}
-
var indent = []byte(". ")
-func (p *printer) Write(data []byte) (n int, err os.Error) {
+func (p *printer) Write(data []byte) (n int, err error) {
var m int
for i, b := range data {
// invariant: data[0:n] has been written
return
}
-
-// localError wraps locally caught os.Errors so we can distinguish
+// localError wraps locally caught errors so we can distinguish
// them from genuine panics which we don't want to return as errors.
type localError struct {
- err os.Error
+ err error
}
-
// printf is a convenience wrapper that takes care of print errors.
func (p *printer) printf(format string, args ...interface{}) {
- n, err := fmt.Fprintf(p, format, args...)
- p.written += n
- if err != nil {
+ if _, err := fmt.Fprintf(p, format, args...); err != nil {
panic(localError{err})
}
}
-
// Implementation note: Print is written for AST nodes but could be
// used to print arbitrary data structures; such a version should
// probably be in a different package.
+//
+// Note: This code detects (some) cycles created via pointers but
+// not cycles that are created via slices or maps containing the
+// same slice or map. Code for general data structures probably
+// should catch those as well.
func (p *printer) print(x reflect.Value) {
if !NotNilFilter("", x) {
return
}
- switch v := x.(type) {
- case *reflect.InterfaceValue:
- p.print(v.Elem())
+ switch x.Kind() {
+ case reflect.Interface:
+ p.print(x.Elem())
- case *reflect.MapValue:
- p.printf("%s (len = %d) {\n", x.Type().String(), v.Len())
- p.indent++
- for _, key := range v.Keys() {
- p.print(key)
- p.printf(": ")
- p.print(v.Elem(key))
+ case reflect.Map:
+ p.printf("%s (len = %d) {", x.Type(), x.Len())
+ if x.Len() > 0 {
+ p.indent++
p.printf("\n")
+ for _, key := range x.MapKeys() {
+ p.print(key)
+ p.printf(": ")
+ p.print(x.MapIndex(key))
+ p.printf("\n")
+ }
+ p.indent--
}
- p.indent--
p.printf("}")
- case *reflect.PtrValue:
+ case reflect.Ptr:
p.printf("*")
// type-checked ASTs may contain cycles - use ptrmap
// to keep track of objects that have been printed
// already and print the respective line number instead
- ptr := v.Interface()
+ ptr := x.Interface()
if line, exists := p.ptrmap[ptr]; exists {
p.printf("(obj @ %d)", line)
} else {
p.ptrmap[ptr] = p.line
- p.print(v.Elem())
+ p.print(x.Elem())
}
- case *reflect.SliceValue:
- if s, ok := v.Interface().([]byte); ok {
+ case reflect.Array:
+ p.printf("%s {", x.Type())
+ if x.Len() > 0 {
+ p.indent++
+ p.printf("\n")
+ for i, n := 0, x.Len(); i < n; i++ {
+ p.printf("%d: ", i)
+ p.print(x.Index(i))
+ p.printf("\n")
+ }
+ p.indent--
+ }
+ p.printf("}")
+
+ case reflect.Slice:
+ if s, ok := x.Interface().([]byte); ok {
p.printf("%#q", s)
return
}
- p.printf("%s (len = %d) {\n", x.Type().String(), v.Len())
- p.indent++
- for i, n := 0, v.Len(); i < n; i++ {
- p.printf("%d: ", i)
- p.print(v.Elem(i))
+ p.printf("%s (len = %d) {", 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.print(x.Index(i))
+ p.printf("\n")
+ }
+ p.indent--
}
- p.indent--
p.printf("}")
- case *reflect.StructValue:
- p.printf("%s {\n", x.Type().String())
+ case reflect.Struct:
+ t := x.Type()
+ p.printf("%s {", t)
p.indent++
- t := v.Type().(*reflect.StructType)
+ first := true
for i, n := 0, t.NumField(); i < n; i++ {
- name := t.Field(i).Name
- value := v.Field(i)
- if p.filter == nil || p.filter(name, value) {
- p.printf("%s: ", name)
- p.print(value)
- p.printf("\n")
+ // exclude non-exported fields because their
+ // values cannot be accessed via reflection
+ if name := t.Field(i).Name; IsExported(name) {
+ value := x.Field(i)
+ if p.filter == nil || p.filter(name, value) {
+ if first {
+ p.printf("\n")
+ first = false
+ }
+ p.printf("%s: ", name)
+ p.print(value)
+ p.printf("\n")
+ }
}
}
p.indent--
p.printf("}")
default:
- value := x.Interface()
- // position values can be printed nicely if we have a file set
- if pos, ok := value.(token.Pos); ok && p.fset != nil {
- value = p.fset.Position(pos)
+ v := x.Interface()
+ switch v := v.(type) {
+ case string:
+ // print strings in quotes
+ p.printf("%q", v)
+ return
+ case token.Pos:
+ // position values can be printed nicely if we have a file set
+ if p.fset != nil {
+ p.printf("%s", p.fset.Position(v))
+ return
+ }
}
- p.printf("%v", value)
+ // default
+ p.printf("%v", v)
}
}