package ast import ( "fmt" "go/token" "io" "os" "reflect" apitoken "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token" ) // 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.Pointer, reflect.Slice: return !v.IsNil() } return true } // Fprint prints the value of x to the writer w. func Fprint(w io.Writer, x interface{}, f FieldFilter) error { return fprint(w, x, f) } func fprint(w io.Writer, x interface{}, f FieldFilter) (err 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() { 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 } func Print(x interface{}) error { return Fprint(os.Stdout, x, NotNilFilter) } type printer struct { output io.Writer filter FieldFilter ptrmap map[interface{}]int // *T -> line number prefixIndent int // current indentation level last byte // the last byte processed by Write line int // current line number } var prefixIndent = []byte(". ") // Write implements io.Writer. 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.prefixIndent; j > 0; j-- { _, err = p.output.Write(prefixIndent) 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.prefixIndent++ p.printf("\n") for _, key := range x.MapKeys() { p.print(key) p.printf(": ") p.print(x.MapIndex(key)) p.printf("\n") } p.prefixIndent-- } p.printf("}") case reflect.Pointer: 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.prefixIndent++ 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.prefixIndent-- } 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.prefixIndent++ 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.prefixIndent-- } p.printf("}") case reflect.Struct: if val, ok := x.Interface().(apitoken.Position); ok { p.printf(val.String()) return } t := x.Type() p.printf("%s {", t) p.prefixIndent++ 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; token.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.prefixIndent-- p.printf("}") default: v := x.Interface() switch v := v.(type) { case string: // print strings in quotes p.printf("%q", v) return } // default p.printf("%v", v) } }