Mercurial > yakumo_izuru > aya
comparison vendor/github.com/eknkc/amber/parser/parser.go @ 66:787b5ee0289d draft
Use vendored modules
Signed-off-by: Izuru Yakumo <yakumo.izuru@chaotic.ninja>
author | yakumo.izuru |
---|---|
date | Sun, 23 Jul 2023 13:18:53 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
65:6d985efa0f7a | 66:787b5ee0289d |
---|---|
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 } |