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 } |
