You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

455 lines
9.7 KiB

  1. package parser
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "path/filepath"
  8. "strings"
  9. )
  10. type Parser struct {
  11. scanner *scanner
  12. filename string
  13. currenttoken *token
  14. namedBlocks map[string]*NamedBlock
  15. parent *Parser
  16. result *Block
  17. }
  18. func newParser(rdr io.Reader) *Parser {
  19. p := new(Parser)
  20. p.scanner = newScanner(rdr)
  21. p.namedBlocks = make(map[string]*NamedBlock)
  22. return p
  23. }
  24. func StringParser(input string) (*Parser, error) {
  25. return newParser(bytes.NewReader([]byte(input))), nil
  26. }
  27. func ByteParser(input []byte) (*Parser, error) {
  28. return newParser(bytes.NewReader(input)), nil
  29. }
  30. func (p *Parser) SetFilename(filename string) {
  31. p.filename = filename
  32. }
  33. func FileParser(filename string) (*Parser, error) {
  34. data, err := ioutil.ReadFile(filename)
  35. if err != nil {
  36. return nil, err
  37. }
  38. parser := newParser(bytes.NewReader(data))
  39. parser.filename = filename
  40. return parser, nil
  41. }
  42. func (p *Parser) Parse() *Block {
  43. if p.result != nil {
  44. return p.result
  45. }
  46. defer func() {
  47. if r := recover(); r != nil {
  48. if rs, ok := r.(string); ok && rs[:len("Amber Error")] == "Amber Error" {
  49. panic(r)
  50. }
  51. pos := p.pos()
  52. if len(pos.Filename) > 0 {
  53. panic(fmt.Sprintf("Amber Error in <%s>: %v - Line: %d, Column: %d, Length: %d", pos.Filename, r, pos.LineNum, pos.ColNum, pos.TokenLength))
  54. } else {
  55. panic(fmt.Sprintf("Amber Error: %v - Line: %d, Column: %d, Length: %d", r, pos.LineNum, pos.ColNum, pos.TokenLength))
  56. }
  57. }
  58. }()
  59. block := newBlock()
  60. p.advance()
  61. for {
  62. if p.currenttoken == nil || p.currenttoken.Kind == tokEOF {
  63. break
  64. }
  65. if p.currenttoken.Kind == tokBlank {
  66. p.advance()
  67. continue
  68. }
  69. block.push(p.parse())
  70. }
  71. if p.parent != nil {
  72. p.parent.Parse()
  73. for _, prev := range p.parent.namedBlocks {
  74. ours := p.namedBlocks[prev.Name]
  75. if ours == nil {
  76. // Put a copy of the named block into current context, so that sub-templates can use the block
  77. p.namedBlocks[prev.Name] = prev
  78. continue
  79. }
  80. top := findTopmostParentWithNamedBlock(p, prev.Name)
  81. nb := top.namedBlocks[prev.Name]
  82. switch ours.Modifier {
  83. case NamedBlockAppend:
  84. for i := 0; i < len(ours.Children); i++ {
  85. nb.push(ours.Children[i])
  86. }
  87. case NamedBlockPrepend:
  88. for i := len(ours.Children) - 1; i >= 0; i-- {
  89. nb.pushFront(ours.Children[i])
  90. }
  91. default:
  92. nb.Children = ours.Children
  93. }
  94. }
  95. block = p.parent.result
  96. }
  97. p.result = block
  98. return block
  99. }
  100. func (p *Parser) pos() SourcePosition {
  101. pos := p.scanner.Pos()
  102. pos.Filename = p.filename
  103. return pos
  104. }
  105. func (p *Parser) parseRelativeFile(filename string) *Parser {
  106. if len(p.filename) == 0 {
  107. panic("Unable to import or extend " + filename + " in a non filesystem based parser.")
  108. }
  109. filename = filepath.Join(filepath.Dir(p.filename), filename)
  110. if strings.IndexRune(filepath.Base(filename), '.') < 0 {
  111. filename = filename + ".amber"
  112. }
  113. parser, err := FileParser(filename)
  114. if err != nil {
  115. panic("Unable to read " + filename + ", Error: " + string(err.Error()))
  116. }
  117. return parser
  118. }
  119. func (p *Parser) parse() Node {
  120. switch p.currenttoken.Kind {
  121. case tokDoctype:
  122. return p.parseDoctype()
  123. case tokComment:
  124. return p.parseComment()
  125. case tokText:
  126. return p.parseText()
  127. case tokIf:
  128. return p.parseIf()
  129. case tokEach:
  130. return p.parseEach()
  131. case tokImport:
  132. return p.parseImport()
  133. case tokTag:
  134. return p.parseTag()
  135. case tokAssignment:
  136. return p.parseAssignment()
  137. case tokNamedBlock:
  138. return p.parseNamedBlock()
  139. case tokExtends:
  140. return p.parseExtends()
  141. case tokIndent:
  142. return p.parseBlock(nil)
  143. case tokMixin:
  144. return p.parseMixin()
  145. case tokMixinCall:
  146. return p.parseMixinCall()
  147. }
  148. panic(fmt.Sprintf("Unexpected token: %d", p.currenttoken.Kind))
  149. }
  150. func (p *Parser) expect(typ rune) *token {
  151. if p.currenttoken.Kind != typ {
  152. panic("Unexpected token!")
  153. }
  154. curtok := p.currenttoken
  155. p.advance()
  156. return curtok
  157. }
  158. func (p *Parser) advance() {
  159. p.currenttoken = p.scanner.Next()
  160. }
  161. func (p *Parser) parseExtends() *Block {
  162. if p.parent != nil {
  163. panic("Unable to extend multiple parent templates.")
  164. }
  165. tok := p.expect(tokExtends)
  166. parser := p.parseRelativeFile(tok.Value)
  167. parser.Parse()
  168. p.parent = parser
  169. return newBlock()
  170. }
  171. func (p *Parser) parseBlock(parent Node) *Block {
  172. p.expect(tokIndent)
  173. block := newBlock()
  174. block.SourcePosition = p.pos()
  175. for {
  176. if p.currenttoken == nil || p.currenttoken.Kind == tokEOF || p.currenttoken.Kind == tokOutdent {
  177. break
  178. }
  179. if p.currenttoken.Kind == tokBlank {
  180. p.advance()
  181. continue
  182. }
  183. if p.currenttoken.Kind == tokId ||
  184. p.currenttoken.Kind == tokClassName ||
  185. p.currenttoken.Kind == tokAttribute {
  186. if tag, ok := parent.(*Tag); ok {
  187. attr := p.expect(p.currenttoken.Kind)
  188. cond := attr.Data["Condition"]
  189. switch attr.Kind {
  190. case tokId:
  191. tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "id", attr.Value, true, cond})
  192. case tokClassName:
  193. tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "class", attr.Value, true, cond})
  194. case tokAttribute:
  195. tag.Attributes = append(tag.Attributes, Attribute{p.pos(), attr.Value, attr.Data["Content"], attr.Data["Mode"] == "raw", cond})
  196. }
  197. continue
  198. } else {
  199. panic("Conditional attributes must be placed immediately within a parent tag.")
  200. }
  201. }
  202. block.push(p.parse())
  203. }
  204. p.expect(tokOutdent)
  205. return block
  206. }
  207. func (p *Parser) parseIf() *Condition {
  208. tok := p.expect(tokIf)
  209. cnd := newCondition(tok.Value)
  210. cnd.SourcePosition = p.pos()
  211. readmore:
  212. switch p.currenttoken.Kind {
  213. case tokIndent:
  214. cnd.Positive = p.parseBlock(cnd)
  215. goto readmore
  216. case tokElse:
  217. p.expect(tokElse)
  218. if p.currenttoken.Kind == tokIf {
  219. cnd.Negative = newBlock()
  220. cnd.Negative.push(p.parseIf())
  221. } else if p.currenttoken.Kind == tokIndent {
  222. cnd.Negative = p.parseBlock(cnd)
  223. } else {
  224. panic("Unexpected token!")
  225. }
  226. goto readmore
  227. }
  228. return cnd
  229. }
  230. func (p *Parser) parseEach() *Each {
  231. tok := p.expect(tokEach)
  232. ech := newEach(tok.Value)
  233. ech.SourcePosition = p.pos()
  234. ech.X = tok.Data["X"]
  235. ech.Y = tok.Data["Y"]
  236. if p.currenttoken.Kind == tokIndent {
  237. ech.Block = p.parseBlock(ech)
  238. }
  239. return ech
  240. }
  241. func (p *Parser) parseImport() *Block {
  242. tok := p.expect(tokImport)
  243. node := p.parseRelativeFile(tok.Value).Parse()
  244. node.SourcePosition = p.pos()
  245. return node
  246. }
  247. func (p *Parser) parseNamedBlock() *Block {
  248. tok := p.expect(tokNamedBlock)
  249. if p.namedBlocks[tok.Value] != nil {
  250. panic("Multiple definitions of named blocks are not permitted. Block " + tok.Value + " has been re defined.")
  251. }
  252. block := newNamedBlock(tok.Value)
  253. block.SourcePosition = p.pos()
  254. if tok.Data["Modifier"] == "append" {
  255. block.Modifier = NamedBlockAppend
  256. } else if tok.Data["Modifier"] == "prepend" {
  257. block.Modifier = NamedBlockPrepend
  258. }
  259. if p.currenttoken.Kind == tokIndent {
  260. block.Block = *(p.parseBlock(nil))
  261. }
  262. p.namedBlocks[block.Name] = block
  263. if block.Modifier == NamedBlockDefault {
  264. return &block.Block
  265. }
  266. return newBlock()
  267. }
  268. func (p *Parser) parseDoctype() *Doctype {
  269. tok := p.expect(tokDoctype)
  270. node := newDoctype(tok.Value)
  271. node.SourcePosition = p.pos()
  272. return node
  273. }
  274. func (p *Parser) parseComment() *Comment {
  275. tok := p.expect(tokComment)
  276. cmnt := newComment(tok.Value)
  277. cmnt.SourcePosition = p.pos()
  278. cmnt.Silent = tok.Data["Mode"] == "silent"
  279. if p.currenttoken.Kind == tokIndent {
  280. cmnt.Block = p.parseBlock(cmnt)
  281. }
  282. return cmnt
  283. }
  284. func (p *Parser) parseText() *Text {
  285. tok := p.expect(tokText)
  286. node := newText(tok.Value, tok.Data["Mode"] == "raw")
  287. node.SourcePosition = p.pos()
  288. return node
  289. }
  290. func (p *Parser) parseAssignment() *Assignment {
  291. tok := p.expect(tokAssignment)
  292. node := newAssignment(tok.Data["X"], tok.Value)
  293. node.SourcePosition = p.pos()
  294. return node
  295. }
  296. func (p *Parser) parseTag() *Tag {
  297. tok := p.expect(tokTag)
  298. tag := newTag(tok.Value)
  299. tag.SourcePosition = p.pos()
  300. ensureBlock := func() {
  301. if tag.Block == nil {
  302. tag.Block = newBlock()
  303. }
  304. }
  305. readmore:
  306. switch p.currenttoken.Kind {
  307. case tokIndent:
  308. if tag.IsRawText() {
  309. p.scanner.readRaw = true
  310. }
  311. block := p.parseBlock(tag)
  312. if tag.Block == nil {
  313. tag.Block = block
  314. } else {
  315. for _, c := range block.Children {
  316. tag.Block.push(c)
  317. }
  318. }
  319. case tokId:
  320. id := p.expect(tokId)
  321. if len(id.Data["Condition"]) > 0 {
  322. panic("Conditional attributes must be placed in a block within a tag.")
  323. }
  324. tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "id", id.Value, true, ""})
  325. goto readmore
  326. case tokClassName:
  327. cls := p.expect(tokClassName)
  328. if len(cls.Data["Condition"]) > 0 {
  329. panic("Conditional attributes must be placed in a block within a tag.")
  330. }
  331. tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "class", cls.Value, true, ""})
  332. goto readmore
  333. case tokAttribute:
  334. attr := p.expect(tokAttribute)
  335. if len(attr.Data["Condition"]) > 0 {
  336. panic("Conditional attributes must be placed in a block within a tag.")
  337. }
  338. tag.Attributes = append(tag.Attributes, Attribute{p.pos(), attr.Value, attr.Data["Content"], attr.Data["Mode"] == "raw", ""})
  339. goto readmore
  340. case tokText:
  341. if p.currenttoken.Data["Mode"] != "piped" {
  342. ensureBlock()
  343. tag.Block.pushFront(p.parseText())
  344. goto readmore
  345. }
  346. }
  347. return tag
  348. }
  349. func (p *Parser) parseMixin() *Mixin {
  350. tok := p.expect(tokMixin)
  351. mixin := newMixin(tok.Value, tok.Data["Args"])
  352. mixin.SourcePosition = p.pos()
  353. if p.currenttoken.Kind == tokIndent {
  354. mixin.Block = p.parseBlock(mixin)
  355. }
  356. return mixin
  357. }
  358. func (p *Parser) parseMixinCall() *MixinCall {
  359. tok := p.expect(tokMixinCall)
  360. mixinCall := newMixinCall(tok.Value, tok.Data["Args"])
  361. mixinCall.SourcePosition = p.pos()
  362. return mixinCall
  363. }
  364. func findTopmostParentWithNamedBlock(p *Parser, name string) *Parser {
  365. top := p
  366. for {
  367. if top.namedBlocks[name] == nil {
  368. return nil
  369. }
  370. if top.parent == nil {
  371. return top
  372. }
  373. if top.parent.namedBlocks[name] != nil {
  374. top = top.parent
  375. }
  376. }
  377. }