-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcompile.go
More file actions
469 lines (407 loc) · 10.5 KB
/
compile.go
File metadata and controls
469 lines (407 loc) · 10.5 KB
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
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
package tmpl
import (
"bytes"
"fmt"
"io"
"strings"
)
//parseTree represents a parsed template and the set of blocks/functions that
//it will use to execute.
type parseTree struct {
base executer
context *context
}
//Execute runs the parsed template with the context value as the root.
func (p *parseTree) Execute(w io.Writer, ctx interface{}) error {
if p.base == nil {
return nil
}
p.context.stack = pathRootedAt(ctx)
return p.base.Execute(w, p.context)
}
//String returns a nice printable representation of the parse tree and context.
func (p *parseTree) String() string {
var buf bytes.Buffer
fmt.Fprintln(&buf, p.context)
fmt.Fprintln(&buf, p.base)
return buf.String()
}
//parser is a type that represnts an ongoing parse of a template.
type parser struct {
//parser setup
in chan token //token channel
out chan executer //output channel
err error //error during parsing
end tokenType //for subparse to check for the correct end type
//block channel
blocks chan *executeBlockValue
inBlock bool
//token state types
curr token //currently read token
backed bool //if we're in a backup state
errd token //if a token is an EOF or Error to repeat it forever
}
//parseState is a transition state of the parser state machine.
type parseState func(*parser) parseState
//parse compiles the incoming channel of tokens into a parseTree.
func parse(toks chan token) (t *parseTree, err error) {
t = &parseTree{
context: newContext(),
}
//make a channel of blocks to stick into the context
blocks := make(chan *executeBlockValue)
go func() {
//start a new parser
p := &parser{
in: toks,
errd: tokenNone,
blocks: blocks,
}
//set the base of the parse tree
t.base, err = subParse(p, tokenNoneType)
//signal no more blocks are coming
close(blocks)
}()
//array for redefined block errors
var redef []string
for b := range blocks {
//check if we're redefining a block
if _, ex := t.context.blocks[b.ident]; ex {
redef = append(redef, fmt.Sprintf("Redefined block %s", b.ident))
}
//set our block
t.context.blocks[b.ident] = b
}
//return an error about redefined blocks
if redef != nil {
err = fmt.Errorf(strings.Join(redef, "\n"))
}
//if we have an error, don't return a parse tree
if err != nil {
t = nil
}
return
}
//run executes the parser state machine
func (p *parser) run() {
for state := parseText; state != nil; {
state = state(p)
}
close(p.out)
}
//errorf is a helper that sets an error and returns a stop state
func (p *parser) errorf(format string, args ...interface{}) parseState {
p.err = fmt.Errorf(format, args...)
return nil
}
//errExpect is a helper that sets an error and returns a stop state
func (p *parser) errExpect(ex tokenType, got token) parseState {
return p.errorf("Compile: Expected a %q got a %q", ex, got)
}
//unexpected is a helper that sets an error and returns a stop state
func (p *parser) unexpected(t token) parseState {
return p.errorf("Unexpected %q", t)
}
//accept will accept a token of the given type, and return if it did
func (p *parser) accept(tok tokenType) bool {
if p.next().typ == tok {
return true
}
p.backup()
return false
}
//next returns the next token from the channel. If an error is ever encountered,
//it will return that token every time. It also respects backing up.
func (p *parser) next() token {
if p.backed {
p.backed = false
return p.curr
}
if p.errd.typ != tokenNoneType {
return p.errd
}
p.curr = <-p.in
if isErrorType(p.curr.typ) {
p.errd = p.curr
}
return p.curr
}
//backup puts the last read token back into the channel to be read again. It
//will panic if backup happens more than once.
func (p *parser) backup() {
if p.backed {
panic("double backup")
}
p.backed = true
}
//peek returns the next token in the channel without consuming it. It is
//equivelant to a next() and a backup()
func (p *parser) peek() (t token) {
t = p.next()
p.backup()
return
}
//acceptUntil accepts tokens until a token of the given type is found and it
//will backup so that the next token is of the given type. It will also stop
//if an error type is encountered.
func (p *parser) acceptUntil(tok tokenType) (t []token) {
for {
curr := p.next()
switch typ := curr.typ; {
case typ == tok:
p.backup()
return
case isErrorType(typ): //eof and error signify no more tokens
return
}
t = append(t, curr)
}
panic("unreachable")
}
//subParse starts another parser that runs until an end clause is encountered
//of the given tokenType.
func subParse(parp *parser, end tokenType) (ex executer, err error) {
//create our sub-parser
p := &parser{
in: parp.in, //use the same in channel
out: make(chan executer), //make a new out channel
end: end, //look for the given end token
errd: tokenNone, //we haven't errored yet
inBlock: parp.inBlock || end == tokenBlock, //check if we're in a block
blocks: parp.blocks, //use the same block channel
}
//run the parser
go p.run()
//grab our executers
l := executeList{}
for e := range p.out {
l.Push(e)
}
//compact the list for execute efficiency
l.compact()
//set our executer, dropping the list if it is one element
switch len(l) {
case 0:
case 1:
ex = l[0]
default:
ex = l
}
//grab an error if it happened
err = p.err
//set the token state on the parent to make backup/peek work
parp.curr = p.curr
parp.backed = p.backed
parp.errd = p.errd
return
}
//parseText is the start state of the parser.
func parseText(p *parser) (s parseState) {
switch tok := p.next(); tok.typ {
//do nothing with comments
case tokenComment:
return parseText
//send out literal values and keep parsing text
case tokenLiteral:
p.out <- constantValue(tok.dat)
return parseText
//with an open check what the next action is
case tokenOpen:
return parseOpen
case tokenEOF:
if p.end == tokenNoneType {
return nil
}
return p.errorf("unexpected eof. in a %q context", p.end)
default:
return p.errorf("Unexpected token: %s", tok)
}
return nil
}
//parseOpen is the state after an open action token.
func parseOpen(p *parser) parseState {
switch tok := p.next(); {
//advanced calls to start a sub parser
case tok.typ == tokenBlock:
//check for a sub parse
if p.inBlock {
return p.errorf("%d:%d: nested blocks", tok.line, tok.pos)
}
return parseBlock
case tok.typ == tokenWith:
return parseWith
case tok.typ == tokenRange:
return parseRange
case tok.typ == tokenIf:
return parseIf
case tok.typ == tokenEvoke:
return parseEvoke
//very special call to handle else
case tok.typ == tokenElse:
if p.end != tokenIf {
return p.errorf("Unexpected else not inside an if context")
}
return nil
//value calls
case isValueType(tok):
p.backup()
val, s := consumeValue(p)
if s != nil {
return p.errorf(s.Error())
}
//grab the close
if t := p.next(); t.typ != tokenClose {
return p.errExpect(tokenClose, t)
}
p.out <- val
return parseText
//end tag
case tok.typ == tokenEnd:
return parseEnd
default:
return p.unexpected(tok)
}
panic("unreachable")
}
//parseEnd should signal the end of a sub parser
func parseEnd(p *parser) parseState {
//didn't get the end we're looking for
if tok := p.next(); tok.typ != p.end {
return p.errExpect(p.end, tok)
}
if tok := p.next(); tok.typ != tokenClose {
return p.errExpect(tokenClose, tok)
}
return nil
}
//parseEvoke parses an evoke action.
func parseEvoke(p *parser) parseState {
//grab the name
ident := p.next()
if ident.typ != tokenIdent {
return p.errExpect(tokenIdent, ident)
}
//see if we have a value type
var ctx *selectorValue
if isValueType(p.peek()) {
var err error
ctx, err = consumeSelector(p)
if err != nil {
return p.errorf(err.Error())
}
}
//grab the close
if tok := p.next(); tok.typ != tokenClose {
return p.errExpect(tokenClose, tok)
}
p.out <- &executeEvoke{string(ident.dat), ctx}
return parseText
}
//parseBlock parses a block definition.
func parseBlock(p *parser) parseState {
//grab the name
ident := p.next()
if ident.typ != tokenIdent {
return p.errExpect(tokenIdent, ident)
}
//consume the close
if tok := p.next(); tok.typ != tokenClose {
return p.errExpect(tokenClose, tok)
}
//start a sub parser looking for an end block
ex, err := subParse(p, tokenBlock)
if err != nil {
return p.errorf(err.Error())
}
//send it to blocks instead of out
p.blocks <- &executeBlockValue{string(ident.dat), "", ex}
return parseText
}
//parseWith parses a with action.
func parseWith(p *parser) parseState {
//grab the value type
ctx, st := consumeSelector(p)
if st != nil {
return p.errorf(st.Error())
}
//grab the close
if tok := p.next(); tok.typ != tokenClose {
return p.errExpect(tokenClose, tok)
}
ex, err := subParse(p, tokenWith)
if err != nil {
return p.errorf(err.Error())
}
p.out <- &executeWith{ctx, ex}
return parseText
}
//parseRange parses a range action.
func parseRange(p *parser) parseState {
//grab the value type
ctx, st := consumeValue(p)
if st != nil {
return p.errorf(st.Error())
}
//default to none
key, val := tokenNone, tokenNone
//check for an as
if tok := p.next(); tok.typ == tokenAs {
//grab two idenitifers
if key = p.next(); key.typ != tokenIdent {
return p.errExpect(tokenIdent, key)
}
if val = p.next(); val.typ != tokenIdent {
return p.errExpect(tokenIdent, val)
}
} else {
//whoops wasn't an as
p.backup()
}
//grab the close
if tok := p.next(); tok.typ != tokenClose {
return p.errExpect(tokenClose, tok)
}
ex, err := subParse(p, tokenRange)
if err != nil {
return p.errorf(err.Error())
}
p.out <- &executeRange{ctx, ex, key, val}
return parseText
}
//parseIf parses an if clause.
func parseIf(p *parser) parseState {
//grab the value
cond, st := consumeValue(p)
if st != nil {
return p.errorf(st.Error())
}
//grab the close
if tok := p.next(); tok.typ != tokenClose {
return p.errExpect(tokenClose, tok)
}
//start a sub parser for succ
succ, err := subParse(p, tokenIf)
if err != nil {
return p.errorf(err.Error())
}
//backup to check how we exited
p.backup()
var fail executer
switch tok := p.next(); tok.typ {
case tokenElse:
//grab the close
if tok := p.next(); tok.typ != tokenClose {
return p.errExpect(tokenClose, tok)
}
var err error
fail, err = subParse(p, tokenIf)
if err != nil {
return p.errorf(err.Error())
}
case tokenClose:
default:
return p.unexpected(tok)
}
p.out <- &executeIf{cond, succ, fail}
return parseText
}