Skip to content
This repository was archived by the owner on Aug 27, 2018. It is now read-only.

Commit 11440ca

Browse files
committed
examples/sites/syntaxviewer: implement a proper printer
1 parent 954e900 commit 11440ca

File tree

2 files changed

+213
-2
lines changed

2 files changed

+213
-2
lines changed

examples/sites/syntaxviewer/app.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,12 @@ func (a AppDef) handleEvent() {
147147

148148
case a.State().Shell:
149149
in := strings.NewReader(st.Code)
150-
f, err := syntax.NewParser().Parse(in, "")
150+
f, err := syntax.NewParser().Parse(in, "stdin")
151151
if err != nil {
152152
st.Ast = err.Error()
153153
return
154154
}
155-
syntax.NewPrinter().Print(b, f)
155+
fprint(b, f, nil)
156156
}
157157

158158
st.Ast = b.String()
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"go/ast"
6+
"io"
7+
"reflect"
8+
9+
"mvdan.cc/sh/syntax"
10+
)
11+
12+
func fprint(w io.Writer, x interface{}, f FieldFilter) (err error) {
13+
// setup printer
14+
p := printer{
15+
output: w,
16+
filter: f,
17+
ptrmap: make(map[interface{}]int),
18+
last: '\n', // force printing of line number on first line
19+
}
20+
21+
// install error handler
22+
defer func() {
23+
if e := recover(); e != nil {
24+
err = e.(localError).err // re-panics if it's not a localError
25+
}
26+
}()
27+
28+
// print x
29+
if x == nil {
30+
p.printf("nil\n")
31+
return
32+
}
33+
p.print(reflect.ValueOf(x))
34+
p.printf("\n")
35+
36+
return
37+
}
38+
39+
type FieldFilter func(name string, value reflect.Value) bool
40+
41+
func NotNilFilter(_ string, v reflect.Value) bool {
42+
switch v.Kind() {
43+
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
44+
return !v.IsNil()
45+
}
46+
return true
47+
}
48+
49+
type printer struct {
50+
output io.Writer
51+
filter FieldFilter
52+
ptrmap map[interface{}]int // *T -> line number
53+
indent int // current indentation level
54+
last byte // the last byte processed by Write
55+
line int // current line number
56+
}
57+
58+
var indent = []byte(". ")
59+
60+
func (p *printer) Write(data []byte) (n int, err error) {
61+
var m int
62+
for i, b := range data {
63+
// invariant: data[0:n] has been written
64+
if b == '\n' {
65+
m, err = p.output.Write(data[n : i+1])
66+
n += m
67+
if err != nil {
68+
return
69+
}
70+
p.line++
71+
} else if p.last == '\n' {
72+
_, err = fmt.Fprintf(p.output, "%6d ", p.line)
73+
if err != nil {
74+
return
75+
}
76+
for j := p.indent; j > 0; j-- {
77+
_, err = p.output.Write(indent)
78+
if err != nil {
79+
return
80+
}
81+
}
82+
}
83+
p.last = b
84+
}
85+
if len(data) > n {
86+
m, err = p.output.Write(data[n:])
87+
n += m
88+
}
89+
return
90+
}
91+
92+
type localError struct {
93+
err error
94+
}
95+
96+
func (p *printer) printf(format string, args ...interface{}) {
97+
if _, err := fmt.Fprintf(p, format, args...); err != nil {
98+
panic(localError{err})
99+
}
100+
}
101+
102+
func (p *printer) print(x reflect.Value) {
103+
if !NotNilFilter("", x) {
104+
p.printf("nil")
105+
return
106+
}
107+
108+
switch x.Kind() {
109+
case reflect.Interface:
110+
p.print(x.Elem())
111+
112+
case reflect.Map:
113+
p.printf("%s (len = %d) {", x.Type(), x.Len())
114+
if x.Len() > 0 {
115+
p.indent++
116+
p.printf("\n")
117+
for _, key := range x.MapKeys() {
118+
p.print(key)
119+
p.printf(": ")
120+
p.print(x.MapIndex(key))
121+
p.printf("\n")
122+
}
123+
p.indent--
124+
}
125+
p.printf("}")
126+
127+
case reflect.Ptr:
128+
p.printf("*")
129+
// type-checked ASTs may contain cycles - use ptrmap
130+
// to keep track of objects that have been printed
131+
// already and print the respective line number instead
132+
ptr := x.Interface()
133+
if line, exists := p.ptrmap[ptr]; exists {
134+
p.printf("(obj @ %d)", line)
135+
} else {
136+
p.ptrmap[ptr] = p.line
137+
p.print(x.Elem())
138+
}
139+
140+
case reflect.Array:
141+
p.printf("%s {", x.Type())
142+
if x.Len() > 0 {
143+
p.indent++
144+
p.printf("\n")
145+
for i, n := 0, x.Len(); i < n; i++ {
146+
p.printf("%d: ", i)
147+
p.print(x.Index(i))
148+
p.printf("\n")
149+
}
150+
p.indent--
151+
}
152+
p.printf("}")
153+
154+
case reflect.Slice:
155+
if s, ok := x.Interface().([]byte); ok {
156+
p.printf("%#q", s)
157+
return
158+
}
159+
p.printf("%s (len = %d) {", x.Type(), x.Len())
160+
if x.Len() > 0 {
161+
p.indent++
162+
p.printf("\n")
163+
for i, n := 0, x.Len(); i < n; i++ {
164+
p.printf("%d: ", i)
165+
p.print(x.Index(i))
166+
p.printf("\n")
167+
}
168+
p.indent--
169+
}
170+
p.printf("}")
171+
172+
case reflect.Struct:
173+
t := x.Type()
174+
p.printf("%s {", t)
175+
p.indent++
176+
first := true
177+
for i, n := 0, t.NumField(); i < n; i++ {
178+
// exclude non-exported fields because their
179+
// values cannot be accessed via reflection
180+
if name := t.Field(i).Name; ast.IsExported(name) {
181+
value := x.Field(i)
182+
if p.filter == nil || p.filter(name, value) {
183+
if first {
184+
p.printf("\n")
185+
first = false
186+
}
187+
p.printf("%s: ", name)
188+
p.print(value)
189+
p.printf("\n")
190+
}
191+
}
192+
}
193+
p.indent--
194+
p.printf("}")
195+
196+
default:
197+
v := x.Interface()
198+
switch v := v.(type) {
199+
case string:
200+
// print strings in quotes
201+
p.printf("%q", v)
202+
return
203+
case syntax.Pos:
204+
// position values can be printed nicely if we have a file set
205+
p.printf("%v:%v", v.Line(), v.Col())
206+
return
207+
}
208+
// default
209+
p.printf("%v", v)
210+
}
211+
}

0 commit comments

Comments
 (0)