|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- package parser
-
- import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "path/filepath"
- "strings"
- )
-
- type Parser struct {
- scanner *scanner
- filename string
- currenttoken *token
- namedBlocks map[string]*NamedBlock
- parent *Parser
- result *Block
- }
-
- func newParser(rdr io.Reader) *Parser {
- p := new(Parser)
- p.scanner = newScanner(rdr)
- p.namedBlocks = make(map[string]*NamedBlock)
- return p
- }
-
- func StringParser(input string) (*Parser, error) {
- return newParser(bytes.NewReader([]byte(input))), nil
- }
-
- func ByteParser(input []byte) (*Parser, error) {
- return newParser(bytes.NewReader(input)), nil
- }
-
- func (p *Parser) SetFilename(filename string) {
- p.filename = filename
- }
-
- func FileParser(filename string) (*Parser, error) {
- data, err := ioutil.ReadFile(filename)
-
- if err != nil {
- return nil, err
- }
-
- parser := newParser(bytes.NewReader(data))
- parser.filename = filename
- return parser, nil
- }
-
- func (p *Parser) Parse() *Block {
- if p.result != nil {
- return p.result
- }
-
- defer func() {
- if r := recover(); r != nil {
- if rs, ok := r.(string); ok && rs[:len("Amber Error")] == "Amber Error" {
- panic(r)
- }
-
- pos := p.pos()
-
- if len(pos.Filename) > 0 {
- panic(fmt.Sprintf("Amber Error in <%s>: %v - Line: %d, Column: %d, Length: %d", pos.Filename, r, pos.LineNum, pos.ColNum, pos.TokenLength))
- } else {
- panic(fmt.Sprintf("Amber Error: %v - Line: %d, Column: %d, Length: %d", r, pos.LineNum, pos.ColNum, pos.TokenLength))
- }
- }
- }()
-
- block := newBlock()
- p.advance()
-
- for {
- if p.currenttoken == nil || p.currenttoken.Kind == tokEOF {
- break
- }
-
- if p.currenttoken.Kind == tokBlank {
- p.advance()
- continue
- }
-
- block.push(p.parse())
- }
-
- if p.parent != nil {
- p.parent.Parse()
-
- for _, prev := range p.parent.namedBlocks {
- ours := p.namedBlocks[prev.Name]
-
- if ours == nil {
- // Put a copy of the named block into current context, so that sub-templates can use the block
- p.namedBlocks[prev.Name] = prev
- continue
- }
-
- top := findTopmostParentWithNamedBlock(p, prev.Name)
- nb := top.namedBlocks[prev.Name]
- switch ours.Modifier {
- case NamedBlockAppend:
- for i := 0; i < len(ours.Children); i++ {
- nb.push(ours.Children[i])
- }
- case NamedBlockPrepend:
- for i := len(ours.Children) - 1; i >= 0; i-- {
- nb.pushFront(ours.Children[i])
- }
- default:
- nb.Children = ours.Children
- }
- }
-
- block = p.parent.result
- }
-
- p.result = block
- return block
- }
-
- func (p *Parser) pos() SourcePosition {
- pos := p.scanner.Pos()
- pos.Filename = p.filename
- return pos
- }
-
- func (p *Parser) parseRelativeFile(filename string) *Parser {
- if len(p.filename) == 0 {
- panic("Unable to import or extend " + filename + " in a non filesystem based parser.")
- }
-
- filename = filepath.Join(filepath.Dir(p.filename), filename)
-
- if strings.IndexRune(filepath.Base(filename), '.') < 0 {
- filename = filename + ".amber"
- }
-
- parser, err := FileParser(filename)
- if err != nil {
- panic("Unable to read " + filename + ", Error: " + string(err.Error()))
- }
-
- return parser
- }
-
- func (p *Parser) parse() Node {
- switch p.currenttoken.Kind {
- case tokDoctype:
- return p.parseDoctype()
- case tokComment:
- return p.parseComment()
- case tokText:
- return p.parseText()
- case tokIf:
- return p.parseIf()
- case tokEach:
- return p.parseEach()
- case tokImport:
- return p.parseImport()
- case tokTag:
- return p.parseTag()
- case tokAssignment:
- return p.parseAssignment()
- case tokNamedBlock:
- return p.parseNamedBlock()
- case tokExtends:
- return p.parseExtends()
- case tokIndent:
- return p.parseBlock(nil)
- case tokMixin:
- return p.parseMixin()
- case tokMixinCall:
- return p.parseMixinCall()
- }
-
- panic(fmt.Sprintf("Unexpected token: %d", p.currenttoken.Kind))
- }
-
- func (p *Parser) expect(typ rune) *token {
- if p.currenttoken.Kind != typ {
- panic("Unexpected token!")
- }
- curtok := p.currenttoken
- p.advance()
- return curtok
- }
-
- func (p *Parser) advance() {
- p.currenttoken = p.scanner.Next()
- }
-
- func (p *Parser) parseExtends() *Block {
- if p.parent != nil {
- panic("Unable to extend multiple parent templates.")
- }
-
- tok := p.expect(tokExtends)
- parser := p.parseRelativeFile(tok.Value)
- parser.Parse()
- p.parent = parser
- return newBlock()
- }
-
- func (p *Parser) parseBlock(parent Node) *Block {
- p.expect(tokIndent)
- block := newBlock()
- block.SourcePosition = p.pos()
-
- for {
- if p.currenttoken == nil || p.currenttoken.Kind == tokEOF || p.currenttoken.Kind == tokOutdent {
- break
- }
-
- if p.currenttoken.Kind == tokBlank {
- p.advance()
- continue
- }
-
- if p.currenttoken.Kind == tokId ||
- p.currenttoken.Kind == tokClassName ||
- p.currenttoken.Kind == tokAttribute {
-
- if tag, ok := parent.(*Tag); ok {
- attr := p.expect(p.currenttoken.Kind)
- cond := attr.Data["Condition"]
-
- switch attr.Kind {
- case tokId:
- tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "id", attr.Value, true, cond})
- case tokClassName:
- tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "class", attr.Value, true, cond})
- case tokAttribute:
- tag.Attributes = append(tag.Attributes, Attribute{p.pos(), attr.Value, attr.Data["Content"], attr.Data["Mode"] == "raw", cond})
- }
-
- continue
- } else {
- panic("Conditional attributes must be placed immediately within a parent tag.")
- }
- }
-
- block.push(p.parse())
- }
-
- p.expect(tokOutdent)
-
- return block
- }
-
- func (p *Parser) parseIf() *Condition {
- tok := p.expect(tokIf)
- cnd := newCondition(tok.Value)
- cnd.SourcePosition = p.pos()
-
- readmore:
- switch p.currenttoken.Kind {
- case tokIndent:
- cnd.Positive = p.parseBlock(cnd)
- goto readmore
- case tokElse:
- p.expect(tokElse)
- if p.currenttoken.Kind == tokIf {
- cnd.Negative = newBlock()
- cnd.Negative.push(p.parseIf())
- } else if p.currenttoken.Kind == tokIndent {
- cnd.Negative = p.parseBlock(cnd)
- } else {
- panic("Unexpected token!")
- }
- goto readmore
- }
-
- return cnd
- }
-
- func (p *Parser) parseEach() *Each {
- tok := p.expect(tokEach)
- ech := newEach(tok.Value)
- ech.SourcePosition = p.pos()
- ech.X = tok.Data["X"]
- ech.Y = tok.Data["Y"]
-
- if p.currenttoken.Kind == tokIndent {
- ech.Block = p.parseBlock(ech)
- }
-
- return ech
- }
-
- func (p *Parser) parseImport() *Block {
- tok := p.expect(tokImport)
- node := p.parseRelativeFile(tok.Value).Parse()
- node.SourcePosition = p.pos()
- return node
- }
-
- func (p *Parser) parseNamedBlock() *Block {
- tok := p.expect(tokNamedBlock)
-
- if p.namedBlocks[tok.Value] != nil {
- panic("Multiple definitions of named blocks are not permitted. Block " + tok.Value + " has been re defined.")
- }
-
- block := newNamedBlock(tok.Value)
- block.SourcePosition = p.pos()
-
- if tok.Data["Modifier"] == "append" {
- block.Modifier = NamedBlockAppend
- } else if tok.Data["Modifier"] == "prepend" {
- block.Modifier = NamedBlockPrepend
- }
-
- if p.currenttoken.Kind == tokIndent {
- block.Block = *(p.parseBlock(nil))
- }
-
- p.namedBlocks[block.Name] = block
-
- if block.Modifier == NamedBlockDefault {
- return &block.Block
- }
-
- return newBlock()
- }
-
- func (p *Parser) parseDoctype() *Doctype {
- tok := p.expect(tokDoctype)
- node := newDoctype(tok.Value)
- node.SourcePosition = p.pos()
- return node
- }
-
- func (p *Parser) parseComment() *Comment {
- tok := p.expect(tokComment)
- cmnt := newComment(tok.Value)
- cmnt.SourcePosition = p.pos()
- cmnt.Silent = tok.Data["Mode"] == "silent"
-
- if p.currenttoken.Kind == tokIndent {
- cmnt.Block = p.parseBlock(cmnt)
- }
-
- return cmnt
- }
-
- func (p *Parser) parseText() *Text {
- tok := p.expect(tokText)
- node := newText(tok.Value, tok.Data["Mode"] == "raw")
- node.SourcePosition = p.pos()
- return node
- }
-
- func (p *Parser) parseAssignment() *Assignment {
- tok := p.expect(tokAssignment)
- node := newAssignment(tok.Data["X"], tok.Value)
- node.SourcePosition = p.pos()
- return node
- }
-
- func (p *Parser) parseTag() *Tag {
- tok := p.expect(tokTag)
- tag := newTag(tok.Value)
- tag.SourcePosition = p.pos()
-
- ensureBlock := func() {
- if tag.Block == nil {
- tag.Block = newBlock()
- }
- }
-
- readmore:
- switch p.currenttoken.Kind {
- case tokIndent:
- if tag.IsRawText() {
- p.scanner.readRaw = true
- }
-
- block := p.parseBlock(tag)
- if tag.Block == nil {
- tag.Block = block
- } else {
- for _, c := range block.Children {
- tag.Block.push(c)
- }
- }
- case tokId:
- id := p.expect(tokId)
- if len(id.Data["Condition"]) > 0 {
- panic("Conditional attributes must be placed in a block within a tag.")
- }
- tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "id", id.Value, true, ""})
- goto readmore
- case tokClassName:
- cls := p.expect(tokClassName)
- if len(cls.Data["Condition"]) > 0 {
- panic("Conditional attributes must be placed in a block within a tag.")
- }
- tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "class", cls.Value, true, ""})
- goto readmore
- case tokAttribute:
- attr := p.expect(tokAttribute)
- if len(attr.Data["Condition"]) > 0 {
- panic("Conditional attributes must be placed in a block within a tag.")
- }
- tag.Attributes = append(tag.Attributes, Attribute{p.pos(), attr.Value, attr.Data["Content"], attr.Data["Mode"] == "raw", ""})
- goto readmore
- case tokText:
- if p.currenttoken.Data["Mode"] != "piped" {
- ensureBlock()
- tag.Block.pushFront(p.parseText())
- goto readmore
- }
- }
-
- return tag
- }
-
- func (p *Parser) parseMixin() *Mixin {
- tok := p.expect(tokMixin)
- mixin := newMixin(tok.Value, tok.Data["Args"])
- mixin.SourcePosition = p.pos()
-
- if p.currenttoken.Kind == tokIndent {
- mixin.Block = p.parseBlock(mixin)
- }
-
- return mixin
- }
-
- func (p *Parser) parseMixinCall() *MixinCall {
- tok := p.expect(tokMixinCall)
- mixinCall := newMixinCall(tok.Value, tok.Data["Args"])
- mixinCall.SourcePosition = p.pos()
- return mixinCall
- }
-
- func findTopmostParentWithNamedBlock(p *Parser, name string) *Parser {
- top := p
-
- for {
- if top.namedBlocks[name] == nil {
- return nil
- }
- if top.parent == nil {
- return top
- }
- if top.parent.namedBlocks[name] != nil {
- top = top.parent
- }
- }
- }
|