1
0
mirror of https://github.com/danog/liquid.git synced 2024-11-27 10:54:45 +01:00
liquid/parser/parser.go

106 lines
3.0 KiB
Go
Raw Normal View History

2017-07-10 15:38:46 +02:00
// Package parser parses template source into an abstract syntax tree.
2017-07-07 11:41:37 +02:00
package parser
2017-06-25 17:23:20 +02:00
import (
"fmt"
2017-07-05 15:51:21 +02:00
"strings"
2017-07-14 02:18:23 +02:00
"github.com/osteele/liquid/expressions"
2017-06-25 17:23:20 +02:00
)
2017-07-06 14:07:53 +02:00
// Parse parses a source template. It returns an AST root, that can be compiled and evaluated.
func (c Config) Parse(source string) (ASTNode, Error) {
2017-07-12 13:19:58 +02:00
tokens := Scan(source, c.SourcePath, c.LineNo)
2017-07-09 17:18:35 +02:00
return c.parseTokens(tokens)
2017-06-30 22:51:39 +02:00
}
2017-07-09 17:18:35 +02:00
// Parse creates an AST from a sequence of tokens.
func (c Config) parseTokens(tokens []Token) (ASTNode, Error) { // nolint: gocyclo
2017-06-30 14:42:11 +02:00
// a stack of control tag state, for matching nested {%if}{%endif%} etc.
2017-06-25 17:23:20 +02:00
type frame struct {
2017-07-05 15:51:21 +02:00
syntax BlockSyntax
node *ASTBlock
ap *[]ASTNode
2017-06-25 17:23:20 +02:00
}
var (
2017-07-07 11:41:37 +02:00
g = c.Grammar
root = &ASTSeq{} // root of AST; will be returned
ap = &root.Children // newly-constructed nodes are appended here
2017-07-05 15:51:21 +02:00
sd BlockSyntax // current block syntax definition
bn *ASTBlock // current block node
stack []frame // stack of blocks
rawTag *ASTRaw // current raw tag
2017-06-27 19:47:14 +02:00
inComment = false
2017-06-27 23:40:15 +02:00
inRaw = false
2017-06-25 17:23:20 +02:00
)
2017-07-09 17:18:35 +02:00
for _, tok := range tokens {
2017-06-27 19:47:14 +02:00
switch {
2017-06-30 14:42:11 +02:00
// The parser needs to know about comment and raw, because tags inside
// needn't match each other e.g. {%comment%}{%if%}{%endcomment%}
// TODO is this true?
2017-06-27 19:47:14 +02:00
case inComment:
2017-07-09 17:18:35 +02:00
if tok.Type == TagTokenType && tok.Name == "endcomment" {
2017-06-27 19:47:14 +02:00
inComment = false
}
2017-06-27 23:40:15 +02:00
case inRaw:
2017-07-09 17:18:35 +02:00
if tok.Type == TagTokenType && tok.Name == "endraw" {
2017-06-30 14:45:22 +02:00
inRaw = false
2017-06-27 23:40:15 +02:00
} else {
2017-07-09 17:18:35 +02:00
rawTag.Slices = append(rawTag.Slices, tok.Source)
2017-06-27 23:40:15 +02:00
}
2017-07-09 17:18:35 +02:00
case tok.Type == ObjTokenType:
2017-07-14 02:18:23 +02:00
expr, err := expressions.Parse(tok.Args)
if err != nil {
return nil, WrapError(err, tok)
}
2017-07-09 17:18:35 +02:00
*ap = append(*ap, &ASTObject{tok, expr})
case tok.Type == TextTokenType:
*ap = append(*ap, &ASTText{Token: tok})
case tok.Type == TagTokenType:
if cs, ok := g.BlockSyntax(tok.Name); ok {
2017-06-25 17:23:20 +02:00
switch {
2017-07-09 17:18:35 +02:00
case tok.Name == "comment":
2017-06-27 19:47:14 +02:00
inComment = true
2017-07-09 17:18:35 +02:00
case tok.Name == "raw":
2017-06-27 23:40:15 +02:00
inRaw = true
rawTag = &ASTRaw{}
*ap = append(*ap, rawTag)
2017-07-05 15:51:21 +02:00
case cs.RequiresParent() && (sd == nil || !cs.CanHaveParent(sd)):
2017-06-25 17:23:20 +02:00
suffix := ""
2017-07-05 15:51:21 +02:00
if sd != nil {
suffix = "; immediate parent is " + sd.TagName()
2017-06-25 17:23:20 +02:00
}
return nil, Errorf(tok, "%s not inside %s%s", tok.Name, strings.Join(cs.ParentTags(), " or "), suffix)
2017-07-05 15:51:21 +02:00
case cs.IsBlockStart():
push := func() {
stack = append(stack, frame{syntax: sd, node: bn, ap: ap})
2017-07-09 17:18:35 +02:00
sd, bn = cs, &ASTBlock{Token: tok, syntax: cs}
2017-07-05 15:51:21 +02:00
*ap = append(*ap, bn)
}
push()
ap = &bn.Body
case cs.IsClause():
2017-07-09 17:18:35 +02:00
n := &ASTBlock{Token: tok, syntax: cs}
bn.Clauses = append(bn.Clauses, n)
2017-06-27 17:39:32 +02:00
ap = &n.Body
2017-07-05 15:51:21 +02:00
case cs.IsBlockEnd():
pop := func() {
f := stack[len(stack)-1]
stack = stack[:len(stack)-1]
sd, bn, ap = f.syntax, f.node, f.ap
}
pop()
default:
2017-07-09 17:18:35 +02:00
panic(fmt.Errorf("block type %q", tok.Name))
2017-06-25 17:23:20 +02:00
}
} else {
2017-07-09 17:18:35 +02:00
*ap = append(*ap, &ASTTag{tok})
2017-06-25 17:23:20 +02:00
}
}
}
2017-07-05 15:51:21 +02:00
if bn != nil {
return nil, Errorf(bn, "unterminated %s block", bn.Name)
2017-06-25 17:23:20 +02:00
}
return root, nil
}