-
Notifications
You must be signed in to change notification settings - Fork 2
/
parse.go
244 lines (219 loc) · 6.37 KB
/
parse.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
package moon
import (
"bytes"
"fmt"
"io"
"os"
"strings"
)
const ()
var nodes = map[tokenType]func(p *parser) node{
t_string: func(p *parser) node { return new(stringNode) },
t_real_number: func(p *parser) node { return new(numberNode) },
t_imaginary_number: func(p *parser) node { return new(numberNode) },
t_list_start: func(p *parser) node { p.next(); return &listNode{} },
t_object_start: func(p *parser) node { p.next(); return &objectNode{} },
t_variable: func(p *parser) node { return new(variableNode) },
t_bool: func(p *parser) node { return new(boolNode) },
t_duration: func(p *parser) node { return new(durationNode) },
}
// Static path for configuration file. By default, a call to Parse wil look for
// a file at the path specified by Path.
var Path = ""
func bail(status int, t string, args ...interface{}) {
if status == 0 {
fmt.Fprintf(os.Stdout, t+"\n", args...)
} else {
fmt.Fprintf(os.Stderr, t+"\n", args...)
}
os.Exit(status)
}
// Parse reads in all command line arguments in addition to parsing the Moon
// configuration file found at moon.Path. If moon.Path is not set, Parse does
// not automatically look for a configuration file.
//
// The command-line options as well as the values found in the Moon document
// will be used to fill the destination object pointed to by the dest argument.
// Clients may use struct tags to dictate how values will be filled in the
// destination struct. Within the struct tag, the client may provide a moon
// document describing how the value is to be parsed.
//
// The following tags are currently recognized:
//
// - name: the value's name inside of the moon document
// - help: help text to be printed when running your program as "program help"
// - required: whether or not the specified field is required.
// - default: default value for the given field
// - short: single character to be used as a command-line flag
// - long: a string of characters to be used as a command-line option
//
// Here's an example of a struct definition that is annotated to inform the
// Moon parser how to fill the struct with values from a Moon document.
//
// var config struct {
// Host string `
// name: host
// help: target host with whom we will connect
// required: true
// short: h
// long: host
// `
//
// Port int `
// name: port
// help: port to dial on the target host
// default: 12345
// short: p
// long: port
// `
// }
//
// .. and here's how we would parse our command-line arguments and config file
// at path "./config.moon" to fill the struct at config:
//
// moon.Path = "./config"
// moon.Parse(&config)
//
// Any value provied as a command-line argument will override the value
// supplied by the config file at "./config"
func Parse(dest interface{}) *Object {
cliArgs, err := parseArgs(os.Args, dest)
if err != nil {
bail(1, "unable to parse cli args: %s", err)
}
var obj *Object
if Path != "" {
f, err := os.Open(Path)
if err == nil {
defer f.Close()
o, err := Read(f)
if err != nil {
bail(1, "unable to parse moon config file at path %s: %s", Path, err)
}
obj = o
}
}
if obj == nil {
obj = &Object{items: make(map[string]interface{})}
}
for k, v := range cliArgs.items {
obj.items[k] = v
}
if err := obj.Fill(dest); err != nil {
bail(1, "unable to fill moon config values: %s", err)
}
return obj
}
// Reads a moon object from a given io.Reader. The io.Reader is advanced to
// EOF. The reader is not closed after reading, since it's an io.Reader and not
// an io.ReadCloser. In the event of error, the state that the source reader
// will be left in is undefined.
func Read(r io.Reader) (*Object, error) {
tree, err := parse(r)
if err != nil {
return nil, err
}
ctx := newContext()
if _, err := tree.eval(ctx); err != nil {
return nil, fmt.Errorf("eval error: %s\n", err)
}
return &Object{items: ctx.public}, nil
}
// Reads a moon object from a string. This is purely a convenience method;
// all it does is create a buffer and call the moon.Read function.
func ReadString(source string) (*Object, error) {
return Read(strings.NewReader(source))
}
// Reads a moon object from a slice of bytes. This is purely a concenience
// method; like ReadString, it simply creates a buffer and calls moon.Read
func ReadBytes(b []byte) (*Object, error) {
return Read(bytes.NewBuffer(b))
}
func ReadFile(path string) (*Object, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return Read(f)
}
func parse(r io.Reader) (node, error) {
p := &parser{
root: newRootNode(),
input: lex(r),
backup: make([]token, 0, 8),
}
if err := p.parse(); err != nil {
return nil, err
}
return p.root, nil
}
// parser (little p) is an actual parser. It actually does the parsing of a
// moon document.
type parser struct {
root node
input chan token
backup []token
}
func (p *parser) parse() error {
if p.root == nil {
p.root = newRootNode()
}
return p.root.parse(p)
}
// returns the next token and advances the input stream
func (p *parser) next() token {
if len(p.backup) > 0 {
t := p.backup[len(p.backup)-1]
p.backup = p.backup[:len(p.backup)-1]
return t
}
SKIP_COMMENTS:
t, ok := <-p.input
if !ok {
return token{t_eof, "eof"}
}
if t.t == t_comment {
goto SKIP_COMMENTS
}
return t
}
func (p *parser) peek() token {
t := p.next()
p.unread(t)
return t
}
func (p *parser) unread(t token) {
if p.backup == nil {
p.backup = make([]token, 0, 8)
}
p.backup = append(p.backup, t)
}
func (p *parser) ensureNext(tt tokenType, context string) error {
if p.peek().t != tt {
return fmt.Errorf("unexpected %v in %s: expected %v", p.peek().t, context, tt)
}
return nil
}
// parse the next value. This is to be executed in a context where we know we
// want something that is a value to come next, such as after an equals sign.
func (p *parser) parseValue() (node, error) {
for {
t := p.peek()
switch t.t {
case t_error:
return nil, fmt.Errorf("parse error: saw lex error when looking for value: %v", t.s)
case t_eof:
return nil, fmt.Errorf("parse error: unexpected eof when looking for value")
}
fn, ok := nodes[t.t]
if !ok {
return nil, fmt.Errorf("parse error: unexpected %v token while looking for value", t.t)
}
n := fn(p)
if err := n.parse(p); err != nil {
return nil, err
}
return n, nil
}
}