Source file src/go/ast/print.go

     1  // Copyright 2010 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // This file contains printing support for ASTs.
     6  
     7  package ast
     8  
     9  import (
    10  	"fmt"
    11  	"go/token"
    12  	"io"
    13  	"os"
    14  	"reflect"
    15  )
    16  
    17  // A FieldFilter may be provided to Fprint to control the output.
    18  type FieldFilter func(name string, value reflect.Value) bool
    19  
    20  // NotNilFilter returns true for field values that are not nil;
    21  // it returns false otherwise.
    22  func NotNilFilter(_ string, v reflect.Value) bool {
    23  	switch v.Kind() {
    24  	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
    25  		return !v.IsNil()
    26  	}
    27  	return true
    28  }
    29  
    30  // Fprint prints the (sub-)tree starting at AST node x to w.
    31  // If fset != nil, position information is interpreted relative
    32  // to that file set. Otherwise positions are printed as integer
    33  // values (file set specific offsets).
    34  //
    35  // A non-nil FieldFilter f may be provided to control the output:
    36  // struct fields for which f(fieldname, fieldvalue) is true are
    37  // printed; all others are filtered from the output. Unexported
    38  // struct fields are never printed.
    39  func Fprint(w io.Writer, fset *token.FileSet, x any, f FieldFilter) error {
    40  	return fprint(w, fset, x, f)
    41  }
    42  
    43  func fprint(w io.Writer, fset *token.FileSet, x any, f FieldFilter) (err error) {
    44  	// setup printer
    45  	p := printer{
    46  		output: w,
    47  		fset:   fset,
    48  		filter: f,
    49  		ptrmap: make(map[any]int),
    50  		last:   '\n', // force printing of line number on first line
    51  	}
    52  
    53  	// install error handler
    54  	defer func() {
    55  		if e := recover(); e != nil {
    56  			err = e.(localError).err // re-panics if it's not a localError
    57  		}
    58  	}()
    59  
    60  	// print x
    61  	if x == nil {
    62  		p.printf("nil\n")
    63  		return
    64  	}
    65  	p.print(reflect.ValueOf(x))
    66  	p.printf("\n")
    67  
    68  	return
    69  }
    70  
    71  // Print prints x to standard output, skipping nil fields.
    72  // Print(fset, x) is the same as Fprint(os.Stdout, fset, x, NotNilFilter).
    73  func Print(fset *token.FileSet, x any) error {
    74  	return Fprint(os.Stdout, fset, x, NotNilFilter)
    75  }
    76  
    77  type printer struct {
    78  	output io.Writer
    79  	fset   *token.FileSet
    80  	filter FieldFilter
    81  	ptrmap map[any]int // *T -> line number
    82  	indent int         // current indentation level
    83  	last   byte        // the last byte processed by Write
    84  	line   int         // current line number
    85  }
    86  
    87  var indent = []byte(".  ")
    88  
    89  func (p *printer) Write(data []byte) (n int, err error) {
    90  	var m int
    91  	for i, b := range data {
    92  		// invariant: data[0:n] has been written
    93  		if b == '\n' {
    94  			m, err = p.output.Write(data[n : i+1])
    95  			n += m
    96  			if err != nil {
    97  				return
    98  			}
    99  			p.line++
   100  		} else if p.last == '\n' {
   101  			_, err = fmt.Fprintf(p.output, "%6d  ", p.line)
   102  			if err != nil {
   103  				return
   104  			}
   105  			for j := p.indent; j > 0; j-- {
   106  				_, err = p.output.Write(indent)
   107  				if err != nil {
   108  					return
   109  				}
   110  			}
   111  		}
   112  		p.last = b
   113  	}
   114  	if len(data) > n {
   115  		m, err = p.output.Write(data[n:])
   116  		n += m
   117  	}
   118  	return
   119  }
   120  
   121  // localError wraps locally caught errors so we can distinguish
   122  // them from genuine panics which we don't want to return as errors.
   123  type localError struct {
   124  	err error
   125  }
   126  
   127  // printf is a convenience wrapper that takes care of print errors.
   128  func (p *printer) printf(format string, args ...any) {
   129  	if _, err := fmt.Fprintf(p, format, args...); err != nil {
   130  		panic(localError{err})
   131  	}
   132  }
   133  
   134  // Implementation note: Print is written for AST nodes but could be
   135  // used to print arbitrary data structures; such a version should
   136  // probably be in a different package.
   137  //
   138  // Note: This code detects (some) cycles created via pointers but
   139  // not cycles that are created via slices or maps containing the
   140  // same slice or map. Code for general data structures probably
   141  // should catch those as well.
   142  
   143  func (p *printer) print(x reflect.Value) {
   144  	if !NotNilFilter("", x) {
   145  		p.printf("nil")
   146  		return
   147  	}
   148  
   149  	switch x.Kind() {
   150  	case reflect.Interface:
   151  		p.print(x.Elem())
   152  
   153  	case reflect.Map:
   154  		p.printf("%s (len = %d) {", x.Type(), x.Len())
   155  		if x.Len() > 0 {
   156  			p.indent++
   157  			p.printf("\n")
   158  			for _, key := range x.MapKeys() {
   159  				p.print(key)
   160  				p.printf(": ")
   161  				p.print(x.MapIndex(key))
   162  				p.printf("\n")
   163  			}
   164  			p.indent--
   165  		}
   166  		p.printf("}")
   167  
   168  	case reflect.Pointer:
   169  		p.printf("*")
   170  		// type-checked ASTs may contain cycles - use ptrmap
   171  		// to keep track of objects that have been printed
   172  		// already and print the respective line number instead
   173  		ptr := x.Interface()
   174  		if line, exists := p.ptrmap[ptr]; exists {
   175  			p.printf("(obj @ %d)", line)
   176  		} else {
   177  			p.ptrmap[ptr] = p.line
   178  			p.print(x.Elem())
   179  		}
   180  
   181  	case reflect.Array:
   182  		p.printf("%s {", x.Type())
   183  		if x.Len() > 0 {
   184  			p.indent++
   185  			p.printf("\n")
   186  			for i, n := 0, x.Len(); i < n; i++ {
   187  				p.printf("%d: ", i)
   188  				p.print(x.Index(i))
   189  				p.printf("\n")
   190  			}
   191  			p.indent--
   192  		}
   193  		p.printf("}")
   194  
   195  	case reflect.Slice:
   196  		if s, ok := x.Interface().([]byte); ok {
   197  			p.printf("%#q", s)
   198  			return
   199  		}
   200  		p.printf("%s (len = %d) {", x.Type(), x.Len())
   201  		if x.Len() > 0 {
   202  			p.indent++
   203  			p.printf("\n")
   204  			for i, n := 0, x.Len(); i < n; i++ {
   205  				p.printf("%d: ", i)
   206  				p.print(x.Index(i))
   207  				p.printf("\n")
   208  			}
   209  			p.indent--
   210  		}
   211  		p.printf("}")
   212  
   213  	case reflect.Struct:
   214  		t := x.Type()
   215  		p.printf("%s {", t)
   216  		p.indent++
   217  		first := true
   218  		for i, n := 0, t.NumField(); i < n; i++ {
   219  			// exclude non-exported fields because their
   220  			// values cannot be accessed via reflection
   221  			if name := t.Field(i).Name; IsExported(name) {
   222  				value := x.Field(i)
   223  				if p.filter == nil || p.filter(name, value) {
   224  					if first {
   225  						p.printf("\n")
   226  						first = false
   227  					}
   228  					p.printf("%s: ", name)
   229  					p.print(value)
   230  					p.printf("\n")
   231  				}
   232  			}
   233  		}
   234  		p.indent--
   235  		p.printf("}")
   236  
   237  	default:
   238  		v := x.Interface()
   239  		switch v := v.(type) {
   240  		case string:
   241  			// print strings in quotes
   242  			p.printf("%q", v)
   243  			return
   244  		case token.Pos:
   245  			// position values can be printed nicely if we have a file set
   246  			if p.fset != nil {
   247  				p.printf("%s", p.fset.Position(v))
   248  				return
   249  			}
   250  		}
   251  		// default
   252  		p.printf("%v", v)
   253  	}
   254  }
   255  

View as plain text