cuelang.org/go@v0.13.0/internal/astinternal/debug.go (about)

     1  // Copyright 2021 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package astinternal
    16  
    17  import (
    18  	"fmt"
    19  	gotoken "go/token"
    20  	"reflect"
    21  	"strconv"
    22  	"strings"
    23  
    24  	"cuelang.org/go/cue/ast"
    25  	"cuelang.org/go/cue/token"
    26  	"cuelang.org/go/internal"
    27  )
    28  
    29  // AppendDebug writes a multi-line Go-like representation of a syntax tree node,
    30  // including node position information and any relevant Go types.
    31  func AppendDebug(dst []byte, node ast.Node, config DebugConfig) []byte {
    32  	d := &debugPrinter{
    33  		cfg: config,
    34  		buf: dst,
    35  	}
    36  	if config.IncludeNodeRefs {
    37  		d.nodeRefs = make(map[ast.Node]int)
    38  		d.addNodeRefs(reflect.ValueOf(node))
    39  	}
    40  	if d.value(reflect.ValueOf(node), nil) {
    41  		d.newline()
    42  	}
    43  	return d.buf
    44  }
    45  
    46  // DebugConfig configures the behavior of [AppendDebug].
    47  type DebugConfig struct {
    48  	// Filter is called before each value in a syntax tree.
    49  	// Values for which the function returns false are omitted.
    50  	Filter func(reflect.Value) bool
    51  
    52  	// OmitEmpty causes empty strings, empty structs, empty lists,
    53  	// nil pointers, invalid positions, and missing tokens to be omitted.
    54  	OmitEmpty bool
    55  
    56  	// IncludeNodeRefs causes a Node reference in an identifier
    57  	// to indicate which (if any) ast.Node it refers to.
    58  	IncludeNodeRefs bool
    59  
    60  	// IncludePointers causes all nodes to be printed with their pointer
    61  	// values; setting this also implies [DebugConfig.IncludeNodeRefs]
    62  	// and references will be printed as pointers.
    63  	IncludePointers bool
    64  }
    65  
    66  type debugPrinter struct {
    67  	buf      []byte
    68  	cfg      DebugConfig
    69  	level    int
    70  	nodeRefs map[ast.Node]int
    71  	refID    int
    72  }
    73  
    74  // value produces the given value, omitting type information if
    75  // its type is the same as implied type. It reports whether
    76  // anything was produced.
    77  func (d *debugPrinter) value(v reflect.Value, impliedType reflect.Type) bool {
    78  	start := d.pos()
    79  	d.value0(v, impliedType)
    80  	return d.pos() > start
    81  }
    82  
    83  func (d *debugPrinter) value0(v reflect.Value, impliedType reflect.Type) {
    84  	if d.cfg.Filter != nil && !d.cfg.Filter(v) {
    85  		return
    86  	}
    87  	// Skip over interfaces and pointers, stopping early if nil.
    88  	concreteType := v.Type()
    89  	refName := ""
    90  	ptrVal := uintptr(0)
    91  	for {
    92  		k := v.Kind()
    93  		if k != reflect.Interface && k != reflect.Pointer {
    94  			break
    95  		}
    96  		if v.IsNil() {
    97  			if !d.cfg.OmitEmpty {
    98  				d.printf("nil")
    99  			}
   100  			return
   101  		}
   102  		if k == reflect.Pointer {
   103  			if n, ok := v.Interface().(ast.Node); ok {
   104  				ptrVal = v.Pointer()
   105  				if id, ok := d.nodeRefs[n]; ok {
   106  					refName = refIDToName(id)
   107  				}
   108  			}
   109  		}
   110  		v = v.Elem()
   111  		if k == reflect.Interface {
   112  			// For example, *ast.Ident can be the concrete type behind an ast.Expr.
   113  			concreteType = v.Type()
   114  		}
   115  	}
   116  
   117  	if d.cfg.OmitEmpty && v.IsZero() {
   118  		return
   119  	}
   120  
   121  	t := v.Type()
   122  	switch v := v.Interface().(type) {
   123  	// Simple types which can stringify themselves.
   124  	case token.Pos:
   125  		d.printf("%s(%q", t, v)
   126  		// Show relative positions too, if there are any, as they affect formatting.
   127  		if v.HasRelPos() {
   128  			d.printf(", %v", v.RelPos())
   129  		}
   130  		d.printf(")")
   131  		return
   132  	case token.Token:
   133  		d.printf("%s(%q)", t, v)
   134  		return
   135  	}
   136  
   137  	switch t.Kind() {
   138  	default:
   139  		// We assume all other kinds are basic in practice, like string or bool.
   140  		if t.PkgPath() != "" {
   141  			// Mention defined and non-predeclared types, for clarity.
   142  			d.printf("%s(%#v)", t, v)
   143  		} else {
   144  			d.printf("%#v", v)
   145  		}
   146  
   147  	case reflect.Slice, reflect.Struct:
   148  		valueStart := d.pos()
   149  		// We print the concrete type when it differs from an implied type.
   150  		if concreteType != impliedType {
   151  			d.printf("%s", concreteType)
   152  		}
   153  		if d.cfg.IncludePointers {
   154  			if ptrVal != 0 {
   155  				d.printf("@%#x", ptrVal)
   156  			}
   157  		} else if refName != "" {
   158  			d.printf("@%s", refName)
   159  		}
   160  		d.printf("{")
   161  		d.level++
   162  		var anyElems bool
   163  		if t.Kind() == reflect.Slice {
   164  			anyElems = d.sliceElems(v, t.Elem())
   165  		} else {
   166  			anyElems = d.structFields(v)
   167  		}
   168  		d.level--
   169  		if !anyElems && d.cfg.OmitEmpty {
   170  			d.truncate(valueStart)
   171  		} else {
   172  			if anyElems {
   173  				d.newline()
   174  			}
   175  			d.printf("}")
   176  		}
   177  	}
   178  }
   179  
   180  func (d *debugPrinter) sliceElems(v reflect.Value, elemType reflect.Type) (anyElems bool) {
   181  	for i := 0; i < v.Len(); i++ {
   182  		ev := v.Index(i)
   183  		elemStart := d.pos()
   184  		d.newline()
   185  		// Note: a slice literal implies the type of its elements
   186  		// so we can avoid mentioning the type
   187  		// of each element if it matches.
   188  		if d.value(ev, elemType) {
   189  			anyElems = true
   190  		} else {
   191  			d.truncate(elemStart)
   192  		}
   193  	}
   194  	return anyElems
   195  }
   196  
   197  func (d *debugPrinter) structFields(v reflect.Value) (anyElems bool) {
   198  	t := v.Type()
   199  	for i := 0; i < v.NumField(); i++ {
   200  		f := t.Field(i)
   201  		if !gotoken.IsExported(f.Name) {
   202  			continue
   203  		}
   204  		if f.Name == "Node" {
   205  			nodeVal := v.Field(i)
   206  			if (!d.cfg.IncludeNodeRefs && !d.cfg.IncludePointers) || nodeVal.IsNil() {
   207  				continue
   208  			}
   209  			d.newline()
   210  			if d.cfg.IncludePointers {
   211  				if nodeVal.Kind() == reflect.Interface {
   212  					nodeVal = nodeVal.Elem()
   213  				}
   214  				d.printf("Node: @%#v (%v)", nodeVal.Pointer(), nodeVal.Elem().Type())
   215  			} else {
   216  				d.printf("Node: @%s (%v)", refIDToName(d.nodeRefs[nodeVal.Interface().(ast.Node)]), nodeVal.Elem().Type())
   217  			}
   218  			continue
   219  		}
   220  		switch f.Name {
   221  		// These fields are cyclic, and they don't represent the syntax anyway.
   222  		case "Scope", "Unresolved":
   223  			continue
   224  		}
   225  		elemStart := d.pos()
   226  		d.newline()
   227  		d.printf("%s: ", f.Name)
   228  		if d.value(v.Field(i), nil) {
   229  			anyElems = true
   230  		} else {
   231  			d.truncate(elemStart)
   232  		}
   233  	}
   234  	val := v.Addr().Interface()
   235  	if val, ok := val.(ast.Node); ok {
   236  		// Comments attached to a node aren't a regular field, but are still useful.
   237  		// The majority of nodes won't have comments, so skip them when empty.
   238  		if comments := ast.Comments(val); len(comments) > 0 {
   239  			anyElems = true
   240  			d.newline()
   241  			d.printf("Comments: ")
   242  			d.value(reflect.ValueOf(comments), nil)
   243  		}
   244  	}
   245  	return anyElems
   246  }
   247  
   248  func (d *debugPrinter) printf(format string, args ...any) {
   249  	d.buf = fmt.Appendf(d.buf, format, args...)
   250  }
   251  
   252  func (d *debugPrinter) newline() {
   253  	d.buf = fmt.Appendf(d.buf, "\n%s", strings.Repeat("\t", d.level))
   254  }
   255  
   256  func (d *debugPrinter) pos() int {
   257  	return len(d.buf)
   258  }
   259  
   260  func (d *debugPrinter) truncate(pos int) {
   261  	d.buf = d.buf[:pos]
   262  }
   263  
   264  // addNodeRefs does a first pass over the value looking for
   265  // [ast.Ident] nodes that refer to other nodes.
   266  // This means when we find such a node, we can include
   267  // an anchor name for it
   268  func (d *debugPrinter) addNodeRefs(v reflect.Value) {
   269  	// Skip over interfaces and pointers, stopping early if nil.
   270  	for ; v.Kind() == reflect.Interface || v.Kind() == reflect.Pointer; v = v.Elem() {
   271  		if v.IsNil() {
   272  			return
   273  		}
   274  	}
   275  
   276  	t := v.Type()
   277  	switch v := v.Interface().(type) {
   278  	case token.Pos, token.Token:
   279  		// Simple types which can't contain an ast.Node.
   280  		return
   281  	case ast.Ident:
   282  		if v.Node != nil {
   283  			if _, ok := d.nodeRefs[v.Node]; !ok {
   284  				d.refID++
   285  				d.nodeRefs[v.Node] = d.refID
   286  			}
   287  		}
   288  		return
   289  	}
   290  
   291  	switch t.Kind() {
   292  	case reflect.Slice:
   293  		for i := 0; i < v.Len(); i++ {
   294  			d.addNodeRefs(v.Index(i))
   295  		}
   296  	case reflect.Struct:
   297  		t := v.Type()
   298  		for i := 0; i < v.NumField(); i++ {
   299  			f := t.Field(i)
   300  			if !gotoken.IsExported(f.Name) {
   301  				continue
   302  			}
   303  			switch f.Name {
   304  			// These fields don't point to any nodes that Node can refer to.
   305  			case "Scope", "Node", "Unresolved":
   306  				continue
   307  			}
   308  			d.addNodeRefs(v.Field(i))
   309  		}
   310  	}
   311  }
   312  
   313  func refIDToName(id int) string {
   314  	if id == 0 {
   315  		return "unknown"
   316  	}
   317  	return fmt.Sprintf("ref%03d", id)
   318  }
   319  
   320  func DebugStr(x interface{}) (out string) {
   321  	if n, ok := x.(ast.Node); ok {
   322  		comments := ""
   323  		for _, g := range ast.Comments(n) {
   324  			comments += DebugStr(g)
   325  		}
   326  		if comments != "" {
   327  			defer func() { out = "<" + comments + out + ">" }()
   328  		}
   329  	}
   330  	switch v := x.(type) {
   331  	case *ast.File:
   332  		out := ""
   333  		out += DebugStr(v.Decls)
   334  		return out
   335  
   336  	case *ast.Package:
   337  		out := "package "
   338  		out += DebugStr(v.Name)
   339  		return out
   340  
   341  	case *ast.LetClause:
   342  		out := "let "
   343  		out += DebugStr(v.Ident)
   344  		out += "="
   345  		out += DebugStr(v.Expr)
   346  		return out
   347  
   348  	case *ast.Alias:
   349  		out := DebugStr(v.Ident)
   350  		out += "="
   351  		out += DebugStr(v.Expr)
   352  		return out
   353  
   354  	case *ast.BottomLit:
   355  		return "_|_"
   356  
   357  	case *ast.BasicLit:
   358  		return v.Value
   359  
   360  	case *ast.Interpolation:
   361  		for _, e := range v.Elts {
   362  			out += DebugStr(e)
   363  		}
   364  		return out
   365  
   366  	case *ast.EmbedDecl:
   367  		out += DebugStr(v.Expr)
   368  		return out
   369  
   370  	case *ast.ImportDecl:
   371  		out := "import "
   372  		if v.Lparen != token.NoPos {
   373  			out += "( "
   374  			out += DebugStr(v.Specs)
   375  			out += " )"
   376  		} else {
   377  			out += DebugStr(v.Specs)
   378  		}
   379  		return out
   380  
   381  	case *ast.Comprehension:
   382  		out := DebugStr(v.Clauses)
   383  		out += DebugStr(v.Value)
   384  		return out
   385  
   386  	case *ast.StructLit:
   387  		out := "{"
   388  		out += DebugStr(v.Elts)
   389  		out += "}"
   390  		return out
   391  
   392  	case *ast.ListLit:
   393  		out := "["
   394  		out += DebugStr(v.Elts)
   395  		out += "]"
   396  		return out
   397  
   398  	case *ast.Ellipsis:
   399  		out := "..."
   400  		if v.Type != nil {
   401  			out += DebugStr(v.Type)
   402  		}
   403  		return out
   404  
   405  	case *ast.ForClause:
   406  		out := "for "
   407  		if v.Key != nil {
   408  			out += DebugStr(v.Key)
   409  			out += ": "
   410  		}
   411  		out += DebugStr(v.Value)
   412  		out += " in "
   413  		out += DebugStr(v.Source)
   414  		return out
   415  
   416  	case *ast.IfClause:
   417  		out := "if "
   418  		out += DebugStr(v.Condition)
   419  		return out
   420  
   421  	case *ast.Field:
   422  		out := DebugStr(v.Label)
   423  		if t, ok := internal.ConstraintToken(v); ok {
   424  			out += t.String()
   425  		}
   426  		if v.Value != nil {
   427  			switch v.Token {
   428  			case token.ILLEGAL, token.COLON:
   429  				out += ": "
   430  			default:
   431  				out += fmt.Sprintf(" %s ", v.Token)
   432  			}
   433  			out += DebugStr(v.Value)
   434  			for _, a := range v.Attrs {
   435  				out += " "
   436  				out += DebugStr(a)
   437  			}
   438  		}
   439  		return out
   440  
   441  	case *ast.Attribute:
   442  		return v.Text
   443  
   444  	case *ast.Ident:
   445  		return v.Name
   446  
   447  	case *ast.SelectorExpr:
   448  		return DebugStr(v.X) + "." + DebugStr(v.Sel)
   449  
   450  	case *ast.CallExpr:
   451  		out := DebugStr(v.Fun)
   452  		out += "("
   453  		out += DebugStr(v.Args)
   454  		out += ")"
   455  		return out
   456  
   457  	case *ast.ParenExpr:
   458  		out := "("
   459  		out += DebugStr(v.X)
   460  		out += ")"
   461  		return out
   462  
   463  	case *ast.UnaryExpr:
   464  		return v.Op.String() + DebugStr(v.X)
   465  
   466  	case *ast.BinaryExpr:
   467  		out := DebugStr(v.X)
   468  		op := v.Op.String()
   469  		if 'a' <= op[0] && op[0] <= 'z' {
   470  			op = fmt.Sprintf(" %s ", op)
   471  		}
   472  		out += op
   473  		out += DebugStr(v.Y)
   474  		return out
   475  
   476  	case []*ast.CommentGroup:
   477  		var a []string
   478  		for _, c := range v {
   479  			a = append(a, DebugStr(c))
   480  		}
   481  		return strings.Join(a, "\n")
   482  
   483  	case *ast.CommentGroup:
   484  		str := "["
   485  		if v.Doc {
   486  			str += "d"
   487  		}
   488  		if v.Line {
   489  			str += "l"
   490  		}
   491  		str += strconv.Itoa(int(v.Position))
   492  		var a = []string{}
   493  		for _, c := range v.List {
   494  			a = append(a, c.Text)
   495  		}
   496  		return str + strings.Join(a, " ") + "] "
   497  
   498  	case *ast.IndexExpr:
   499  		out := DebugStr(v.X)
   500  		out += "["
   501  		out += DebugStr(v.Index)
   502  		out += "]"
   503  		return out
   504  
   505  	case *ast.SliceExpr:
   506  		out := DebugStr(v.X)
   507  		out += "["
   508  		out += DebugStr(v.Low)
   509  		out += ":"
   510  		out += DebugStr(v.High)
   511  		out += "]"
   512  		return out
   513  
   514  	case *ast.ImportSpec:
   515  		out := ""
   516  		if v.Name != nil {
   517  			out += DebugStr(v.Name)
   518  			out += " "
   519  		}
   520  		out += DebugStr(v.Path)
   521  		return out
   522  
   523  	case *ast.Func:
   524  		return fmt.Sprintf("func(%v): %v", DebugStr(v.Args), DebugStr(v.Ret))
   525  
   526  	case []ast.Decl:
   527  		if len(v) == 0 {
   528  			return ""
   529  		}
   530  		out := ""
   531  		for _, d := range v {
   532  			out += DebugStr(d)
   533  			out += sep
   534  		}
   535  		return out[:len(out)-len(sep)]
   536  
   537  	case []ast.Clause:
   538  		if len(v) == 0 {
   539  			return ""
   540  		}
   541  		out := ""
   542  		for _, c := range v {
   543  			out += DebugStr(c)
   544  			out += " "
   545  		}
   546  		return out
   547  
   548  	case []ast.Expr:
   549  		if len(v) == 0 {
   550  			return ""
   551  		}
   552  		out := ""
   553  		for _, d := range v {
   554  			out += DebugStr(d)
   555  			out += sep
   556  		}
   557  		return out[:len(out)-len(sep)]
   558  
   559  	case []*ast.ImportSpec:
   560  		if len(v) == 0 {
   561  			return ""
   562  		}
   563  		out := ""
   564  		for _, d := range v {
   565  			out += DebugStr(d)
   566  			out += sep
   567  		}
   568  		return out[:len(out)-len(sep)]
   569  
   570  	default:
   571  		if v == nil {
   572  			return ""
   573  		}
   574  		return fmt.Sprintf("<%T>", x)
   575  	}
   576  }
   577  
   578  const sep = ", "