1
1
// Copyright (c) 2017, Daniel Martí <[email protected] >
2
2
// See LICENSE for licensing information
3
3
4
- package main
4
+ // Package typedjson allows encoding and decoding shell syntax trees as JSON.
5
+ // The decoding process needs to know what syntax node types to decode into,
6
+ // so the "typed JSON" requires "Type" keys in some syntax tree node objects:
7
+ //
8
+ // - The root node
9
+ // - Any node represented as an interface field in the parent Go type
10
+ //
11
+ // The types of all other nodes can be inferred from context alone.
12
+ //
13
+ // For the sake of efficiency and simplicity, the "Type" key
14
+ // described above must be first in each JSON object.
15
+ package typedjson
16
+
17
+ // TODO: encoding and decoding nodes other than File is untested.
5
18
6
19
import (
7
20
"encoding/json"
@@ -12,32 +25,50 @@ import (
12
25
"mvdan.cc/sh/v3/syntax"
13
26
)
14
27
15
- func writeJSON (w io.Writer , node syntax.Node , pretty bool ) error {
28
+ // Encode is a shortcut for EncodeOptions.Encode, with the default options.
29
+ func Encode (w io.Writer , node syntax.Node ) error {
30
+ return EncodeOptions {}.Encode (w , node )
31
+ }
32
+
33
+ // EncodeOptions allows configuring how syntax nodes are encoded.
34
+ type EncodeOptions struct {
35
+ Indent string // e.g. "\t"
36
+
37
+ // Allows us to add options later.
38
+ }
39
+
40
+ // Encode writes node to w in its typed JSON form,
41
+ // as described in the package documentation.
42
+ func (opts EncodeOptions ) Encode (w io.Writer , node syntax.Node ) error {
16
43
val := reflect .ValueOf (node )
17
- encVal , _ := encode (val )
44
+ encVal , tname := encodeValue (val )
45
+ if tname == "" {
46
+ panic ("node did not contain a named type?" )
47
+ }
48
+ encVal .Elem ().Field (0 ).SetString (tname )
18
49
enc := json .NewEncoder (w )
19
- if pretty {
20
- enc .SetIndent ("" , " \t " )
50
+ if opts . Indent != "" {
51
+ enc .SetIndent ("" , opts . Indent )
21
52
}
22
53
return enc .Encode (encVal .Interface ())
23
54
}
24
55
25
- func encode (val reflect.Value ) (reflect.Value , string ) {
56
+ func encodeValue (val reflect.Value ) (reflect.Value , string ) {
26
57
switch val .Kind () {
27
58
case reflect .Ptr :
28
- elem := val .Elem ()
29
- if ! elem .IsValid () {
59
+ if val .IsNil () {
30
60
break
31
61
}
32
- return encode ( elem )
62
+ return encodeValue ( val . Elem () )
33
63
case reflect .Interface :
34
64
if val .IsNil () {
35
65
break
36
66
}
37
- enc , tname := encode (val .Elem ())
38
- if tname ! = "" {
39
- enc . Elem (). Field ( 0 ). SetString ( tname )
67
+ enc , tname := encodeValue (val .Elem ())
68
+ if tname = = "" {
69
+ panic ( "interface did not contain a named type?" )
40
70
}
71
+ enc .Elem ().Field (0 ).SetString (tname )
41
72
return enc , ""
42
73
case reflect .Struct :
43
74
// Construct a new struct with an optional Type, Pos and End,
@@ -71,7 +102,7 @@ func encode(val reflect.Value) (reflect.Value, string) {
71
102
if ftyp .Type == exportedPosType {
72
103
encodePos (enc .Field (i ), fval )
73
104
} else {
74
- encElem , _ := encode (fval )
105
+ encElem , _ := encodeValue (fval )
75
106
if encElem .IsValid () {
76
107
enc .Field (i ).Set (encElem )
77
108
}
@@ -88,7 +119,7 @@ func encode(val reflect.Value) (reflect.Value, string) {
88
119
enc := reflect .MakeSlice (anySliceType , n , n )
89
120
for i := 0 ; i < n ; i ++ {
90
121
elem := val .Index (i )
91
- encElem , _ := encode (elem )
122
+ encElem , _ := encodeValue (elem )
92
123
enc .Index (i ).Set (encElem )
93
124
}
94
125
return enc , ""
@@ -161,19 +192,32 @@ func decodePos(val reflect.Value, enc map[string]interface{}) {
161
192
val .Set (reflect .ValueOf (syntax .NewPos (offset , line , column )))
162
193
}
163
194
164
- func readJSON (r io.Reader ) (syntax.Node , error ) {
195
+ // Decode is a shortcut for DecodeOptions.Decode, with the default options.
196
+ func Decode (r io.Reader ) (syntax.Node , error ) {
197
+ return DecodeOptions {}.Decode (r )
198
+ }
199
+
200
+ // DecodeOptions allows configuring how syntax nodes are encoded.
201
+ type DecodeOptions struct {
202
+ // Empty for now; allows us to add options later.
203
+ }
204
+
205
+ // Decode writes node to w in its typed JSON form,
206
+ // as described in the package documentation.
207
+ func (opts DecodeOptions ) Decode (r io.Reader ) (syntax.Node , error ) {
165
208
var enc interface {}
166
209
if err := json .NewDecoder (r ).Decode (& enc ); err != nil {
167
210
return nil , err
168
211
}
169
- node := & syntax.File {}
170
- if err := decode (reflect .ValueOf (node ), enc ); err != nil {
212
+ node := new ( syntax.Node )
213
+ if err := decodeValue (reflect .ValueOf (node ). Elem ( ), enc ); err != nil {
171
214
return nil , err
172
215
}
173
- return node , nil
216
+ return * node , nil
174
217
}
175
218
176
219
var nodeByName = map [string ]reflect.Type {
220
+ "File" : reflect .TypeOf ((* syntax .File )(nil )).Elem (),
177
221
"Word" : reflect .TypeOf ((* syntax .Word )(nil )).Elem (),
178
222
179
223
"Lit" : reflect .TypeOf ((* syntax .Lit )(nil )).Elem (),
@@ -215,7 +259,7 @@ var nodeByName = map[string]reflect.Type{
215
259
"CStyleLoop" : reflect .TypeOf ((* syntax .CStyleLoop )(nil )).Elem (),
216
260
}
217
261
218
- func decode (val reflect.Value , enc interface {}) error {
262
+ func decodeValue (val reflect.Value , enc interface {}) error {
219
263
switch enc := enc .(type ) {
220
264
case map [string ]interface {}:
221
265
if val .Kind () == reflect .Ptr && val .IsNil () {
@@ -246,14 +290,14 @@ func decode(val reflect.Value, enc interface{}) error {
246
290
decodePos (fval , fv .(map [string ]interface {}))
247
291
continue
248
292
}
249
- if err := decode (fval , fv ); err != nil {
293
+ if err := decodeValue (fval , fv ); err != nil {
250
294
return err
251
295
}
252
296
}
253
297
case []interface {}:
254
298
for _ , encElem := range enc {
255
299
elem := reflect .New (val .Type ().Elem ()).Elem ()
256
- if err := decode (elem , encElem ); err != nil {
300
+ if err := decodeValue (elem , encElem ); err != nil {
257
301
return err
258
302
}
259
303
val .Set (reflect .Append (val , elem ))
0 commit comments