summaryrefslogtreecommitdiff
path: root/src/pkg/go/ast/print.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/go/ast/print.go')
-rw-r--r--src/pkg/go/ast/print.go217
1 files changed, 217 insertions, 0 deletions
diff --git a/src/pkg/go/ast/print.go b/src/pkg/go/ast/print.go
new file mode 100644
index 000000000..d71490d4a
--- /dev/null
+++ b/src/pkg/go/ast/print.go
@@ -0,0 +1,217 @@
+// Copyright 2010 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 contains printing suppport for ASTs.
+
+package ast
+
+import (
+ "fmt"
+ "go/token"
+ "io"
+ "os"
+ "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()
+}
+
+
+// Fprint prints the (sub-)tree starting at AST node x to w.
+//
+// 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.
+//
+func Fprint(w io.Writer, x interface{}, f FieldFilter) (n int, err os.Error) {
+ // setup printer
+ p := printer{
+ output: w,
+ filter: f,
+ ptrmap: make(map[interface{}]int),
+ last: '\n', // force printing of line number on first line
+ }
+
+ // 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
+ }
+ }()
+
+ // print x
+ if x == nil {
+ p.printf("nil\n")
+ return
+ }
+ p.print(reflect.NewValue(x))
+ p.printf("\n")
+
+ return
+}
+
+
+// Print prints x to standard output, skipping nil fields.
+// Print(x) is the same as Fprint(os.Stdout, x, NotNilFilter).
+func Print(x interface{}) (int, os.Error) {
+ return Fprint(os.Stdout, x, NotNilFilter)
+}
+
+
+type printer struct {
+ output io.Writer
+ 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
+}
+
+
+var indent = []byte(". ")
+
+func (p *printer) Write(data []byte) (n int, err os.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
+ }
+ p.line++
+ } else if p.last == '\n' {
+ _, err = fmt.Fprintf(p.output, "%6d ", p.line)
+ if err != nil {
+ return
+ }
+ for j := p.indent; j > 0; j-- {
+ _, err = p.output.Write(indent)
+ if err != nil {
+ return
+ }
+ }
+ }
+ p.last = b
+ }
+ m, err = p.output.Write(data[n:])
+ n += m
+ return
+}
+
+
+// localError wraps locally caught os.Errors so we can distinguish
+// them from genuine panics which we don't want to return as errors.
+type localError struct {
+ err os.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 {
+ 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.
+
+func (p *printer) print(x reflect.Value) {
+ // Note: This test is only needed because AST nodes
+ // embed a token.Position, and thus all of them
+ // understand the String() method (but it only
+ // applies to the Position field).
+ // TODO: Should reconsider this AST design decision.
+ if pos, ok := x.Interface().(token.Position); ok {
+ p.printf("%s", pos)
+ return
+ }
+
+ if !NotNilFilter("", x) {
+ p.printf("nil")
+ return
+ }
+
+ switch v := x.(type) {
+ case *reflect.InterfaceValue:
+ p.print(v.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))
+ }
+ p.indent--
+ p.printf("}")
+
+ case *reflect.PtrValue:
+ 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()
+ if line, exists := p.ptrmap[ptr]; exists {
+ p.printf("(obj @ %d)", line)
+ } else {
+ p.ptrmap[ptr] = p.line
+ p.print(v.Elem())
+ }
+
+ case *reflect.SliceValue:
+ if s, ok := v.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("\n")
+ }
+ p.indent--
+ p.printf("}")
+
+ case *reflect.StructValue:
+ p.printf("%s {\n", x.Type().String())
+ p.indent++
+ t := v.Type().(*reflect.StructType)
+ 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")
+ }
+ }
+ p.indent--
+ p.printf("}")
+
+ default:
+ p.printf("%v", x.Interface())
+ }
+}