1
0
mirror of https://github.com/danog/liquid.git synced 2025-01-22 23:21:15 +01:00

Control tags actions are table-driven

This commit is contained in:
Oliver Steele 2017-06-27 11:12:58 -04:00
parent f52d00f836
commit 20e4df31d3
5 changed files with 74 additions and 42 deletions

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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.

View File

@ -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"},

View File

@ -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: