1
0
mirror of https://github.com/danog/liquid.git synced 2024-11-27 01:24:40 +01:00

Cycle uses Statement; steps towards cycle groups

This commit is contained in:
Oliver Steele 2017-07-13 11:07:45 -04:00
parent 110fee61c6
commit 74441188c7
8 changed files with 181 additions and 142 deletions

View File

@ -16,19 +16,25 @@ func init() {
name string
val interface{}
f func(Context) interface{}
arglist []func(Context) interface{}
s string
ss []string
cycle Cycle
cyclefn func(string) Cycle
loop Loop
loopmods loopModifiers
filter_params []valueFn
}
%type <f> expr rel filtered cond
%type<filter_params> filter_params
%type<cycle> cycle
%type<cyclefn> cycle2
%type<ss> cycle3
%type<loop> loop
%type<loopmods> loop_modifiers
%type<arglist> arglist moreargs
%type<s> string
%token <val> LITERAL
%token <name> IDENTIFIER KEYWORD PROPERTY
%token ARGLIST ASSIGN LOOP
%token ASSIGN CYCLE LOOP
%token EQ NEQ GE LE IN AND OR CONTAINS
%left '.' '|'
%left '<' '>'
@ -38,26 +44,36 @@ start:
| ASSIGN IDENTIFIER '=' filtered ';' {
yylex.(*lexer).Assignment = Assignment{$2, &expression{$4}}
}
| ARGLIST arglist ';' {
args := $2
yylex.(*lexer).val = func(ctx Context) interface{} {
result := make([]interface{}, len(args))
for i, fn := range args {
result[i] = fn(ctx)
}
return result
}
}
| CYCLE cycle ';' { yylex.(*lexer).Cycle = $2 }
| LOOP loop ';' { yylex.(*lexer).Loop = $2 }
;
arglist:
expr moreargs { $$ = append([]func(Context) interface{}{$1}, $2...) };
cycle: string cycle2 { $$ = $2($1) };
moreargs : /* empty */ { $$ = []func(Context) interface{}{}}
| ',' expr moreargs { $$ = append([]func(Context) interface{}{$2}, $3...) }
cycle2:
':' string cycle3 {
h, t := $2, $3
$$ = func(g string) Cycle { return Cycle{g, append([]string{h}, t...)} }
}
| cycle3 {
vals := $1
$$ = func(h string) Cycle { return Cycle{Values: append([]string{h}, vals...)} }
}
;
cycle3:
/* empty */ { $$ = []string{} }
| ',' string cycle3 { $$ = append([]string{$2}, $3...) }
;
string: LITERAL {
s, ok := $1.(string)
if !ok {
panic(ParseError(fmt.Sprintf("expected a string for %q", $1)))
}
$$ = s
};
loop: IDENTIFIER IN filtered loop_modifiers {
name, expr, mods := $1, $3, $4
$$ = Loop{name, &expression{expr}, mods}

View File

@ -8,6 +8,7 @@ import "fmt"
type parseValue struct {
Assignment
Cycle
Loop
val func(Context) interface{}
}

View File

@ -365,7 +365,7 @@ func (lex *lexer) Lex(out *yySymType) int {
//line scanner.rl:84
lex.te = (lex.p) + 1
{
tok = ARGLIST
tok = CYCLE
(lex.p)++
goto _out
}

View File

@ -81,7 +81,7 @@ func (lex *lexer) Lex(out *yySymType) int {
main := |*
# statement selectors, should match constants in parser.go
"%assign " => { tok = ASSIGN; fbreak; };
"{%cycle " => { tok = ARGLIST; fbreak; };
"{%cycle " => { tok = CYCLE; fbreak; };
"%loop " => { tok = LOOP; fbreak; };
# literals

View File

@ -13,13 +13,19 @@ type Statement struct{ parseValue }
// Expression returns a statement's expression function.
func (s *Statement) Expression() Expression { return &expression{s.val} }
// Assignment captures the parse of an {% assign %} statement
// An Assignment is a parse of an {% assign %} statement
type Assignment struct {
Variable string
ValueFn Expression
}
// Loop captures the parse of a {% loop %} statement
// A Cycle is a parse of an {% assign %} statement
type Cycle struct {
Group string
Values []string
}
// An Loop is a parse of a {% loop %} statement
type Loop struct {
Variable string
Expr Expression

View File

@ -21,7 +21,10 @@ type yySymType struct {
name string
val interface{}
f func(Context) interface{}
arglist []func(Context) interface{}
s string
ss []string
cycle Cycle
cyclefn func(string) Cycle
loop Loop
loopmods loopModifiers
filter_params []valueFn
@ -31,8 +34,8 @@ const LITERAL = 57346
const IDENTIFIER = 57347
const KEYWORD = 57348
const PROPERTY = 57349
const ARGLIST = 57350
const ASSIGN = 57351
const ASSIGN = 57350
const CYCLE = 57351
const LOOP = 57352
const EQ = 57353
const NEQ = 57354
@ -51,8 +54,8 @@ var yyToknames = [...]string{
"IDENTIFIER",
"KEYWORD",
"PROPERTY",
"ARGLIST",
"ASSIGN",
"CYCLE",
"LOOP",
"EQ",
"NEQ",
@ -68,6 +71,7 @@ var yyToknames = [...]string{
"'>'",
"';'",
"'='",
"':'",
"','",
"'['",
"']'",
@ -89,67 +93,68 @@ var yyExca = [...]int{
const yyPrivate = 57344
const yyLast = 77
const yyLast = 81
var yyAct = [...]int{
8, 35, 21, 21, 7, 17, 23, 24, 27, 28,
60, 9, 10, 29, 33, 37, 26, 25, 13, 14,
34, 22, 22, 41, 42, 43, 44, 45, 46, 47,
48, 49, 21, 20, 51, 11, 57, 52, 50, 51,
21, 55, 20, 53, 9, 10, 13, 14, 4, 3,
5, 22, 56, 12, 58, 6, 38, 19, 36, 22,
2, 63, 61, 62, 39, 40, 15, 64, 11, 31,
32, 1, 30, 16, 59, 18, 54,
8, 38, 22, 7, 13, 14, 37, 39, 17, 9,
10, 9, 10, 3, 4, 5, 65, 22, 52, 39,
34, 40, 23, 60, 44, 45, 46, 47, 48, 49,
50, 51, 13, 14, 11, 54, 11, 23, 53, 12,
21, 35, 54, 61, 59, 57, 55, 21, 56, 22,
41, 66, 67, 24, 25, 28, 29, 62, 63, 6,
30, 42, 43, 27, 26, 2, 68, 20, 15, 23,
69, 18, 1, 32, 33, 64, 19, 31, 36, 16,
58,
}
var yyPact = [...]int{
40, -1000, 30, 61, 7, 52, -1000, 22, -5, -1000,
-1000, 7, -1000, 7, 7, -10, -3, 33, -8, 41,
59, -1000, 7, 7, 7, 7, 7, 7, 7, 7,
2, -1000, -1000, 7, -1000, -1000, 7, -1000, 7, -1000,
7, 25, -4, -4, -4, -4, -4, -4, -4, -1000,
13, -4, 33, 22, -15, -4, -1000, -1000, -1000, 57,
7, -1000, 63, -4, -1000,
5, -1000, 16, 63, 67, 62, -1000, 27, 42, -1000,
-1000, 7, -1000, 7, 7, -4, 18, -19, -1000, -2,
35, 56, -1000, 7, 7, 7, 7, 7, 7, 7,
7, -12, -1000, -1000, 7, -1000, -1000, 67, -1000, 67,
-1000, 7, -1000, 7, -5, 10, 10, 10, 10, 10,
10, 10, -1000, 20, 10, -7, -7, 27, -10, 10,
-1000, -1000, -1000, -1000, 46, 7, -1000, 66, 10, -1000,
}
var yyPgo = [...]int{
0, 0, 55, 4, 60, 76, 75, 74, 73, 1,
71,
0, 0, 59, 3, 65, 80, 79, 78, 1, 76,
75, 8, 72,
}
var yyR1 = [...]int{
0, 10, 10, 10, 10, 8, 9, 9, 6, 7,
7, 7, 1, 1, 1, 1, 1, 3, 3, 3,
5, 5, 2, 2, 2, 2, 2, 2, 2, 2,
4, 4, 4,
0, 12, 12, 12, 12, 6, 7, 7, 8, 8,
11, 9, 10, 10, 10, 1, 1, 1, 1, 1,
3, 3, 3, 5, 5, 2, 2, 2, 2, 2,
2, 2, 2, 4, 4, 4,
}
var yyR2 = [...]int{
0, 2, 5, 3, 3, 2, 0, 3, 4, 0,
2, 3, 1, 1, 2, 4, 3, 1, 3, 4,
1, 3, 1, 3, 3, 3, 3, 3, 3, 3,
1, 3, 3,
0, 2, 5, 3, 3, 2, 3, 1, 0, 3,
1, 4, 0, 2, 3, 1, 1, 2, 4, 3,
1, 3, 4, 1, 3, 1, 3, 3, 3, 3,
3, 3, 3, 1, 3, 3,
}
var yyChk = [...]int{
-1000, -10, -4, 9, 8, 10, -2, -3, -1, 4,
5, 28, 23, 16, 17, 5, -8, -1, -6, 5,
20, 7, 26, 11, 12, 22, 21, 13, 14, 18,
-4, -2, -2, 24, 23, -9, 25, 23, 15, 5,
6, -1, -1, -1, -1, -1, -1, -1, -1, 29,
-3, -1, -1, -3, -5, -1, 27, 23, -9, -7,
25, 5, 6, -1, 4,
-1000, -12, -4, 8, 9, 10, -2, -3, -1, 4,
5, 29, 23, 16, 17, 5, -6, -11, 4, -9,
5, 20, 7, 27, 11, 12, 22, 21, 13, 14,
18, -4, -2, -2, 24, 23, -7, 25, -8, 26,
23, 15, 5, 6, -1, -1, -1, -1, -1, -1,
-1, -1, 30, -3, -1, -11, -11, -3, -5, -1,
28, 23, -8, -8, -10, 26, 5, 6, -1, 4,
}
var yyDef = [...]int{
0, -2, 0, 0, 0, 0, 30, 22, 17, 12,
13, 0, 1, 0, 0, 0, 0, 6, 0, 0,
0, 14, 0, 0, 0, 0, 0, 0, 0, 0,
0, 31, 32, 0, 3, 5, 0, 4, 0, 18,
0, 0, 23, 24, 25, 26, 27, 28, 29, 16,
0, 17, 6, 9, 19, 20, 15, 2, 7, 8,
0, 10, 0, 21, 11,
0, -2, 0, 0, 0, 0, 33, 25, 20, 15,
16, 0, 1, 0, 0, 0, 0, 8, 10, 0,
0, 0, 17, 0, 0, 0, 0, 0, 0, 0,
0, 0, 34, 35, 0, 3, 5, 0, 7, 0,
4, 0, 21, 0, 0, 26, 27, 28, 29, 30,
31, 32, 19, 0, 20, 8, 8, 12, 22, 23,
18, 2, 6, 9, 11, 0, 13, 0, 24, 14,
}
var yyTok1 = [...]int{
@ -157,12 +162,12 @@ var yyTok1 = [...]int{
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
28, 29, 3, 3, 25, 3, 19, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 23,
29, 30, 3, 3, 26, 3, 19, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 25, 23,
21, 24, 22, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 26, 3, 27, 3, 3, 3, 3, 3, 3,
3, 27, 3, 28, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 20,
@ -515,69 +520,86 @@ yydefault:
case 1:
yyDollar = yyS[yypt-2 : yypt+1]
//line expressions.y:37
//line expressions.y:43
{
yylex.(*lexer).val = yyDollar[1].f
}
case 2:
yyDollar = yyS[yypt-5 : yypt+1]
//line expressions.y:38
//line expressions.y:44
{
yylex.(*lexer).Assignment = Assignment{yyDollar[2].name, &expression{yyDollar[4].f}}
}
case 3:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:41
//line expressions.y:47
{
args := yyDollar[2].arglist
yylex.(*lexer).val = func(ctx Context) interface{} {
result := make([]interface{}, len(args))
for i, fn := range args {
result[i] = fn(ctx)
}
return result
}
yylex.(*lexer).Cycle = yyDollar[2].cycle
}
case 4:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:51
//line expressions.y:48
{
yylex.(*lexer).Loop = yyDollar[2].loop
}
case 5:
yyDollar = yyS[yypt-2 : yypt+1]
//line expressions.y:55
//line expressions.y:51
{
yyVAL.arglist = append([]func(Context) interface{}{yyDollar[1].f}, yyDollar[2].arglist...)
yyVAL.cycle = yyDollar[2].cyclefn(yyDollar[1].s)
}
case 6:
yyDollar = yyS[yypt-0 : yypt+1]
//line expressions.y:57
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:54
{
yyVAL.arglist = []func(Context) interface{}{}
h, t := yyDollar[2].s, yyDollar[3].ss
yyVAL.cyclefn = func(g string) Cycle { return Cycle{g, append([]string{h}, t...)} }
}
case 7:
yyDollar = yyS[yypt-3 : yypt+1]
yyDollar = yyS[yypt-1 : yypt+1]
//line expressions.y:58
{
yyVAL.arglist = append([]func(Context) interface{}{yyDollar[2].f}, yyDollar[3].arglist...)
vals := yyDollar[1].ss
yyVAL.cyclefn = func(h string) Cycle { return Cycle{Values: append([]string{h}, vals...)} }
}
case 8:
yyDollar = yyS[yypt-0 : yypt+1]
//line expressions.y:65
{
yyVAL.ss = []string{}
}
case 9:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:66
{
yyVAL.ss = append([]string{yyDollar[2].s}, yyDollar[3].ss...)
}
case 10:
yyDollar = yyS[yypt-1 : yypt+1]
//line expressions.y:69
{
s, ok := yyDollar[1].val.(string)
if !ok {
panic(ParseError(fmt.Sprintf("expected a string for %q", yyDollar[1].val)))
}
yyVAL.s = s
}
case 11:
yyDollar = yyS[yypt-4 : yypt+1]
//line expressions.y:61
//line expressions.y:77
{
name, expr, mods := yyDollar[1].name, yyDollar[3].f, yyDollar[4].loopmods
yyVAL.loop = Loop{name, &expression{expr}, mods}
}
case 9:
case 12:
yyDollar = yyS[yypt-0 : yypt+1]
//line expressions.y:67
//line expressions.y:83
{
yyVAL.loopmods = loopModifiers{}
}
case 10:
case 13:
yyDollar = yyS[yypt-2 : yypt+1]
//line expressions.y:68
//line expressions.y:84
{
switch yyDollar[2].name {
case "reversed":
@ -587,9 +609,9 @@ yydefault:
}
yyVAL.loopmods = yyDollar[1].loopmods
}
case 11:
case 14:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:77
//line expressions.y:93
{ // TODO can this be a variable?
switch yyDollar[2].name {
case "limit":
@ -609,65 +631,65 @@ yydefault:
}
yyVAL.loopmods = yyDollar[1].loopmods
}
case 12:
case 15:
yyDollar = yyS[yypt-1 : yypt+1]
//line expressions.y:99
//line expressions.y:115
{
val := yyDollar[1].val
yyVAL.f = func(_ Context) interface{} { return val }
}
case 13:
case 16:
yyDollar = yyS[yypt-1 : yypt+1]
//line expressions.y:100
//line expressions.y:116
{
name := yyDollar[1].name
yyVAL.f = func(ctx Context) interface{} { return ctx.Get(name) }
}
case 14:
case 17:
yyDollar = yyS[yypt-2 : yypt+1]
//line expressions.y:101
//line expressions.y:117
{
yyVAL.f = makeObjectPropertyExpr(yyDollar[1].f, yyDollar[2].name)
}
case 15:
case 18:
yyDollar = yyS[yypt-4 : yypt+1]
//line expressions.y:102
//line expressions.y:118
{
yyVAL.f = makeIndexExpr(yyDollar[1].f, yyDollar[3].f)
}
case 16:
case 19:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:103
//line expressions.y:119
{
yyVAL.f = yyDollar[2].f
}
case 18:
case 21:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:108
//line expressions.y:124
{
yyVAL.f = makeFilter(yyDollar[1].f, yyDollar[3].name, nil)
}
case 19:
case 22:
yyDollar = yyS[yypt-4 : yypt+1]
//line expressions.y:109
//line expressions.y:125
{
yyVAL.f = makeFilter(yyDollar[1].f, yyDollar[3].name, yyDollar[4].filter_params)
}
case 20:
case 23:
yyDollar = yyS[yypt-1 : yypt+1]
//line expressions.y:113
//line expressions.y:129
{
yyVAL.filter_params = []valueFn{yyDollar[1].f}
}
case 21:
case 24:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:115
//line expressions.y:131
{
yyVAL.filter_params = append(yyDollar[1].filter_params, yyDollar[3].f)
}
case 23:
case 26:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:119
//line expressions.y:135
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -675,9 +697,9 @@ yydefault:
return evaluator.Equal(a, b)
}
}
case 24:
case 27:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:126
//line expressions.y:142
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -685,9 +707,9 @@ yydefault:
return !evaluator.Equal(a, b)
}
}
case 25:
case 28:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:133
//line expressions.y:149
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -695,9 +717,9 @@ yydefault:
return evaluator.Less(b, a)
}
}
case 26:
case 29:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:140
//line expressions.y:156
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -705,9 +727,9 @@ yydefault:
return evaluator.Less(a, b)
}
}
case 27:
case 30:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:147
//line expressions.y:163
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -715,9 +737,9 @@ yydefault:
return evaluator.Less(b, a) || evaluator.Equal(a, b)
}
}
case 28:
case 31:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:154
//line expressions.y:170
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -725,24 +747,24 @@ yydefault:
return evaluator.Less(a, b) || evaluator.Equal(a, b)
}
}
case 29:
case 32:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:161
//line expressions.y:177
{
yyVAL.f = makeContainsExpr(yyDollar[1].f, yyDollar[3].f)
}
case 31:
case 34:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:166
//line expressions.y:182
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
return evaluator.IsTrue(fa(ctx)) && evaluator.IsTrue(fb(ctx))
}
}
case 32:
case 35:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:172
//line expressions.y:188
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {

View File

@ -31,16 +31,8 @@ func cycleTag(args string) (func(io.Writer, render.Context) error, error) {
if err != nil {
return nil, err
}
expr := stmt.Expression()
cycle := stmt.Cycle
return func(w io.Writer, ctx render.Context) error {
value, err := ctx.Evaluate(expr)
if err != nil {
return err
}
array := value.([]interface{})
if len(array) == 0 {
return nil
}
loopVar := ctx.Get(forloopVarName)
if loopVar == nil {
return ctx.Errorf("cycle must be within a forloop")
@ -49,11 +41,11 @@ func cycleTag(args string) (func(io.Writer, render.Context) error, error) {
// “C++ protects against accident, not against fraud.” Bjarne Stroustrup
loopRec := loopVar.(map[string]interface{})
cycleMap := loopRec[".cycles"].(map[string]int)
group := ""
group, values := cycle.Group, cycle.Values
n := cycleMap[group]
cycleMap[group] = n + 1
fmt.Println(cycleMap)
_, err = w.Write([]byte(fmt.Sprint(array[n%len(array)])))
// The parser guarantees that there will be at least one item.
_, err = w.Write([]byte(values[n%len(values)]))
return err
}, nil
}

View File

@ -60,6 +60,8 @@ var loopTests = []struct{ in, expected string }{
// cycle
{`{% for a in array %}{% cycle 'even', 'odd' %}.{% endfor %}`, "even.odd.even."},
{`{% for a in array %}{% cycle '0', '1' %},{% cycle '0', '1' %}.{% endfor %}`, "0,1.0,1.0,1."},
// {`{% for a in array %}{% cycle group: 'a', '0', '1' %},{% cycle '0', '1' %}.{% endfor %}`, "0,1.0,1.0,1."},
}
var loopErrorTests = []struct{ in, expected string }{