package ast
import (
"fmt"
"io"
"reflect"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
func formatBytes(b []byte) string {
if utf8.Valid(b) {
for r, i, size := rune(0), 0, 0; i < len(b); i += size {
r, size = utf8.DecodeRune(b[i:])
if !unicode.IsPrint(r) {
return strconv.Quote(string(b))
}
}
return string(b)
}
return fmt.Sprintf("%v", b)
}
func formatString(s string) string {
if utf8.ValidString(s) {
for _, r := range s {
if !unicode.IsPrint(r) {
return strconv.Quote(s)
}
}
return s
}
return fmt.Sprintf("%v", []byte(s))
}
func printAST(w io.Writer, n interface{}, s []byte, prefix string,
detail bool, depth int) (int, error) {
indent := strings.Repeat(prefix, depth)
indentLong := strings.Repeat(prefix, depth+1)
if n == nil {
return fmt.Fprintf(w, "%snil\n", indent)
}
typeOf := reflect.TypeOf(n)
valueOf := reflect.ValueOf(n)
kind := typeOf.Kind()
if kind == reflect.Ptr {
if valueOf.IsNil() {
return fmt.Fprintf(w, "%snil\n", indent)
}
valueOf = valueOf.Elem()
typeOf = typeOf.Elem()
kind = typeOf.Kind()
}
name := typeOf.Name()
if stringer, ok := n.(fmt.Stringer); ok {
s := stringer.String()
return fmt.Fprintf(w, "%s%s\n", indent, formatString(s))
}
if s, ok := n.(string); ok {
return fmt.Fprintf(w, "%s%s\n", indent, formatString(s))
}
if bs, ok := n.([]byte); ok {
return fmt.Fprintf(w, "%s%s\n", indent, formatBytes(bs))
}
if kind == reflect.Slice {
l := valueOf.Len()
if l == 0 {
return fmt.Fprintf(w, "%s[]\n", indent)
}
var bytesWritten int
b, err := fmt.Fprintf(w, "%s[\n", indent)
bytesWritten += b
if err != nil {
return bytesWritten, err
}
for i := 0; i < l; i++ {
v := valueOf.Index(i)
b, err = printAST(w, v.Interface(), s, prefix, detail, depth+1)
bytesWritten += b
if err != nil {
return bytesWritten, err
}
}
b, err = fmt.Fprintf(w, "%s]\n", indent)
bytesWritten += b
return bytesWritten, err
}
if kind == reflect.Struct {
type field struct {
name string
value interface{}
}
var fields []field
var collect func(reflect.Type, reflect.Value)
collect = func(typeOf reflect.Type, valueOf reflect.Value) {
l := typeOf.NumField()
for i := 0; i < l; i++ {
if !detail && typeOf.Field(i).Tag.Get("print") == "-" {
continue
}
if typeOf.Field(i).Anonymous {
embeddedInterface := valueOf.Field(i).Interface()
embeddedTypeOf := reflect.TypeOf(embeddedInterface)
embeddedValueOf := reflect.ValueOf(embeddedInterface)
collect(embeddedTypeOf, embeddedValueOf)
continue
}
fields = append(fields, field{
name: typeOf.Field(i).Name,
value: valueOf.Field(i).Interface(),
})
}
}
collect(typeOf, valueOf)
var position string
if node, ok := n.(Node); ok {
begin := node.GetPosition()
length := node.GetLength()
if node.HasPosition() {
end := begin + length - 1
token := s[begin : begin+length]
position = fmt.Sprintf("%d-%d %s",
begin, end, strconv.Quote(string(token)))
} else {
position = "no position info"
}
}
var bytesWritten int
b, err := fmt.Fprintf(w, "%s%s", indent, name)
bytesWritten += b
if err != nil {
return bytesWritten, err
}
if len(fields) == 0 {
b, err = fmt.Fprintf(w, " {} // %s\n", position)
bytesWritten += b
return bytesWritten, err
}
b, err = fmt.Fprintf(w, " { // %s\n", position)
bytesWritten += b
if err != nil {
return bytesWritten, err
}
for i := 0; i < len(fields); i++ {
b, err = fmt.Fprintf(w, "%s%s:\n", indentLong, fields[i].name)
bytesWritten += b
if err != nil {
return bytesWritten, err
}
b, err = printAST(w, fields[i].value, s, prefix, detail, depth+2)
bytesWritten += b
if err != nil {
return bytesWritten, err
}
}
b, err = fmt.Fprintf(w, "%s}\n", indent)
bytesWritten += b
return bytesWritten, err
}
return fmt.Fprintf(w, "%s%+v\n", indent, valueOf.Interface())
}
// PrintAST prints AST for debugging.
func PrintAST(output io.Writer, node interface{}, source []byte,
indent string, detail bool) (int, error) {
return printAST(output, node, source, indent, detail, 0)
}