1 |
package parser |
2 |
|
3 |
import ( |
4 |
"bytes" |
5 |
"fmt" |
6 |
"io" |
7 |
"io/ioutil" |
8 |
"net/http" |
9 |
"path/filepath" |
10 |
"strings" |
11 |
) |
12 |
|
13 |
type Parser struct { |
14 |
scanner *scanner |
15 |
filename string |
16 |
fs http.FileSystem |
17 |
currenttoken *token |
18 |
namedBlocks map[string]*NamedBlock |
19 |
parent *Parser |
20 |
result *Block |
21 |
} |
22 |
|
23 |
func newParser(rdr io.Reader) *Parser { |
24 |
p := new(Parser) |
25 |
p.scanner = newScanner(rdr) |
26 |
p.namedBlocks = make(map[string]*NamedBlock) |
27 |
return p |
28 |
} |
29 |
|
30 |
func StringParser(input string) (*Parser, error) { |
31 |
return newParser(bytes.NewReader([]byte(input))), nil |
32 |
} |
33 |
|
34 |
func ByteParser(input []byte) (*Parser, error) { |
35 |
return newParser(bytes.NewReader(input)), nil |
36 |
} |
37 |
|
38 |
func (p *Parser) SetFilename(filename string) { |
39 |
p.filename = filename |
40 |
} |
41 |
|
42 |
func (p *Parser) SetVirtualFilesystem(fs http.FileSystem) { |
43 |
p.fs = fs |
44 |
} |
45 |
|
46 |
func FileParser(filename string) (*Parser, error) { |
47 |
data, err := ioutil.ReadFile(filename) |
48 |
|
49 |
if err != nil { |
50 |
return nil, err |
51 |
} |
52 |
|
53 |
parser := newParser(bytes.NewReader(data)) |
54 |
parser.filename = filename |
55 |
return parser, nil |
56 |
} |
57 |
|
58 |
func VirtualFileParser(filename string, fs http.FileSystem) (*Parser, error) { |
59 |
file, err := fs.Open(filename) |
60 |
if err != nil { |
61 |
return nil, err |
62 |
} |
63 |
|
64 |
data, err := ioutil.ReadAll(file) |
65 |
if err != nil { |
66 |
return nil, err |
67 |
} |
68 |
|
69 |
parser := newParser(bytes.NewReader(data)) |
70 |
parser.filename = filename |
71 |
parser.fs = fs |
72 |
return parser, nil |
73 |
} |
74 |
|
75 |
func (p *Parser) Parse() *Block { |
76 |
if p.result != nil { |
77 |
return p.result |
78 |
} |
79 |
|
80 |
defer func() { |
81 |
if r := recover(); r != nil { |
82 |
if rs, ok := r.(string); ok && rs[:len("Amber Error")] == "Amber Error" { |
83 |
panic(r) |
84 |
} |
85 |
|
86 |
pos := p.pos() |
87 |
|
88 |
if len(pos.Filename) > 0 { |
89 |
panic(fmt.Sprintf("Amber Error in <%s>: %v - Line: %d, Column: %d, Length: %d", pos.Filename, r, pos.LineNum, pos.ColNum, pos.TokenLength)) |
90 |
} else { |
91 |
panic(fmt.Sprintf("Amber Error: %v - Line: %d, Column: %d, Length: %d", r, pos.LineNum, pos.ColNum, pos.TokenLength)) |
92 |
} |
93 |
} |
94 |
}() |
95 |
|
96 |
block := newBlock() |
97 |
p.advance() |
98 |
|
99 |
for { |
100 |
if p.currenttoken == nil || p.currenttoken.Kind == tokEOF { |
101 |
break |
102 |
} |
103 |
|
104 |
if p.currenttoken.Kind == tokBlank { |
105 |
p.advance() |
106 |
continue |
107 |
} |
108 |
|
109 |
block.push(p.parse()) |
110 |
} |
111 |
|
112 |
if p.parent != nil { |
113 |
p.parent.Parse() |
114 |
|
115 |
for _, prev := range p.parent.namedBlocks { |
116 |
ours := p.namedBlocks[prev.Name] |
117 |
|
118 |
if ours == nil { |
119 |
// Put a copy of the named block into current context, so that sub-templates can use the block |
120 |
p.namedBlocks[prev.Name] = prev |
121 |
continue |
122 |
} |
123 |
|
124 |
top := findTopmostParentWithNamedBlock(p, prev.Name) |
125 |
nb := top.namedBlocks[prev.Name] |
126 |
switch ours.Modifier { |
127 |
case NamedBlockAppend: |
128 |
for i := 0; i < len(ours.Children); i++ { |
129 |
nb.push(ours.Children[i]) |
130 |
} |
131 |
case NamedBlockPrepend: |
132 |
for i := len(ours.Children) - 1; i >= 0; i-- { |
133 |
nb.pushFront(ours.Children[i]) |
134 |
} |
135 |
default: |
136 |
nb.Children = ours.Children |
137 |
} |
138 |
} |
139 |
|
140 |
block = p.parent.result |
141 |
} |
142 |
|
143 |
p.result = block |
144 |
return block |
145 |
} |
146 |
|
147 |
func (p *Parser) pos() SourcePosition { |
148 |
pos := p.scanner.Pos() |
149 |
pos.Filename = p.filename |
150 |
return pos |
151 |
} |
152 |
|
153 |
func (p *Parser) parseRelativeFile(filename string) *Parser { |
154 |
if len(p.filename) == 0 { |
155 |
panic("Unable to import or extend " + filename + " in a non filesystem based parser.") |
156 |
} |
157 |
|
158 |
filename = filepath.Join(filepath.Dir(p.filename), filename) |
159 |
|
160 |
if strings.IndexRune(filepath.Base(filename), '.') < 0 { |
161 |
filename = filename + ".amber" |
162 |
} |
163 |
|
164 |
parser, err := FileParser(filename) |
165 |
if err != nil && p.fs != nil { |
166 |
parser, err = VirtualFileParser(filename, p.fs) |
167 |
} |
168 |
if err != nil { |
169 |
panic("Unable to read " + filename + ", Error: " + string(err.Error())) |
170 |
} |
171 |
|
172 |
return parser |
173 |
} |
174 |
|
175 |
func (p *Parser) parse() Node { |
176 |
switch p.currenttoken.Kind { |
177 |
case tokDoctype: |
178 |
return p.parseDoctype() |
179 |
case tokComment: |
180 |
return p.parseComment() |
181 |
case tokText: |
182 |
return p.parseText() |
183 |
case tokIf: |
184 |
return p.parseIf() |
185 |
case tokEach: |
186 |
return p.parseEach() |
187 |
case tokImport: |
188 |
return p.parseImport() |
189 |
case tokTag: |
190 |
return p.parseTag() |
191 |
case tokAssignment: |
192 |
return p.parseAssignment() |
193 |
case tokNamedBlock: |
194 |
return p.parseNamedBlock() |
195 |
case tokExtends: |
196 |
return p.parseExtends() |
197 |
case tokIndent: |
198 |
return p.parseBlock(nil) |
199 |
case tokMixin: |
200 |
return p.parseMixin() |
201 |
case tokMixinCall: |
202 |
return p.parseMixinCall() |
203 |
} |
204 |
|
205 |
panic(fmt.Sprintf("Unexpected token: %d", p.currenttoken.Kind)) |
206 |
} |
207 |
|
208 |
func (p *Parser) expect(typ rune) *token { |
209 |
if p.currenttoken.Kind != typ { |
210 |
panic("Unexpected token!") |
211 |
} |
212 |
curtok := p.currenttoken |
213 |
p.advance() |
214 |
return curtok |
215 |
} |
216 |
|
217 |
func (p *Parser) advance() { |
218 |
p.currenttoken = p.scanner.Next() |
219 |
} |
220 |
|
221 |
func (p *Parser) parseExtends() *Block { |
222 |
if p.parent != nil { |
223 |
panic("Unable to extend multiple parent templates.") |
224 |
} |
225 |
|
226 |
tok := p.expect(tokExtends) |
227 |
parser := p.parseRelativeFile(tok.Value) |
228 |
parser.Parse() |
229 |
p.parent = parser |
230 |
return newBlock() |
231 |
} |
232 |
|
233 |
func (p *Parser) parseBlock(parent Node) *Block { |
234 |
p.expect(tokIndent) |
235 |
block := newBlock() |
236 |
block.SourcePosition = p.pos() |
237 |
|
238 |
for { |
239 |
if p.currenttoken == nil || p.currenttoken.Kind == tokEOF || p.currenttoken.Kind == tokOutdent { |
240 |
break |
241 |
} |
242 |
|
243 |
if p.currenttoken.Kind == tokBlank { |
244 |
p.advance() |
245 |
continue |
246 |
} |
247 |
|
248 |
if p.currenttoken.Kind == tokId || |
249 |
p.currenttoken.Kind == tokClassName || |
250 |
p.currenttoken.Kind == tokAttribute { |
251 |
|
252 |
if tag, ok := parent.(*Tag); ok { |
253 |
attr := p.expect(p.currenttoken.Kind) |
254 |
cond := attr.Data["Condition"] |
255 |
|
256 |
switch attr.Kind { |
257 |
case tokId: |
258 |
tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "id", attr.Value, true, cond}) |
259 |
case tokClassName: |
260 |
tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "class", attr.Value, true, cond}) |
261 |
case tokAttribute: |
262 |
tag.Attributes = append(tag.Attributes, Attribute{p.pos(), attr.Value, attr.Data["Content"], attr.Data["Mode"] == "raw", cond}) |
263 |
} |
264 |
|
265 |
continue |
266 |
} else { |
267 |
panic("Conditional attributes must be placed immediately within a parent tag.") |
268 |
} |
269 |
} |
270 |
|
271 |
block.push(p.parse()) |
272 |
} |
273 |
|
274 |
p.expect(tokOutdent) |
275 |
|
276 |
return block |
277 |
} |
278 |
|
279 |
func (p *Parser) parseIf() *Condition { |
280 |
tok := p.expect(tokIf) |
281 |
cnd := newCondition(tok.Value) |
282 |
cnd.SourcePosition = p.pos() |
283 |
|
284 |
readmore: |
285 |
switch p.currenttoken.Kind { |
286 |
case tokIndent: |
287 |
cnd.Positive = p.parseBlock(cnd) |
288 |
goto readmore |
289 |
case tokElse: |
290 |
p.expect(tokElse) |
291 |
if p.currenttoken.Kind == tokIf { |
292 |
cnd.Negative = newBlock() |
293 |
cnd.Negative.push(p.parseIf()) |
294 |
} else if p.currenttoken.Kind == tokIndent { |
295 |
cnd.Negative = p.parseBlock(cnd) |
296 |
} else { |
297 |
panic("Unexpected token!") |
298 |
} |
299 |
goto readmore |
300 |
} |
301 |
|
302 |
return cnd |
303 |
} |
304 |
|
305 |
func (p *Parser) parseEach() *Each { |
306 |
tok := p.expect(tokEach) |
307 |
ech := newEach(tok.Value) |
308 |
ech.SourcePosition = p.pos() |
309 |
ech.X = tok.Data["X"] |
310 |
ech.Y = tok.Data["Y"] |
311 |
|
312 |
if p.currenttoken.Kind == tokIndent { |
313 |
ech.Block = p.parseBlock(ech) |
314 |
} |
315 |
|
316 |
return ech |
317 |
} |
318 |
|
319 |
func (p *Parser) parseImport() *Block { |
320 |
tok := p.expect(tokImport) |
321 |
node := p.parseRelativeFile(tok.Value).Parse() |
322 |
node.SourcePosition = p.pos() |
323 |
return node |
324 |
} |
325 |
|
326 |
func (p *Parser) parseNamedBlock() *Block { |
327 |
tok := p.expect(tokNamedBlock) |
328 |
|
329 |
if p.namedBlocks[tok.Value] != nil { |
330 |
panic("Multiple definitions of named blocks are not permitted. Block " + tok.Value + " has been re defined.") |
331 |
} |
332 |
|
333 |
block := newNamedBlock(tok.Value) |
334 |
block.SourcePosition = p.pos() |
335 |
|
336 |
if tok.Data["Modifier"] == "append" { |
337 |
block.Modifier = NamedBlockAppend |
338 |
} else if tok.Data["Modifier"] == "prepend" { |
339 |
block.Modifier = NamedBlockPrepend |
340 |
} |
341 |
|
342 |
if p.currenttoken.Kind == tokIndent { |
343 |
block.Block = *(p.parseBlock(nil)) |
344 |
} |
345 |
|
346 |
p.namedBlocks[block.Name] = block |
347 |
|
348 |
if block.Modifier == NamedBlockDefault { |
349 |
return &block.Block |
350 |
} |
351 |
|
352 |
return newBlock() |
353 |
} |
354 |
|
355 |
func (p *Parser) parseDoctype() *Doctype { |
356 |
tok := p.expect(tokDoctype) |
357 |
node := newDoctype(tok.Value) |
358 |
node.SourcePosition = p.pos() |
359 |
return node |
360 |
} |
361 |
|
362 |
func (p *Parser) parseComment() *Comment { |
363 |
tok := p.expect(tokComment) |
364 |
cmnt := newComment(tok.Value) |
365 |
cmnt.SourcePosition = p.pos() |
366 |
cmnt.Silent = tok.Data["Mode"] == "silent" |
367 |
|
368 |
if p.currenttoken.Kind == tokIndent { |
369 |
cmnt.Block = p.parseBlock(cmnt) |
370 |
} |
371 |
|
372 |
return cmnt |
373 |
} |
374 |
|
375 |
func (p *Parser) parseText() *Text { |
376 |
tok := p.expect(tokText) |
377 |
node := newText(tok.Value, tok.Data["Mode"] == "raw") |
378 |
node.SourcePosition = p.pos() |
379 |
return node |
380 |
} |
381 |
|
382 |
func (p *Parser) parseAssignment() *Assignment { |
383 |
tok := p.expect(tokAssignment) |
384 |
node := newAssignment(tok.Data["X"], tok.Value) |
385 |
node.SourcePosition = p.pos() |
386 |
return node |
387 |
} |
388 |
|
389 |
func (p *Parser) parseTag() *Tag { |
390 |
tok := p.expect(tokTag) |
391 |
tag := newTag(tok.Value) |
392 |
tag.SourcePosition = p.pos() |
393 |
|
394 |
ensureBlock := func() { |
395 |
if tag.Block == nil { |
396 |
tag.Block = newBlock() |
397 |
} |
398 |
} |
399 |
|
400 |
readmore: |
401 |
switch p.currenttoken.Kind { |
402 |
case tokIndent: |
403 |
if tag.IsRawText() { |
404 |
p.scanner.readRaw = true |
405 |
} |
406 |
|
407 |
block := p.parseBlock(tag) |
408 |
if tag.Block == nil { |
409 |
tag.Block = block |
410 |
} else { |
411 |
for _, c := range block.Children { |
412 |
tag.Block.push(c) |
413 |
} |
414 |
} |
415 |
case tokId: |
416 |
id := p.expect(tokId) |
417 |
if len(id.Data["Condition"]) > 0 { |
418 |
panic("Conditional attributes must be placed in a block within a tag.") |
419 |
} |
420 |
tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "id", id.Value, true, ""}) |
421 |
goto readmore |
422 |
case tokClassName: |
423 |
cls := p.expect(tokClassName) |
424 |
if len(cls.Data["Condition"]) > 0 { |
425 |
panic("Conditional attributes must be placed in a block within a tag.") |
426 |
} |
427 |
tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "class", cls.Value, true, ""}) |
428 |
goto readmore |
429 |
case tokAttribute: |
430 |
attr := p.expect(tokAttribute) |
431 |
if len(attr.Data["Condition"]) > 0 { |
432 |
panic("Conditional attributes must be placed in a block within a tag.") |
433 |
} |
434 |
tag.Attributes = append(tag.Attributes, Attribute{p.pos(), attr.Value, attr.Data["Content"], attr.Data["Mode"] == "raw", ""}) |
435 |
goto readmore |
436 |
case tokText: |
437 |
if p.currenttoken.Data["Mode"] != "piped" { |
438 |
ensureBlock() |
439 |
tag.Block.pushFront(p.parseText()) |
440 |
goto readmore |
441 |
} |
442 |
} |
443 |
|
444 |
return tag |
445 |
} |
446 |
|
447 |
func (p *Parser) parseMixin() *Mixin { |
448 |
tok := p.expect(tokMixin) |
449 |
mixin := newMixin(tok.Value, tok.Data["Args"]) |
450 |
mixin.SourcePosition = p.pos() |
451 |
|
452 |
if p.currenttoken.Kind == tokIndent { |
453 |
mixin.Block = p.parseBlock(mixin) |
454 |
} |
455 |
|
456 |
return mixin |
457 |
} |
458 |
|
459 |
func (p *Parser) parseMixinCall() *MixinCall { |
460 |
tok := p.expect(tokMixinCall) |
461 |
mixinCall := newMixinCall(tok.Value, tok.Data["Args"]) |
462 |
mixinCall.SourcePosition = p.pos() |
463 |
return mixinCall |
464 |
} |
465 |
|
466 |
func findTopmostParentWithNamedBlock(p *Parser, name string) *Parser { |
467 |
top := p |
468 |
|
469 |
for { |
470 |
if top.namedBlocks[name] == nil { |
471 |
return nil |
472 |
} |
473 |
if top.parent == nil { |
474 |
return top |
475 |
} |
476 |
if top.parent.namedBlocks[name] != nil { |
477 |
top = top.parent |
478 |
} else { |
479 |
return top |
480 |
} |
481 |
} |
482 |
} |