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