...
Run Format

Source file src/go/ast/print.go

Documentation: go/ast

  // 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 support 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, 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
  // values (file set specific offsets).
  //
  // A non-nil FieldFilter f may be provided to control the output:
  // struct fields for which f(fieldname, fieldvalue) is true 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) error {
  	return fprint(w, fset, x, f)
  }
  
  func fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (err error) {
  	// setup printer
  	p := printer{
  		output: w,
  		fset:   fset,
  		filter: f,
  		ptrmap: make(map[interface{}]int),
  		last:   '\n', // force printing of line number on first line
  	}
  
  	// install error handler
  	defer func() {
  		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.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{}) error {
  	return Fprint(os.Stdout, fset, x, NotNilFilter)
  }
  
  type printer struct {
  	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 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
  	}
  	if len(data) > n {
  		m, err = p.output.Write(data[n:])
  		n += m
  	}
  	return
  }
  
  // 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 error
  }
  
  // printf is a convenience wrapper that takes care of print errors.
  func (p *printer) printf(format string, args ...interface{}) {
  	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) {
  		p.printf("nil")
  		return
  	}
  
  	switch x.Kind() {
  	case reflect.Interface:
  		p.print(x.Elem())
  
  	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.printf("}")
  
  	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 := x.Interface()
  		if line, exists := p.ptrmap[ptr]; exists {
  			p.printf("(obj @ %d)", line)
  		} else {
  			p.ptrmap[ptr] = p.line
  			p.print(x.Elem())
  		}
  
  	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) {", 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.printf("}")
  
  	case reflect.Struct:
  		t := x.Type()
  		p.printf("%s {", t)
  		p.indent++
  		first := true
  		for i, n := 0, t.NumField(); i < n; i++ {
  			// 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:
  		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
  			}
  		}
  		// default
  		p.printf("%v", v)
  	}
  }
  

View as plain text