Skip to content

Commit

Permalink
cmd/cue-ast-print: allow reading stdin
Browse files Browse the repository at this point in the history
It's useful to be able to pipe stdin into this command.
While we're about it, add a proper usage message
and make the output slightly more compact by:
- omitting a newline for empty slices and structs
- omitting the type name when it's implied by an enclosing
slice (as allowed in Go slice literals).

Also avoid the unneeded calls to `reflect.Value.Interface`: the
fmt package will do that automatically for us.

Signed-off-by: Roger Peppe <rogpeppe@gmail.com>
Change-Id: I6339c4ef8d0af0f7c567301050a837350cb4de7f
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1199601
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
  • Loading branch information
rogpeppe committed Aug 16, 2024
1 parent 616f91a commit b52cd92
Showing 1 changed file with 52 additions and 21 deletions.
73 changes: 52 additions & 21 deletions internal/cmd/cue-ast-print/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main

import (
"flag"
"log"
"os"

"fmt"
Expand All @@ -33,13 +34,27 @@ import (
)

func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "usage: cue-ast-print [file.cue]\n")
os.Exit(2)
}
flag.Parse()
args := flag.Args()
if len(args) != 1 {
// We could support multiple arguments or stdin if useful.
panic("expecting exactly one argument")
var filename string
var src any
switch flag.NArg() {
case 0:
filename = "<stdin>"
data, err := io.ReadAll(os.Stdin)
if err != nil {
log.Fatal(err)
}
src = data
case 1:
filename = flag.Arg(0)
default:
flag.Usage()
}
file, err := parser.ParseFile(args[0], nil, parser.ParseComments)
file, err := parser.ParseFile(filename, src, parser.ParseComments)
if err != nil {
panic(err)
}
Expand All @@ -48,7 +63,7 @@ func main() {

func debugPrint(w io.Writer, node ast.Node) {
d := &debugPrinter{w: w}
d.value(reflect.ValueOf(node))
d.value(reflect.ValueOf(node), nil)
d.newline()
}

Expand All @@ -70,7 +85,7 @@ var (
typeTokenToken = reflect.TypeFor[token.Token]()
)

func (d *debugPrinter) value(v reflect.Value) {
func (d *debugPrinter) value(v reflect.Value, impliedType reflect.Type) {
// Skip over interface types.
if v.Kind() == reflect.Interface {
v = v.Elem()
Expand All @@ -90,7 +105,7 @@ func (d *debugPrinter) value(v reflect.Value) {
switch t {
// Simple types which can stringify themselves.
case typeTokenPos, typeTokenToken:
d.printf("%s(%q)", t, v.Interface())
d.printf("%s(%q)", t, v)
return
}

Expand All @@ -99,23 +114,35 @@ func (d *debugPrinter) value(v reflect.Value) {
// We assume all other kinds are basic in practice, like string or bool.
if t.PkgPath() != "" {
// Mention defined and non-predeclared types, for clarity.
d.printf("%s(%#v)", t, v.Interface())
d.printf("%s(%#v)", t, v)
} else {
d.printf("%#v", v.Interface())
d.printf("%#v", v)
}
case reflect.Slice:
d.printf("%s{", origType)
d.level++
for i := 0; i < v.Len(); i++ {
if origType != impliedType {
d.printf("%s", origType)
}
d.printf("{")
if v.Len() > 0 {
d.level++
for i := 0; i < v.Len(); i++ {
d.newline()
ev := v.Index(i)
// Note: a slice literal implies the type of its elements
// so we can avoid mentioning the type
// of each element if it matches.
d.value(ev, t.Elem())
}
d.level--
d.newline()
ev := v.Index(i)
d.value(ev)
}
d.level--
d.newline()
d.printf("}")
case reflect.Struct:
d.printf("%s{", origType)
if origType != impliedType {
d.printf("%s", origType)
}
d.printf("{")
printed := false
d.level++
for i := 0; i < v.NumField(); i++ {
f := t.Field(i)
Expand All @@ -127,22 +154,26 @@ func (d *debugPrinter) value(v reflect.Value) {
case "Scope", "Node", "Unresolved":
continue
}
printed = true
d.newline()
d.printf("%s: ", f.Name)
d.value(v.Field(i))
d.value(v.Field(i), nil)
}
val := v.Addr().Interface()
if val, ok := val.(ast.Node); ok {
// Comments attached to a node aren't a regular field, but are still useful.
// The majority of nodes won't have comments, so skip them when empty.
if comments := ast.Comments(val); len(comments) > 0 {
printed = true
d.newline()
d.printf("Comments: ")
d.value(reflect.ValueOf(comments))
d.value(reflect.ValueOf(comments), nil)
}
}
d.level--
d.newline()
if printed {
d.newline()
}
d.printf("}")
}
}

0 comments on commit b52cd92

Please sign in to comment.