1 |
yakumo_izuru |
1.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 |
|
|
} |