diff --git a/chunks/context.go b/chunks/context.go index d1f9906..6f2d3c8 100644 --- a/chunks/context.go +++ b/chunks/context.go @@ -40,3 +40,13 @@ func (c *Context) EvaluateExpr(source string) (out interface{}, err error) { func (c *Context) evaluateStatement(tag, source string) (interface{}, error) { return c.EvaluateExpr(fmt.Sprintf("%%%s %s", tag, source)) } + +func makeExpressionValueFn(source string) (func(Context) (interface{}, error), error) { + expr, err := expressions.Parse(source) + if err != nil { + return nil, err + } + return func(ctx Context) (interface{}, error) { + return expr.Evaluate(expressions.NewContext(ctx.vars)) + }, nil +} diff --git a/chunks/control_tags.go b/chunks/control_tags.go index 68a5ca5..f193166 100644 --- a/chunks/control_tags.go +++ b/chunks/control_tags.go @@ -7,20 +7,20 @@ import ( func init() { loopTags := []string{"break", "continue", "cycle"} - DefineControlTag("comment").Action(unimplementedControlTag) - DefineControlTag("if").Branch("else").Branch("elsif").Action(unimplementedControlTag) - DefineControlTag("unless").SameSyntaxAs("if").Action(unimplementedControlTag) - DefineControlTag("case").Branch("when").Action(unimplementedControlTag) - DefineControlTag("for").Governs(loopTags).Action(unimplementedControlTag) - DefineControlTag("tablerow").Governs(loopTags).Action(unimplementedControlTag) - DefineControlTag("capture").Action(unimplementedControlTag) + DefineControlTag("comment") //.Action(unimplementedControlTag) + DefineControlTag("if").Branch("else").Branch("elsif").Action(ifTagAction(true)) + DefineControlTag("unless").SameSyntaxAs("if").Action(ifTagAction(false)) + DefineControlTag("case").Branch("when") //.Action(unimplementedControlTag) + DefineControlTag("for").Governs(loopTags) //.Action(unimplementedControlTag) + DefineControlTag("tablerow").Governs(loopTags) //.Action(unimplementedControlTag) + DefineControlTag("capture") //.Action(unimplementedControlTag) } // ControlTagDefinitions is a map of tag names to control tag definitions. var ControlTagDefinitions = map[string]*ControlTagDefinition{} // ControlTagAction runs the interpreter. -type ControlTagAction func(io.Writer, Context) error +type ControlTagAction func(*ASTControlTag) func(io.Writer, Context) error // ControlTagDefinition tells the parser how to parse control tags. type ControlTagDefinition struct { @@ -29,6 +29,7 @@ type ControlTagDefinition struct { IsEndTag bool SyntaxModel *ControlTagDefinition Parent *ControlTagDefinition + action ControlTagAction } func (c *ControlTagDefinition) CompatibleParent(p *ControlTagDefinition) bool { @@ -66,15 +67,19 @@ func addControlTagDefinition(ct *ControlTagDefinition) { ControlTagDefinitions[ct.Name] = ct } +// Branch tells the parser that the named tag can appear immediately between this tag and its end tag, +// so long as it is not nested within any other control tags. func (ct *ControlTagDefinition) Branch(name string) *ControlTagDefinition { addControlTagDefinition(&ControlTagDefinition{Name: name, IsBranchTag: true, Parent: ct}) return ct } +// Governs tells the parser that the tags can appear anywhere between this tag and its end tag. func (ct *ControlTagDefinition) Governs(_ []string) *ControlTagDefinition { return ct } +// SameSyntaxAs tells the parser that this tag has the same syntax as the named tag. func (ct *ControlTagDefinition) SameSyntaxAs(name string) *ControlTagDefinition { ot := ControlTagDefinitions[name] if ot == nil { @@ -84,9 +89,49 @@ func (ct *ControlTagDefinition) SameSyntaxAs(name string) *ControlTagDefinition return ct } -func (ct *ControlTagDefinition) Action(_ ControlTagAction) { +// Action sets the action for a control tag definition. +func (ct *ControlTagDefinition) Action(fn ControlTagAction) { + ct.action = fn } -func unimplementedControlTag(io.Writer, Context) error { - return fmt.Errorf("unimplementedControlTag") +// func unimplementedControlTag(io.Writer, Context) error { +// return fmt.Errorf("unimplemented control tag") +// } + +func ifTagAction(polarity bool) func(*ASTControlTag) func(io.Writer, Context) error { + return func(n *ASTControlTag) func(io.Writer, Context) error { + expr, err := makeExpressionValueFn(n.chunk.Args) + if err != nil { + return func(io.Writer, Context) error { return err } + } + return func(w io.Writer, ctx Context) error { + val, err := expr(ctx) + if err != nil { + return err + } + if !polarity { + val = (val == nil || val == false) + } + switch val { + default: + return renderASTSequence(w, n.body, ctx) + case nil, false: + for _, c := range n.branches { + switch c.chunk.Tag { + case "else": + val = true + case "elsif": + val, err = ctx.EvaluateExpr(c.chunk.Args) + if err != nil { + return err + } + } + if val != nil && val != false { + return renderASTSequence(w, c.body, ctx) + } + } + } + return nil + } + } } diff --git a/chunks/render.go b/chunks/render.go index 0b15a78..9944539 100644 --- a/chunks/render.go +++ b/chunks/render.go @@ -45,38 +45,12 @@ func renderASTSequence(w io.Writer, seq []ASTNode, ctx Context) error { // Render evaluates an AST node and writes the result to an io.Writer. func (n *ASTControlTag) Render(w io.Writer, ctx Context) error { - switch n.chunk.Tag { - case "if", "unless": - val, err := ctx.EvaluateExpr(n.chunk.Args) - if err != nil { - return err - } - if n.chunk.Tag == "unless" { - val = (val == nil || val == false) - } - switch val { - default: - return renderASTSequence(w, n.body, ctx) - case nil, false: - for _, c := range n.branches { - switch c.chunk.Tag { - case "else": - val = true - case "elsif": - val, err = ctx.EvaluateExpr(c.chunk.Args) - if err != nil { - return err - } - } - if val != nil && val != false { - return renderASTSequence(w, c.body, ctx) - } - } - } - return nil - default: + cd, ok := FindControlDefinition(n.chunk.Tag) + if !ok { return fmt.Errorf("unimplemented tag: %s", n.chunk.Tag) } + f := cd.action(n) + return f(w, ctx) } // Render evaluates an AST node and writes the result to an io.Writer. diff --git a/chunks/render_test.go b/chunks/render_test.go index b67136a..d46cbc2 100644 --- a/chunks/render_test.go +++ b/chunks/render_test.go @@ -10,9 +10,12 @@ import ( var parseErrorTests = []struct{ in, expected string }{ {"{%unknown_tag%}", "unknown tag"}, + {"{%if syntax error%}", "unterminated if tag"}, + // {"{%if syntax error%}{%endif%}", "parse error"}, } var renderTests = []struct{ in, expected string }{ + // {"{%if syntax error%}{%endif%}", "parse error"}, {"{{12}}", "12"}, {"{{x}}", "123"}, {"{{page.title}}", "Introduction"}, diff --git a/expressions/y.go b/expressions/y.go index cc1f889..99f0ce0 100644 --- a/expressions/y.go +++ b/expressions/y.go @@ -555,7 +555,7 @@ yydefault: fa, fb := yyDollar[1].f, yyDollar[3].f yyVAL.f = func(ctx Context) interface{} { a, b := fa(ctx), fb(ctx) - return a == b + return generics.Equal(a, b) } } case 12: