66
|
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 }
|