1
0
mirror of https://github.com/danog/liquid.git synced 2025-01-23 05:01:13 +01:00
liquid/chunks/parser.go

128 lines
3.1 KiB
Go
Raw Normal View History

2017-06-26 09:36:52 -04:00
package chunks
2017-06-25 11:23:20 -04:00
import (
"fmt"
"github.com/osteele/liquid/expressions"
2017-06-25 11:23:20 -04:00
)
2017-06-26 12:41:41 -04:00
// Parse creates an AST from a sequence of Chunks.
func Parse(chunks []Chunk) (ASTNode, error) {
2017-06-25 11:23:20 -04:00
type frame struct {
cd *controlTagDefinition
2017-06-25 11:23:20 -04:00
cn *ASTControlTag
2017-06-26 12:41:41 -04:00
ap *[]ASTNode
2017-06-25 11:23:20 -04:00
}
var (
2017-06-27 13:47:14 -04:00
root = &ASTSeq{}
ap = &root.Children // pointer to current node accumulation slice
ccd *controlTagDefinition
ccn *ASTControlTag
stack []frame // stack of control structures
2017-06-27 17:40:15 -04:00
rawTag *ASTRaw
2017-06-27 13:47:14 -04:00
inComment = false
2017-06-27 17:40:15 -04:00
inRaw = false
2017-06-25 11:23:20 -04:00
)
for _, c := range chunks {
2017-06-27 13:47:14 -04:00
switch {
case inComment:
2017-06-29 12:20:16 -04:00
if c.Type == TagChunkType && c.Name == "endcomment" {
2017-06-27 13:47:14 -04:00
inComment = false
}
2017-06-27 17:40:15 -04:00
case inRaw:
2017-06-29 12:20:16 -04:00
if c.Type == TagChunkType && c.Name == "endraw" {
2017-06-27 17:40:15 -04:00
inComment = false
} else {
rawTag.slices = append(rawTag.slices, c.Source)
}
2017-06-27 13:47:14 -04:00
case c.Type == ObjChunkType:
2017-06-29 12:20:16 -04:00
expr, err := expressions.Parse(c.Parameters)
if err != nil {
return nil, err
}
*ap = append(*ap, &ASTObject{c, expr})
2017-06-27 13:47:14 -04:00
case c.Type == TextChunkType:
2017-06-27 11:19:12 -04:00
*ap = append(*ap, &ASTText{Chunk: c})
2017-06-27 13:47:14 -04:00
case c.Type == TagChunkType:
2017-06-29 12:20:16 -04:00
if cd, ok := findControlTagDefinition(c.Name); ok {
2017-06-25 11:23:20 -04:00
switch {
2017-06-29 12:20:16 -04:00
case c.Name == "comment":
2017-06-27 13:47:14 -04:00
inComment = true
2017-06-29 12:20:16 -04:00
case c.Name == "raw":
2017-06-27 17:40:15 -04:00
inRaw = true
rawTag = &ASTRaw{}
*ap = append(*ap, rawTag)
case cd.requiresParent() && !cd.compatibleParent(ccd):
2017-06-25 11:23:20 -04:00
suffix := ""
if ccd != nil {
suffix = "; immediate parent is " + ccd.name
2017-06-25 11:23:20 -04:00
}
return nil, fmt.Errorf("%s not inside %s%s", cd.name, cd.parent.name, suffix)
case cd.isStartTag():
2017-06-25 11:23:20 -04:00
stack = append(stack, frame{cd: ccd, cn: ccn, ap: ap})
2017-06-27 11:19:12 -04:00
ccd, ccn = cd, &ASTControlTag{Chunk: c, cd: cd}
2017-06-25 11:23:20 -04:00
*ap = append(*ap, ccn)
2017-06-27 11:39:32 -04:00
ap = &ccn.Body
case cd.isBranchTag:
2017-06-27 11:19:12 -04:00
n := &ASTControlTag{Chunk: c, cd: cd}
2017-06-27 11:39:32 -04:00
ccn.Branches = append(ccn.Branches, n)
ap = &n.Body
case cd.isEndTag:
2017-06-25 11:23:20 -04:00
f := stack[len(stack)-1]
stack = stack[:len(stack)-1]
ccd, ccn, ap = f.cd, f.cn, f.ap
2017-06-25 11:23:20 -04:00
}
2017-06-29 12:20:16 -04:00
} else if td, ok := FindTagDefinition(c.Name); ok {
f, err := td(c.Parameters)
2017-06-26 15:36:05 -04:00
if err != nil {
return nil, err
2017-06-25 11:23:20 -04:00
}
*ap = append(*ap, &ASTFunctional{c, f})
2017-06-25 11:23:20 -04:00
} else {
2017-06-29 12:20:16 -04:00
return nil, fmt.Errorf("unknown tag: %s", c.Name)
2017-06-25 11:23:20 -04:00
}
}
}
if ccd != nil {
return nil, fmt.Errorf("unterminated %s tag", ccd.name)
2017-06-25 11:23:20 -04:00
}
2017-06-28 21:45:24 -04:00
if err := evaluateBuilders(root); err != nil {
return nil, err
}
2017-06-25 11:23:20 -04:00
if len(root.Children) == 1 {
return root.Children[0], nil
}
return root, nil
}
2017-06-28 21:45:24 -04:00
func evaluateBuilders(n ASTNode) error {
switch n := n.(type) {
case *ASTControlTag:
for _, child := range n.Body {
if err := evaluateBuilders(child); err != nil {
return err
}
}
for _, branch := range n.Branches {
if err := evaluateBuilders(branch); err != nil {
return err
}
}
2017-06-29 12:20:16 -04:00
cd, ok := findControlTagDefinition(n.Name)
2017-06-28 21:45:24 -04:00
if ok && cd.parser != nil {
renderer, err := cd.parser(*n)
if err != nil {
return err
}
n.renderer = renderer
}
case *ASTSeq:
for _, child := range n.Children {
if error := evaluateBuilders(child); error != nil {
return error
}
}
}
return nil
}