From 3112bb0727d95d4f6765d808453c363c8d9f43ec Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 19 Aug 2010 09:39:35 -0700 Subject: [PATCH] go/ast, gofmt: facility for printing AST nodes go/ast: implement Fprint and print functions to print AST nodes gofmt: print AST nodes by setting -ast flag R=rsc, r CC=golang-dev https://golang.org/cl/1981044 --- src/cmd/gofmt/doc.go | 2 + src/cmd/gofmt/gofmt.go | 5 + src/pkg/go/ast/Makefile | 1 + src/pkg/go/ast/print.go | 197 ++++++++++++++++++++++++++++++++++++++ src/pkg/go/token/token.go | 2 +- 5 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 src/pkg/go/ast/print.go diff --git a/src/cmd/gofmt/doc.go b/src/cmd/gofmt/doc.go index 2e4c40c216d..6fee2278368 100644 --- a/src/cmd/gofmt/doc.go +++ b/src/cmd/gofmt/doc.go @@ -33,6 +33,8 @@ Debugging flags: -trace print parse trace. + -ast + print AST (before rewrites). -comments=true print comments; if false, all comments are elided from the output. diff --git a/src/cmd/gofmt/gofmt.go b/src/cmd/gofmt/gofmt.go index a0163b75fbe..88c9f197ce5 100644 --- a/src/cmd/gofmt/gofmt.go +++ b/src/cmd/gofmt/gofmt.go @@ -28,6 +28,7 @@ var ( // debugging support comments = flag.Bool("comments", true, "print comments") trace = flag.Bool("trace", false, "print parse trace") + printAST = flag.Bool("ast", false, "print AST (before rewrites)") // layout control tabWidth = flag.Int("tabwidth", 8, "tab width") @@ -97,6 +98,10 @@ func processFile(f *os.File) os.Error { return err } + if *printAST { + ast.Print(file) + } + if rewrite != nil { file = rewrite(file) } diff --git a/src/pkg/go/ast/Makefile b/src/pkg/go/ast/Makefile index d95210b2710..e9b885c7052 100644 --- a/src/pkg/go/ast/Makefile +++ b/src/pkg/go/ast/Makefile @@ -8,6 +8,7 @@ TARG=go/ast GOFILES=\ ast.go\ filter.go\ + print.go\ scope.go\ walk.go\ diff --git a/src/pkg/go/ast/print.go b/src/pkg/go/ast/print.go new file mode 100644 index 00000000000..b4b3ed6672c --- /dev/null +++ b/src/pkg/go/ast/print.go @@ -0,0 +1,197 @@ +// 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} + + // 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 + written int // number of bytes written to output + indent int // current indentation level + last byte // the last byte processed by Write +} + + +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 + } + } else if p.last == '\n' { + 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 any acyclic data structure. It would also be easy +// to generalize it to 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("*") + 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()) + } +} diff --git a/src/pkg/go/token/token.go b/src/pkg/go/token/token.go index 70c2501e9cb..bc6c6a865b2 100644 --- a/src/pkg/go/token/token.go +++ b/src/pkg/go/token/token.go @@ -353,7 +353,7 @@ func (pos Position) String() string { s += fmt.Sprintf("%d:%d", pos.Line, pos.Column) } if s == "" { - s = "???" + s = "-" } return s }