-
Notifications
You must be signed in to change notification settings - Fork 0
/
parser.go
158 lines (145 loc) · 3.4 KB
/
parser.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
package narg
import (
"errors"
"fmt"
"io"
"github.com/nochso/narg/token"
)
// Parse narg input into an ItemSlice.
func Parse(r io.Reader) (ItemSlice, error) {
p := &Parser{l: NewLexer(r)}
return p.Parse()
}
// Parser turns tokens into items.
type Parser struct {
l *Lexer
buf []token.T
}
// Parse all items.
func (p *Parser) Parse() (ItemSlice, error) {
doc := []Item{}
item, err := p.parse()
for err == nil {
doc = append(doc, item)
item, err = p.parse()
}
if err == io.EOF {
err = nil
}
if err == errEos || err == errSos {
t := p.l.Token
err = fmt.Errorf("line %d, column %d: expected quoted or unquoted value at beginning of item, got %s %#v", t.Line, t.Col, t.Type, t.Str)
}
return doc, err
}
// parse exactly one Item
func (p *Parser) parse() (i Item, err error) {
i, err = p.parseName(i)
if err != nil {
return
}
i, err = p.parseArgs(i)
if err == errSos {
i, err = p.parseChildren(i)
if err == errEos { // ok, it ends with a '}'
err = nil
}
return
}
return
}
var errEos = errors.New("end of child section")
var errSos = errors.New("start of child section")
func (p *Parser) parseChildren(i Item) (Item, error) {
child, err := p.parse()
for err == nil {
i.Children = append(i.Children, child)
child, err = p.parse()
}
if err == errEos && child.Name != "" {
i.Children = append(i.Children, child)
}
return i, err
}
func (p *Parser) parseName(i Item) (Item, error) {
t := p.scanIgnore(token.Whitespace, token.Linefeed, token.Comment)
if t.Type == token.EOF {
return i, io.EOF
}
if p.l.Err != nil {
return i, p.l.Err
}
if t.Type == token.BraceClose {
return i, errEos
}
if t.Type == token.BraceOpen {
return i, errSos
}
if t.Type != token.QuotedValue && t.Type != token.UnquotedValue {
err := fmt.Errorf("line %d, column %d: expected quoted or unquoted value at beginning of item, got %s %#v", t.Line, t.Col, t.Type, t.Str)
return i, err
}
i.Name = t.String()
return i, nil
}
func (p *Parser) parseArgs(i Item) (Item, error) {
for {
t := p.scanIgnore(token.Whitespace, token.Comment)
if t.Type == token.EOF {
// valid Item end without any (more) args
return i, nil
}
if t.Type == token.Linefeed {
// valid end of args, but look ahead
t = p.scanIgnore(token.Whitespace, token.Comment, token.Linefeed)
if t.Type != token.BraceClose && t.Type != token.BraceOpen {
// nah, we can't use this. put it back.
p.unscan(t)
return i, nil
}
// fall through to open/close brace
}
if p.l.Err != nil {
return i, p.l.Err
}
if t.Type == token.BraceOpen {
return i, errSos
}
if t.Type == token.BraceClose {
return i, errEos
}
if t.Type != token.QuotedValue && t.Type != token.UnquotedValue {
err := fmt.Errorf("line %d, column %d: expected quoted or unquoted value as argument no. %d, got %s %#v", t.Line, t.Col, len(i.Args)+1, t.Type, t.Str)
return i, err
}
i.Args = append(i.Args, t.String())
}
}
func (p *Parser) scan() (t token.T) {
if len(p.buf) > 0 {
t, p.buf = p.buf[len(p.buf)-1], p.buf[:len(p.buf)-1]
return
}
p.l.Scan()
return p.l.Token
}
func (p *Parser) unscan(t token.T) {
p.buf = append(p.buf, t)
}
func (p *Parser) scanIgnore(ignore ...token.Type) token.T {
t := p.scan()
for t.Type != token.EOF && t.Type != token.Invalid {
ignored := false
for _, it := range ignore {
if t.Type == it {
ignored = true
continue
}
}
if !ignored {
return t
}
t = p.scan()
}
return t
}