cuelang.org/go@v0.10.1/internal/cmd/cue-ast-print/main.go (about)

     1  // Copyright 2023 The 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  // cue-ast-print parses a CUE file and prints its syntax tree, for example:
    16  //
    17  //	cue-ast-print file.cue
    18  package main
    19  
    20  import (
    21  	"flag"
    22  	"os"
    23  
    24  	"fmt"
    25  	gotoken "go/token"
    26  	"io"
    27  	"reflect"
    28  	"strings"
    29  
    30  	"cuelang.org/go/cue/ast"
    31  	"cuelang.org/go/cue/parser"
    32  	"cuelang.org/go/cue/token"
    33  )
    34  
    35  func main() {
    36  	flag.Parse()
    37  	args := flag.Args()
    38  	if len(args) != 1 {
    39  		// We could support multiple arguments or stdin if useful.
    40  		panic("expecting exactly one argument")
    41  	}
    42  	file, err := parser.ParseFile(args[0], nil, parser.ParseComments)
    43  	if err != nil {
    44  		panic(err)
    45  	}
    46  	debugPrint(os.Stdout, file)
    47  }
    48  
    49  func debugPrint(w io.Writer, node ast.Node) {
    50  	d := &debugPrinter{w: w}
    51  	d.value(reflect.ValueOf(node))
    52  	d.newline()
    53  }
    54  
    55  type debugPrinter struct {
    56  	w     io.Writer
    57  	level int
    58  }
    59  
    60  func (d *debugPrinter) printf(format string, args ...any) {
    61  	fmt.Fprintf(d.w, format, args...)
    62  }
    63  
    64  func (d *debugPrinter) newline() {
    65  	fmt.Fprintf(d.w, "\n%s", strings.Repeat("\t", d.level))
    66  }
    67  
    68  var (
    69  	typeTokenPos   = reflect.TypeFor[token.Pos]()
    70  	typeTokenToken = reflect.TypeFor[token.Token]()
    71  )
    72  
    73  func (d *debugPrinter) value(v reflect.Value) {
    74  	// Skip over interface types.
    75  	if v.Kind() == reflect.Interface {
    76  		v = v.Elem()
    77  	}
    78  
    79  	// Indirecting a nil interface/pointer gives a zero value;
    80  	// stop as calling reflect.Value.Type on an invalid type would panic.
    81  	if !v.IsValid() {
    82  		d.printf("nil")
    83  		return
    84  	}
    85  	// We print the original pointer type if there was one.
    86  	origType := v.Type()
    87  	v = reflect.Indirect(v)
    88  
    89  	t := v.Type()
    90  	switch t {
    91  	// Simple types which can stringify themselves.
    92  	case typeTokenPos, typeTokenToken:
    93  		d.printf("%s(%q)", t, v.Interface())
    94  		return
    95  	}
    96  
    97  	switch t.Kind() {
    98  	default:
    99  		// We assume all other kinds are basic in practice, like string or bool.
   100  		if t.PkgPath() != "" {
   101  			// Mention defined and non-predeclared types, for clarity.
   102  			d.printf("%s(%#v)", t, v.Interface())
   103  		} else {
   104  			d.printf("%#v", v.Interface())
   105  		}
   106  	case reflect.Slice:
   107  		d.printf("%s{", origType)
   108  		d.level++
   109  		for i := 0; i < v.Len(); i++ {
   110  			d.newline()
   111  			ev := v.Index(i)
   112  			d.value(ev)
   113  		}
   114  		d.level--
   115  		d.newline()
   116  		d.printf("}")
   117  	case reflect.Struct:
   118  		d.printf("%s{", origType)
   119  		d.level++
   120  		for i := 0; i < v.NumField(); i++ {
   121  			f := t.Field(i)
   122  			if !gotoken.IsExported(f.Name) {
   123  				continue
   124  			}
   125  			switch f.Name {
   126  			// These fields are cyclic, and they don't represent the syntax anyway.
   127  			case "Scope", "Node", "Unresolved":
   128  				continue
   129  			}
   130  			d.newline()
   131  			d.printf("%s: ", f.Name)
   132  			d.value(v.Field(i))
   133  		}
   134  		val := v.Addr().Interface()
   135  		if val, ok := val.(ast.Node); ok {
   136  			// Comments attached to a node aren't a regular field, but are still useful.
   137  			// The majority of nodes won't have comments, so skip them when empty.
   138  			if comments := ast.Comments(val); len(comments) > 0 {
   139  				d.newline()
   140  				d.printf("Comments: ")
   141  				d.value(reflect.ValueOf(comments))
   142  			}
   143  		}
   144  		d.level--
   145  		d.newline()
   146  		d.printf("}")
   147  	}
   148  }