diff --git a/chunks/ast.go b/chunks/ast.go index 999b232..6de8d53 100644 --- a/chunks/ast.go +++ b/chunks/ast.go @@ -47,6 +47,7 @@ type ASTObject struct { // ASTControlTag is a control tag. type ASTControlTag struct { Chunk + renderer func(io.Writer, Context) error cd *controlTagDefinition Body []ASTNode Branches []*ASTControlTag diff --git a/chunks/parser.go b/chunks/parser.go index 3bb6ea9..0299355 100644 --- a/chunks/parser.go +++ b/chunks/parser.go @@ -68,6 +68,13 @@ func Parse(chunks []Chunk) (ASTNode, error) { ccn.Branches = append(ccn.Branches, n) ap = &n.Body case cd.isEndTag: + // if ccn != nil && cd.parser != nil { + // renderer, err := cd.parser(*ccn) + // if err != nil { + // return nil, err + // } + // ccn.renderer = renderer + // } f := stack[len(stack)-1] stack = stack[:len(stack)-1] ccd, ccn, ap = f.cd, f.cn, f.ap @@ -96,8 +103,42 @@ func Parse(chunks []Chunk) (ASTNode, error) { if ccd != nil { return nil, fmt.Errorf("unterminated %s tag", ccd.name) } + if err := evaluateBuilders(root); err != nil { + return nil, err + } if len(root.Children) == 1 { return root.Children[0], nil } return root, nil } + +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 +} diff --git a/chunks/parser_test.go b/chunks/parser_test.go index 1cd0f60..b404139 100644 --- a/chunks/parser_test.go +++ b/chunks/parser_test.go @@ -18,7 +18,7 @@ func init() { var parseErrorTests = []struct{ in, expected string }{ {"{%unknown_tag%}", "unknown tag"}, {"{%if test%}", "unterminated if tag"}, - // {"{%if syntax error%}{%endif%}", "parse error"}, + // {"{%for syntax error%}{%endfor%}", "parse error"}, } var parserTests = []struct{ in string }{ @@ -30,7 +30,7 @@ var parserTests = []struct{ in string }{ func TestParseErrors(t *testing.T) { for i, test := range parseErrorTests { - t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { + t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) { tokens := Scan(test.in, "") ast, err := Parse(tokens) require.Nilf(t, ast, test.in) @@ -42,7 +42,7 @@ func TestParseErrors(t *testing.T) { func TestParser(t *testing.T) { for i, test := range parserTests { - t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { + t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) { tokens := Scan(test.in, "") _, err := Parse(tokens) require.NoError(t, err, test.in) diff --git a/chunks/render.go b/chunks/render.go index 538bac9..522b16f 100644 --- a/chunks/render.go +++ b/chunks/render.go @@ -56,11 +56,12 @@ func (n *ASTControlTag) Render(w io.Writer, ctx Context) error { if !ok || cd.parser == nil { return fmt.Errorf("unimplemented tag: %s", n.Tag) } - f, err := cd.parser(*n) - if err != nil { - return err + renderer := n.renderer + if renderer == nil { + panic(fmt.Errorf("unset renderer for %v", n)) + return nil } - return f(w, ctx) + return renderer(w, ctx) } // Render evaluates an AST node and writes the result to an io.Writer.