-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdecoder.go
344 lines (322 loc) · 7.82 KB
/
decoder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
package brief
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"text/scanner"
)
// DecoderState constants
type DecoderState int
// States of the decoder
const (
Unknown DecoderState = iota
NewLine // LineStart
KeyElem // Key set to elem
KeyValue // Key set to Key
KeyEmpty // ready for next key or content key is empty
OnName // Set Name
OnValue // Put key-value
OnFeature // Exec Feature
FeatureSet // Feature value is set
NegValue // Minus sign instead of a value
OnComment // A comment
)
// Decoder for brief formated files
type Decoder struct {
Err error
Roots, Nesting []*Node
Text Scanner
ScanType rune
Token string
State DecoderState
Key, Feature string
Padding int
Dir string
Debug bool
}
// NewDecoder from reader with tabsize and optional directory
// srcdir is used with #include files relative to this reader
func NewDecoder(reader io.Reader, tabsize int, srcdir string) *Decoder {
dir := srcdir
if len(dir) == 0 {
var err error
dir, err = os.Getwd()
if err != nil {
dir = "./"
}
}
var decoder Decoder
decoder.Dir = dir
decoder.Text.Init(reader, tabsize)
decoder.Roots = make([]*Node, 0)
decoder.Nesting = make([]*Node, 0)
return &decoder
}
// NewFileDecoder new decoder that reads from a filename
func NewFileDecoder(filename string) (*Decoder, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
return NewDecoder(file, 4, fileDir(filename)), nil
}
func fileDir(filename string) string {
if filepath.IsAbs(filename) {
return filepath.Dir(filename)
}
abs, err := filepath.Abs(filename)
if err != nil {
abs = "./" + filename // because os.Getwd failed
}
return filepath.Dir(abs)
}
// DecodeFile into brief Nodes
func DecodeFile(filename string) ([]*Node, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
nodes, err := Decode(file, fileDir(filename))
if err != nil {
return nil, err
}
return nodes, nil
}
// Decode creates a Node by parsing brief format from reader
func Decode(reader io.Reader, srcdir string) ([]*Node, error) {
dec := NewDecoder(reader, 4, srcdir)
return dec.Decode()
}
// Errorf added to decoder and returned
func (dec *Decoder) Errorf(format string, args ...interface{}) error {
return dec.Error(fmt.Sprintf(format, args...))
}
// Error added to decoder and returned
func (dec *Decoder) Error(msg string) error {
pos := dec.Text.Pos()
err := fmt.Errorf("%s on %q at %d:%d", msg, dec.Token, pos.Line, pos.Column-len(dec.Token))
if dec.Err != nil {
err = fmt.Errorf("%s\n%s", dec.Err, err)
}
dec.Err = err
return dec.Err
}
// topLevel returns true if
func (dec *Decoder) topLevel() bool {
return len(dec.Nesting) == 0
}
// indent adds padding to indent
func (dec *Decoder) indent() int {
return dec.Text.Indent + dec.Padding
}
// parent returns parent node, if any
// reduces nesting
func (dec *Decoder) parent() *Node {
size := len(dec.Nesting)
if size > 0 {
return dec.Nesting[size-1]
}
return nil
}
func (dec *Decoder) next() bool {
dec.ScanType = dec.Text.Scan()
dec.Token = dec.Text.TokenText()
return dec.ScanType != scanner.EOF
}
func (dec *Decoder) setName() {
parent := dec.parent()
if parent == nil {
dec.Error("SetName parent not found")
return
}
parent.Name = strings.Trim(dec.Token, "\"")
}
func (dec *Decoder) setValue(neg bool) {
parent := dec.parent()
if parent == nil {
dec.Error("SetValue parent not found")
return
}
if len(dec.Key) == 0 {
dec.Error("SetValue no key")
}
if neg && dec.Token[0] != '"' {
parent.Put(dec.Key, "-"+dec.Token)
return
}
parent.Put(dec.Key, strings.Trim(dec.Token, "\""))
}
func (dec *Decoder) setContent() {
parent := dec.parent()
if parent == nil {
dec.Error("SetContent parent not found")
return
}
parent.Content = strings.Trim(dec.Token, "`")
}
func (dec *Decoder) findParent(indent int) *Node {
for size := len(dec.Nesting); size > 0; size = len(dec.Nesting) {
last := size - 1
parent := dec.Nesting[last]
if indent > parent.Indent {
return parent
}
dec.Nesting = dec.Nesting[:last]
}
return nil
}
func (dec *Decoder) addNode() {
node := NewNode(dec.Token, dec.indent())
parent := dec.findParent(node.Indent)
if parent != nil {
node.Parent = parent
parent.Body = append(parent.Body, node)
} else {
dec.Roots = append(dec.Roots, node)
}
dec.Nesting = append(dec.Nesting, node)
}
// Decode creates a Node by parsing brief format from reader
func (dec *Decoder) Decode() ([]*Node, error) {
dec.State = KeyEmpty
for dec.next() {
if dec.Err != nil {
return nil, dec.Err
}
if dec.Text.LineStart {
switch dec.State {
case KeyElem, KeyEmpty, OnComment:
dec.State = NewLine
default:
return nil, dec.Error("invalid stray token at end of line above")
}
}
// if this is a feature use the feature handler
if dec.State == FeatureSet {
dec.handleFeature()
dec.State = KeyEmpty
continue
}
switch dec.ScanType {
case scanner.Comment: // skip comments
dec.State = OnComment
case scanner.Ident:
switch dec.State {
case NewLine:
dec.addNode()
dec.Key = dec.Token
dec.State = KeyElem
case KeyElem: // no colon after elem
dec.Key = dec.Token
dec.State = KeyValue
case KeyEmpty:
dec.Key = dec.Token
dec.State = KeyValue
case OnName:
dec.setName()
dec.Key = ""
dec.State = KeyEmpty
case NegValue:
return nil, dec.Error("invalid minus before symbol")
case OnValue:
dec.setValue(false)
dec.Key = ""
dec.State = KeyEmpty
case OnFeature:
dec.Feature = dec.Token
dec.State = FeatureSet
default:
return nil, dec.Error("invalid identifier found")
}
case scanner.String, scanner.Int, scanner.Float:
if dec.State == NegValue && dec.ScanType == scanner.String {
return nil, dec.Error("invalid minus before string")
}
switch dec.State {
case OnName:
dec.setName()
dec.Key = ""
dec.State = KeyEmpty
case OnValue, NegValue:
dec.setValue(dec.State == NegValue)
dec.Key = ""
dec.State = KeyEmpty
default:
return nil, dec.Error("invalid value found")
}
case scanner.RawString:
if dec.State == NegValue {
return nil, dec.Error("invalid minus before content")
}
switch dec.State {
case KeyElem, KeyEmpty:
dec.Key = ""
dec.setContent()
dec.State = KeyEmpty
default:
return nil, dec.Error("invalid content found")
}
case '-':
switch dec.State {
case OnValue, OnName:
dec.State = NegValue
default:
return nil, dec.Error("invalid minus")
}
case ':':
switch dec.State {
case KeyElem:
dec.State = OnName
case KeyValue:
dec.State = OnValue
default:
return nil, dec.Error("invalid syntax ':'")
}
case '+':
switch dec.State {
case NewLine:
dec.State = KeyEmpty
default:
return nil, dec.Error("invalid syntax '+'")
}
case '#':
switch dec.State {
case KeyElem, KeyEmpty:
dec.Key = ""
dec.readBlock()
dec.State = KeyEmpty
case NewLine:
dec.State = OnFeature
default:
return nil, dec.Error("invalid syntax '#'")
}
}
}
return dec.Roots, nil
}
func (dec *Decoder) readBlock() error {
delim := dec.Text.Next()
if !strings.ContainsAny(string(delim), "|@$%") {
return dec.Error("invalid block delimiter: #" + string(delim))
}
var build strings.Builder
for ch := dec.Text.Next(); ch != scanner.EOF; ch = dec.Text.Next() {
if ch == delim {
at := dec.Text.Next()
if at == '#' {
dec.Token = build.String()
dec.setContent()
return nil
}
build.WriteRune(ch)
build.WriteRune(at)
continue
}
build.WriteRune(ch)
}
return dec.Error("Found EOF while reading block no matching " + string(delim))
}