diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/github.com/eknkc/amber/parser/parser.go	Sun Jul 23 13:18:53 2023 +0000
@@ -0,0 +1,482 @@
+package parser
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"path/filepath"
+	"strings"
+)
+
+type Parser struct {
+	scanner      *scanner
+	filename     string
+	fs           http.FileSystem
+	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 (p *Parser) SetVirtualFilesystem(fs http.FileSystem) {
+	p.fs = fs
+}
+
+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 VirtualFileParser(filename string, fs http.FileSystem) (*Parser, error) {
+	file, err := fs.Open(filename)
+	if err != nil {
+		return nil, err
+	}
+
+	data, err := ioutil.ReadAll(file)
+	if err != nil {
+		return nil, err
+	}
+
+	parser := newParser(bytes.NewReader(data))
+	parser.filename = filename
+	parser.fs = fs
+	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 && p.fs != nil {
+		parser, err = VirtualFileParser(filename, p.fs)
+	}
+	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
+		} else {
+			return top
+		}
+	}
+}