ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/aya/vendor/github.com/eknkc/amber/parser/parser.go
Revision: 1.1
Committed: Mon Sep 30 00:42:06 2024 UTC (6 weeks, 4 days ago) by yakumo_izuru
Branch: MAIN
CVS Tags: HEAD
Log Message:
Mirrored from https://git.chaotic.ninja/git/yakumo_izuru/aya

File Contents

# User Rev Content
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     }