1
0
mirror of https://github.com/danog/liquid.git synced 2024-11-30 08:18:59 +01:00

Start #2 cycle tag

This commit is contained in:
Oliver Steele 2017-07-12 14:25:41 -04:00
parent 3c242c40f1
commit a637d2710a
11 changed files with 421 additions and 282 deletions

View File

@ -30,7 +30,8 @@ The [feature parity board](https://github.com/osteele/liquid/projects/1) lists d
In brief, these aren't implemented:
- The `cycle` and `tablerow` tags
- The group property of the `cycle` tag
- The `tablerow` tag
- `{% when a or b %}`
- Loop ranges `{% for a in 1...10 %}`
- Error modes

View File

@ -16,15 +16,17 @@ func init() {
name string
val interface{}
f func(Context) interface{}
arglist []func(Context) interface{}
loopmods loopModifiers
filter_params []valueFn
}
%type <f> expr rel filtered cond loop
%type<filter_params> filter_params
%type<loopmods> loop_modifiers
%type<arglist> arglist moreargs
%token <val> LITERAL
%token <name> IDENTIFIER KEYWORD PROPERTY
%token ASSIGN LOOP
%token ARGLIST ASSIGN LOOP
%token EQ NEQ GE LE FOR IN AND OR CONTAINS
%left '.' '|'
%left '<' '>'
@ -38,10 +40,27 @@ start:
return nil
}
}
| LOOP loop { yylex.(*lexer).val = $2 }
| 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
}
}
| LOOP loop ';' { yylex.(*lexer).val = $2 }
;
loop: IDENTIFIER IN filtered loop_modifiers ';' {
arglist:
expr moreargs { $$ = append([]func(Context) interface{}{$1}, $2...) };
moreargs : /* empty */ { $$ = []func(Context) interface{}{}}
| ',' expr moreargs { $$ = append([]func(Context) interface{}{$2}, $3...) }
;
loop: IDENTIFIER IN filtered loop_modifiers {
name, expr, mods := $1, $3, $4
$$ = func(ctx Context) interface{} {
return &Loop{name, expr(ctx), mods}

View File

@ -7,8 +7,14 @@ import (
"github.com/stretchr/testify/require"
)
var parseTests = []struct{ in, expected string }{
{"a | filter: b", "parse error"},
var parseTests = []struct {
in string
expect interface{}
}{
{`a | filter: b`, 3},
{`%assign a = 3`, nil},
{`{%cycle 'a'`, []interface{}{"a"}},
{`{%cycle 'a', 'b'`, []interface{}{"a", "b"}},
}
var parseErrorTests = []struct{ in, expected string }{
@ -25,7 +31,7 @@ func TestParse(t *testing.T) {
require.NoError(t, err, test.in)
value, err := expr.Evaluate(ctx)
require.NoError(t, err, test.in)
require.Equal(t, 3, value, test.in)
require.Equal(t, test.expect, value, test.in)
})
}
}

View File

@ -10,186 +10,207 @@ var _expression_actions []byte = []byte{
1, 16, 1, 17, 1, 18, 1, 19,
1, 20, 1, 21, 1, 22, 1, 23,
1, 24, 1, 25, 1, 26, 1, 27,
1, 28, 2, 2, 3, 2, 2, 4,
2, 2, 5, 2, 2, 6, 2, 2,
7, 2, 2, 8, 2, 2, 9, 2,
2, 10,
1, 28, 1, 29, 2, 2, 3, 2,
2, 4, 2, 2, 5, 2, 2, 6,
2, 2, 7, 2, 2, 8, 2, 2,
9, 2, 2, 10,
}
var _expression_key_offsets []int16 = []int16{
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 12, 38, 41, 42, 43,
45, 46, 49, 51, 54, 62, 71, 80,
81, 82, 83, 93, 94, 105, 116, 127,
138, 149, 160, 171, 182, 193, 205, 216,
227, 238, 249, 260, 271, 282, 293, 304,
8, 9, 10, 11, 12, 14, 15, 16,
17, 18, 19, 20, 47, 50, 51, 52,
54, 55, 58, 60, 63, 71, 80, 89,
90, 91, 92, 102, 103, 114, 125, 136,
147, 158, 169, 180, 191, 202, 214, 225,
236, 247, 258, 269, 280, 291, 302, 313,
324,
}
var _expression_trans_keys []byte = []byte{
34, 115, 115, 105, 103, 110, 111, 111,
112, 39, 48, 57, 32, 33, 34, 37,
34, 115, 115, 105, 103, 110, 32, 111,
111, 112, 32, 39, 48, 57, 99, 121,
99, 108, 101, 32, 32, 33, 34, 37,
39, 45, 46, 60, 61, 62, 95, 97,
99, 102, 105, 110, 111, 116, 9, 13,
48, 57, 65, 90, 98, 122, 32, 9,
13, 61, 34, 97, 108, 39, 46, 48,
57, 48, 57, 46, 48, 57, 45, 95,
48, 57, 65, 90, 97, 122, 45, 63,
99, 102, 105, 110, 111, 116, 123, 9,
13, 48, 57, 65, 90, 98, 122, 32,
9, 13, 61, 34, 97, 108, 39, 46,
48, 57, 48, 57, 46, 48, 57, 45,
95, 48, 57, 65, 90, 97, 122, 45,
63, 95, 48, 57, 65, 90, 97, 122,
61, 61, 61, 45, 58, 63, 95, 48,
57, 65, 90, 97, 122, 58, 45, 58,
63, 95, 110, 48, 57, 65, 90, 97,
122, 45, 58, 63, 95, 100, 48, 57,
65, 90, 97, 122, 45, 58, 63, 95,
111, 48, 57, 65, 90, 97, 122, 45,
45, 63, 95, 48, 57, 65, 90, 97,
122, 61, 61, 61, 45, 58, 63, 95,
48, 57, 65, 90, 97, 122, 58, 45,
58, 63, 95, 110, 48, 57, 65, 90,
97, 122, 45, 58, 63, 95, 116, 48,
97, 122, 45, 58, 63, 95, 100, 48,
57, 65, 90, 97, 122, 45, 58, 63,
95, 97, 48, 57, 65, 90, 98, 122,
45, 58, 63, 95, 105, 48, 57, 65,
90, 97, 122, 45, 58, 63, 95, 110,
95, 111, 48, 57, 65, 90, 97, 122,
45, 58, 63, 95, 110, 48, 57, 65,
90, 97, 122, 45, 58, 63, 95, 116,
48, 57, 65, 90, 97, 122, 45, 58,
63, 95, 115, 48, 57, 65, 90, 97,
122, 45, 58, 63, 95, 97, 111, 48,
57, 65, 90, 98, 122, 45, 58, 63,
95, 108, 48, 57, 65, 90, 97, 122,
45, 58, 63, 95, 115, 48, 57, 65,
90, 97, 122, 45, 58, 63, 95, 101,
63, 95, 97, 48, 57, 65, 90, 98,
122, 45, 58, 63, 95, 105, 48, 57,
65, 90, 97, 122, 45, 58, 63, 95,
110, 48, 57, 65, 90, 97, 122, 45,
58, 63, 95, 115, 48, 57, 65, 90,
97, 122, 45, 58, 63, 95, 97, 111,
48, 57, 65, 90, 98, 122, 45, 58,
63, 95, 108, 48, 57, 65, 90, 97,
122, 45, 58, 63, 95, 115, 48, 57,
65, 90, 97, 122, 45, 58, 63, 95,
101, 48, 57, 65, 90, 97, 122, 45,
58, 63, 95, 114, 48, 57, 65, 90,
97, 122, 45, 58, 63, 95, 110, 48,
57, 65, 90, 97, 122, 45, 58, 63,
95, 105, 48, 57, 65, 90, 97, 122,
45, 58, 63, 95, 108, 48, 57, 65,
90, 97, 122, 45, 58, 63, 95, 114,
48, 57, 65, 90, 97, 122, 45, 58,
63, 95, 114, 48, 57, 65, 90, 97,
122, 45, 58, 63, 95, 110, 48, 57,
65, 90, 97, 122, 45, 58, 63, 95,
105, 48, 57, 65, 90, 97, 122, 45,
58, 63, 95, 108, 48, 57, 65, 90,
97, 122, 45, 58, 63, 95, 114, 48,
57, 65, 90, 97, 122, 45, 58, 63,
95, 114, 48, 57, 65, 90, 97, 122,
45, 58, 63, 95, 117, 48, 57, 65,
90, 97, 122,
122, 45, 58, 63, 95, 117, 48, 57,
65, 90, 97, 122, 37,
}
var _expression_single_lengths []byte = []byte{
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 18, 1, 1, 1, 2,
1, 1, 1, 1, 0, 1, 1, 1,
1, 1, 1, 19, 1, 1, 1, 2,
1, 1, 0, 1, 2, 3, 3, 1,
1, 1, 4, 1, 5, 5, 5, 5,
5, 5, 5, 5, 5, 6, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5,
1,
}
var _expression_range_lengths []byte = []byte{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 4, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 4, 1, 0, 0, 0,
0, 1, 1, 1, 3, 3, 3, 0,
0, 0, 3, 0, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3,
0,
}
var _expression_index_offsets []int16 = []int16{
0, 2, 4, 6, 8, 10, 12, 14,
16, 18, 20, 22, 45, 48, 50, 52,
55, 57, 60, 62, 65, 71, 78, 85,
87, 89, 91, 99, 101, 110, 119, 128,
137, 146, 155, 164, 173, 182, 192, 201,
210, 219, 228, 237, 246, 255, 264, 273,
16, 18, 20, 22, 24, 26, 28, 30,
32, 34, 36, 38, 62, 65, 67, 69,
72, 74, 77, 79, 82, 88, 95, 102,
104, 106, 108, 116, 118, 127, 136, 145,
154, 163, 172, 181, 190, 199, 209, 218,
227, 236, 245, 254, 263, 272, 281, 290,
299,
}
var _expression_indicies []byte = []byte{
2, 1, 3, 0, 4, 0, 5, 0,
6, 0, 7, 0, 8, 0, 9, 0,
10, 0, 2, 11, 12, 0, 14, 15,
16, 17, 18, 19, 20, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32,
14, 21, 25, 25, 13, 14, 14, 33,
35, 34, 2, 1, 36, 37, 34, 2,
11, 38, 21, 34, 12, 39, 12, 21,
40, 41, 41, 42, 41, 41, 34, 41,
44, 41, 41, 41, 41, 43, 41, 44,
41, 42, 41, 41, 39, 45, 34, 46,
34, 47, 34, 25, 49, 50, 25, 25,
25, 25, 48, 49, 51, 25, 49, 50,
25, 52, 25, 25, 25, 51, 25, 49,
50, 25, 53, 25, 25, 25, 51, 25,
49, 50, 25, 54, 25, 25, 25, 51,
25, 49, 50, 25, 55, 25, 25, 25,
51, 25, 49, 50, 25, 56, 25, 25,
25, 51, 25, 49, 50, 25, 57, 25,
25, 25, 51, 25, 49, 50, 25, 58,
25, 25, 25, 51, 25, 49, 50, 25,
59, 25, 25, 25, 51, 25, 49, 50,
25, 60, 25, 25, 25, 51, 25, 49,
50, 25, 61, 62, 25, 25, 25, 51,
25, 49, 50, 25, 63, 25, 25, 25,
51, 25, 49, 50, 25, 64, 25, 25,
25, 51, 25, 49, 50, 25, 65, 25,
25, 25, 51, 25, 49, 50, 25, 66,
25, 25, 25, 51, 25, 49, 50, 25,
67, 25, 25, 25, 51, 25, 49, 50,
25, 68, 25, 25, 25, 51, 25, 49,
50, 25, 69, 25, 25, 25, 51, 25,
49, 50, 25, 70, 25, 25, 25, 51,
25, 49, 50, 25, 71, 25, 25, 25,
51, 25, 49, 50, 25, 64, 25, 25,
25, 51,
10, 0, 11, 0, 12, 0, 2, 13,
14, 0, 15, 0, 16, 0, 17, 0,
18, 0, 19, 0, 20, 0, 22, 23,
24, 25, 26, 27, 28, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40,
41, 22, 29, 33, 33, 21, 22, 22,
42, 44, 43, 2, 1, 45, 46, 43,
2, 13, 47, 29, 43, 14, 48, 14,
29, 49, 50, 50, 51, 50, 50, 43,
50, 53, 50, 50, 50, 50, 52, 50,
53, 50, 51, 50, 50, 48, 54, 43,
55, 43, 56, 43, 33, 58, 59, 33,
33, 33, 33, 57, 58, 60, 33, 58,
59, 33, 61, 33, 33, 33, 60, 33,
58, 59, 33, 62, 33, 33, 33, 60,
33, 58, 59, 33, 63, 33, 33, 33,
60, 33, 58, 59, 33, 64, 33, 33,
33, 60, 33, 58, 59, 33, 65, 33,
33, 33, 60, 33, 58, 59, 33, 66,
33, 33, 33, 60, 33, 58, 59, 33,
67, 33, 33, 33, 60, 33, 58, 59,
33, 68, 33, 33, 33, 60, 33, 58,
59, 33, 69, 33, 33, 33, 60, 33,
58, 59, 33, 70, 71, 33, 33, 33,
60, 33, 58, 59, 33, 72, 33, 33,
33, 60, 33, 58, 59, 33, 73, 33,
33, 33, 60, 33, 58, 59, 33, 74,
33, 33, 33, 60, 33, 58, 59, 33,
75, 33, 33, 33, 60, 33, 58, 59,
33, 76, 33, 33, 33, 60, 33, 58,
59, 33, 77, 33, 33, 33, 60, 33,
58, 59, 33, 78, 33, 33, 33, 60,
33, 58, 59, 33, 79, 33, 33, 33,
60, 33, 58, 59, 33, 80, 33, 33,
33, 60, 33, 58, 59, 33, 73, 33,
33, 33, 60, 81, 43,
}
var _expression_trans_targs []byte = []byte{
11, 0, 11, 2, 3, 4, 5, 11,
7, 8, 11, 9, 18, 11, 12, 13,
14, 15, 16, 17, 20, 19, 23, 24,
25, 26, 28, 30, 37, 42, 43, 45,
46, 11, 11, 11, 1, 6, 10, 11,
11, 21, 22, 11, 11, 11, 11, 11,
11, 11, 27, 11, 29, 26, 31, 32,
33, 34, 35, 36, 26, 38, 41, 39,
40, 26, 26, 26, 44, 26, 26, 47,
19, 0, 19, 2, 3, 4, 5, 6,
19, 8, 9, 10, 19, 11, 26, 14,
15, 16, 17, 18, 19, 19, 20, 21,
22, 23, 24, 25, 28, 27, 31, 32,
33, 34, 36, 38, 45, 50, 51, 53,
54, 56, 19, 19, 19, 1, 7, 12,
19, 19, 29, 30, 19, 19, 19, 19,
19, 19, 19, 35, 19, 37, 34, 39,
40, 41, 42, 43, 44, 34, 46, 49,
47, 48, 34, 34, 34, 52, 34, 34,
55, 13,
}
var _expression_trans_actions []byte = []byte{
39, 0, 11, 0, 0, 0, 0, 7,
0, 0, 9, 0, 0, 25, 0, 0,
41, 0, 13, 0, 0, 0, 0, 0,
7, 0, 0, 0, 11, 0, 0, 0,
0, 0, 0, 0, 9, 27, 0, 0,
5, 5, 5, 5, 0, 0, 0, 0,
0, 64, 0, 0, 0, 0, 0, 0,
0, 35, 37, 15, 0, 0, 0, 29,
27, 0, 0, 33, 23, 19, 13, 17,
41, 21, 0, 31, 0, 49, 0, 0,
0, 0, 0, 0, 55, 0, 0, 0,
0, 43, 58, 61, 0, 46, 52, 0,
0, 66, 0, 0, 0, 0, 0, 0,
0, 5, 37, 39, 17, 0, 0, 0,
31, 29, 0, 0, 35, 25, 21, 15,
19, 43, 23, 0, 33, 0, 51, 0,
0, 0, 0, 0, 0, 57, 0, 0,
0, 0, 45, 60, 63, 0, 48, 54,
0, 0,
}
var _expression_to_state_actions []byte = []byte{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0,
}
var _expression_from_state_actions []byte = []byte{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 3, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0,
}
var _expression_eof_trans []int16 = []int16{
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 34, 35, 35, 35,
35, 35, 40, 41, 35, 44, 40, 35,
35, 35, 49, 52, 52, 52, 52, 52,
52, 52, 52, 52, 52, 52, 52, 52,
52, 52, 52, 52, 52, 52, 52, 52,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 43, 44, 44, 44,
44, 44, 49, 50, 44, 53, 49, 44,
44, 44, 58, 61, 61, 61, 61, 61,
61, 61, 61, 61, 61, 61, 61, 61,
61, 61, 61, 61, 61, 61, 61, 61,
44,
}
const expression_start int = 11
const expression_first_final int = 11
const expression_start int = 19
const expression_first_final int = 19
const expression_error int = -1
const expression_en_main int = 11
const expression_en_main int = 19
//line scanner.rl:11
@ -210,7 +231,7 @@ func newLexer(data []byte) *lexer {
pe: len(data),
}
//line scanner.go:218
//line scanner.go:239
{
lex.cs = expression_start
lex.ts = 0
@ -226,7 +247,7 @@ func (lex *lexer) Lex(out *yySymType) int {
eof := lex.pe
tok := 0
//line scanner.go:235
//line scanner.go:256
{
var _klen int
var _trans int
@ -247,7 +268,7 @@ func (lex *lexer) Lex(out *yySymType) int {
//line NONE:1
lex.ts = (lex.p)
//line scanner.go:255
//line scanner.go:276
}
}
@ -324,28 +345,28 @@ func (lex *lexer) Lex(out *yySymType) int {
case 3:
//line scanner.rl:38
lex.act = 6
case 4:
//line scanner.rl:88
lex.act = 7
case 4:
//line scanner.rl:89
lex.act = 8
case 5:
//line scanner.rl:93
lex.act = 12
case 6:
//line scanner.rl:94
lex.act = 13
case 7:
case 6:
//line scanner.rl:95
lex.act = 14
case 8:
case 7:
//line scanner.rl:96
lex.act = 15
case 9:
case 8:
//line scanner.rl:97
lex.act = 16
case 9:
//line scanner.rl:98
lex.act = 17
case 10:
//line scanner.rl:43
lex.act = 18
lex.act = 19
case 11:
//line scanner.rl:82
lex.te = (lex.p) + 1
@ -358,11 +379,19 @@ func (lex *lexer) Lex(out *yySymType) int {
//line scanner.rl:83
lex.te = (lex.p) + 1
{
tok = LOOP
tok = ARGLIST
(lex.p)++
goto _out
}
case 13:
//line scanner.rl:84
lex.te = (lex.p) + 1
{
tok = LOOP
(lex.p)++
goto _out
}
case 14:
//line scanner.rl:66
lex.te = (lex.p) + 1
{
@ -373,19 +402,11 @@ func (lex *lexer) Lex(out *yySymType) int {
goto _out
}
case 14:
//line scanner.rl:89
lex.te = (lex.p) + 1
{
tok = EQ
(lex.p)++
goto _out
}
case 15:
//line scanner.rl:90
lex.te = (lex.p) + 1
{
tok = NEQ
tok = EQ
(lex.p)++
goto _out
}
@ -393,7 +414,7 @@ func (lex *lexer) Lex(out *yySymType) int {
//line scanner.rl:91
lex.te = (lex.p) + 1
{
tok = GE
tok = NEQ
(lex.p)++
goto _out
}
@ -401,12 +422,20 @@ func (lex *lexer) Lex(out *yySymType) int {
//line scanner.rl:92
lex.te = (lex.p) + 1
{
tok = LE
tok = GE
(lex.p)++
goto _out
}
case 18:
//line scanner.rl:98
//line scanner.rl:93
lex.te = (lex.p) + 1
{
tok = LE
(lex.p)++
goto _out
}
case 19:
//line scanner.rl:99
lex.te = (lex.p) + 1
{
tok = KEYWORD
@ -414,8 +443,8 @@ func (lex *lexer) Lex(out *yySymType) int {
(lex.p)++
goto _out
}
case 19:
//line scanner.rl:100
case 20:
//line scanner.rl:101
lex.te = (lex.p) + 1
{
tok = PROPERTY
@ -423,15 +452,15 @@ func (lex *lexer) Lex(out *yySymType) int {
(lex.p)++
goto _out
}
case 20:
//line scanner.rl:102
case 21:
//line scanner.rl:103
lex.te = (lex.p) + 1
{
tok = int(lex.data[lex.ts])
(lex.p)++
goto _out
}
case 21:
case 22:
//line scanner.rl:48
lex.te = (lex.p)
(lex.p)--
@ -446,7 +475,7 @@ func (lex *lexer) Lex(out *yySymType) int {
goto _out
}
case 22:
case 23:
//line scanner.rl:57
lex.te = (lex.p)
(lex.p)--
@ -461,7 +490,7 @@ func (lex *lexer) Lex(out *yySymType) int {
goto _out
}
case 23:
case 24:
//line scanner.rl:43
lex.te = (lex.p)
(lex.p)--
@ -472,8 +501,8 @@ func (lex *lexer) Lex(out *yySymType) int {
goto _out
}
case 24:
//line scanner.rl:100
case 25:
//line scanner.rl:101
lex.te = (lex.p)
(lex.p)--
{
@ -482,32 +511,32 @@ func (lex *lexer) Lex(out *yySymType) int {
(lex.p)++
goto _out
}
case 25:
//line scanner.rl:101
lex.te = (lex.p)
(lex.p)--
case 26:
//line scanner.rl:102
lex.te = (lex.p)
(lex.p)--
{
tok = int(lex.data[lex.ts])
(lex.p)++
goto _out
}
case 27:
//line scanner.rl:102
(lex.p) = (lex.te) - 1
//line scanner.rl:103
lex.te = (lex.p)
(lex.p)--
{
tok = int(lex.data[lex.ts])
(lex.p)++
goto _out
}
case 28:
//line scanner.rl:103
(lex.p) = (lex.te) - 1
{
tok = int(lex.data[lex.ts])
(lex.p)++
goto _out
}
case 29:
//line NONE:1
switch lex.act {
case 6:
case 7:
{
(lex.p) = (lex.te) - 1
@ -517,7 +546,7 @@ func (lex *lexer) Lex(out *yySymType) int {
goto _out
}
case 7:
case 8:
{
(lex.p) = (lex.te) - 1
tok = LITERAL
@ -525,42 +554,42 @@ func (lex *lexer) Lex(out *yySymType) int {
(lex.p)++
goto _out
}
case 12:
case 13:
{
(lex.p) = (lex.te) - 1
tok = AND
(lex.p)++
goto _out
}
case 13:
case 14:
{
(lex.p) = (lex.te) - 1
tok = OR
(lex.p)++
goto _out
}
case 14:
case 15:
{
(lex.p) = (lex.te) - 1
tok = CONTAINS
(lex.p)++
goto _out
}
case 15:
case 16:
{
(lex.p) = (lex.te) - 1
tok = FOR
(lex.p)++
goto _out
}
case 16:
case 17:
{
(lex.p) = (lex.te) - 1
tok = IN
(lex.p)++
goto _out
}
case 18:
case 19:
{
(lex.p) = (lex.te) - 1
@ -572,7 +601,7 @@ func (lex *lexer) Lex(out *yySymType) int {
}
}
//line scanner.go:513
//line scanner.go:539
}
}
@ -587,7 +616,7 @@ func (lex *lexer) Lex(out *yySymType) int {
//line NONE:1
lex.ts = 0
//line scanner.go:527
//line scanner.go:553
}
}
@ -610,7 +639,7 @@ func (lex *lexer) Lex(out *yySymType) int {
}
}
//line scanner.rl:106
//line scanner.rl:107
return tok
}

View File

@ -80,6 +80,7 @@ func (lex *lexer) Lex(out *yySymType) int {
main := |*
"%assign " => { tok = ASSIGN; fbreak; };
"{%cycle " => { tok = ARGLIST; fbreak; };
"%loop " => { tok = LOOP; fbreak; };
int => Int;
float => Float;

View File

@ -83,4 +83,7 @@ func TestLex(t *testing.T) {
require.Equal(t, "ab_c", ts[1].typ.name)
require.Equal(t, "ab-c", ts[2].typ.name)
require.Equal(t, "abc?", ts[3].typ.name)
ts, _ = scanExpression(`{%cycle 'a', 'b'`)
require.Len(t, ts, 4)
}

View File

@ -21,6 +21,7 @@ type yySymType struct {
name string
val interface{}
f func(Context) interface{}
arglist []func(Context) interface{}
loopmods loopModifiers
filter_params []valueFn
}
@ -29,17 +30,18 @@ const LITERAL = 57346
const IDENTIFIER = 57347
const KEYWORD = 57348
const PROPERTY = 57349
const ASSIGN = 57350
const LOOP = 57351
const EQ = 57352
const NEQ = 57353
const GE = 57354
const LE = 57355
const FOR = 57356
const IN = 57357
const AND = 57358
const OR = 57359
const CONTAINS = 57360
const ARGLIST = 57350
const ASSIGN = 57351
const LOOP = 57352
const EQ = 57353
const NEQ = 57354
const GE = 57355
const LE = 57356
const FOR = 57357
const IN = 57358
const AND = 57359
const OR = 57360
const CONTAINS = 57361
var yyToknames = [...]string{
"$end",
@ -49,6 +51,7 @@ var yyToknames = [...]string{
"IDENTIFIER",
"KEYWORD",
"PROPERTY",
"ARGLIST",
"ASSIGN",
"LOOP",
"EQ",
@ -66,11 +69,11 @@ var yyToknames = [...]string{
"'>'",
"';'",
"'='",
"','",
"'['",
"']'",
"'('",
"')'",
"','",
}
var yyStatenames = [...]string{}
@ -87,60 +90,67 @@ var yyExca = [...]int{
const yyPrivate = 57344
const yyLast = 67
const yyLast = 77
var yyAct = [...]int{
7, 18, 51, 30, 20, 21, 24, 25, 6, 17,
8, 9, 26, 8, 9, 23, 22, 3, 4, 19,
34, 35, 36, 37, 38, 39, 40, 41, 18, 12,
13, 44, 44, 10, 47, 18, 10, 53, 54, 43,
45, 42, 12, 13, 31, 17, 19, 48, 49, 11,
5, 2, 55, 19, 16, 52, 32, 33, 14, 56,
1, 50, 27, 28, 29, 46, 15,
8, 35, 21, 60, 7, 17, 23, 24, 27, 28,
9, 10, 33, 37, 29, 9, 10, 26, 25, 4,
3, 5, 22, 41, 42, 43, 44, 45, 46, 47,
48, 13, 14, 21, 51, 11, 21, 52, 50, 51,
11, 55, 21, 53, 49, 34, 20, 2, 13, 14,
38, 6, 36, 22, 58, 12, 22, 56, 19, 30,
20, 63, 22, 57, 15, 31, 32, 61, 62, 39,
40, 64, 1, 16, 59, 54, 18,
}
var yyPact = [...]int{
9, -1000, 26, 53, 49, -1000, -11, -6, -1000, -1000,
6, -1000, 6, 6, -21, -1000, 29, 51, -1000, 6,
6, 6, 6, 6, 6, 6, 6, 13, -1000, -1000,
6, 6, -1000, 6, 21, 28, 28, 28, 28, 28,
28, 28, -1000, 25, 28, -11, -27, 28, -1000, -1000,
32, 6, -1000, -1000, 55, 28, -1000,
11, -1000, 31, 59, 6, 53, -1000, 25, -5, -1000,
-1000, 6, -1000, 6, 6, -13, 21, 26, -11, 34,
64, -1000, 6, 6, 6, 6, 6, 6, 6, 6,
14, -1000, -1000, 6, -1000, -1000, 6, -1000, 6, -1000,
6, 29, 35, 35, 35, 35, 35, 35, 35, -1000,
39, 35, 26, 25, -23, 35, -1000, -1000, -1000, 62,
6, -1000, 67, 35, -1000,
}
var yyPgo = [...]int{
0, 0, 50, 8, 51, 66, 65, 61, 60,
0, 0, 51, 4, 47, 76, 75, 74, 73, 1,
72,
}
var yyR1 = [...]int{
0, 8, 8, 8, 5, 7, 7, 7, 1, 1,
1, 1, 1, 3, 3, 3, 6, 6, 2, 2,
2, 2, 2, 2, 2, 2, 4, 4, 4,
0, 10, 10, 10, 10, 8, 9, 9, 5, 7,
7, 7, 1, 1, 1, 1, 1, 3, 3, 3,
6, 6, 2, 2, 2, 2, 2, 2, 2, 2,
4, 4, 4,
}
var yyR2 = [...]int{
0, 2, 5, 2, 5, 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, 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,
}
var yyChk = [...]int{
-1000, -8, -4, 8, 9, -2, -3, -1, 4, 5,
27, 23, 16, 17, 5, -5, 5, 20, 7, 25,
10, 11, 22, 21, 12, 13, 18, -4, -2, -2,
24, 15, 5, 6, -1, -1, -1, -1, -1, -1,
-1, -1, 28, -3, -1, -3, -6, -1, 26, 23,
-7, 29, 23, 5, 6, -1, 4,
-1000, -10, -4, 9, 8, 10, -2, -3, -1, 4,
5, 29, 24, 17, 18, 5, -8, -1, -5, 5,
21, 7, 27, 11, 12, 23, 22, 13, 14, 19,
-4, -2, -2, 25, 24, -9, 26, 24, 16, 5,
6, -1, -1, -1, -1, -1, -1, -1, -1, 30,
-3, -1, -1, -3, -6, -1, 28, 24, -9, -7,
26, 5, 6, -1, 4,
}
var yyDef = [...]int{
0, -2, 0, 0, 0, 26, 18, 13, 8, 9,
0, 1, 0, 0, 0, 3, 0, 0, 10, 0,
0, 0, 0, 0, 0, 0, 0, 0, 27, 28,
0, 0, 14, 0, 0, 19, 20, 21, 22, 23,
24, 25, 12, 0, 13, 5, 15, 16, 11, 2,
0, 0, 4, 6, 0, 17, 7,
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,
}
var yyTok1 = [...]int{
@ -148,20 +158,20 @@ 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,
27, 28, 3, 3, 29, 3, 19, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 23,
21, 24, 22, 3, 3, 3, 3, 3, 3, 3,
29, 30, 3, 3, 26, 3, 20, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 24,
22, 25, 23, 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, 25, 3, 26, 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,
3, 3, 3, 3, 21,
}
var yyTok2 = [...]int{
2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18,
12, 13, 14, 15, 16, 17, 18, 19,
}
var yyTok3 = [...]int{
0,
@ -506,13 +516,13 @@ yydefault:
case 1:
yyDollar = yyS[yypt-2 : yypt+1]
//line expressions.y:33
//line expressions.y:35
{
yylex.(*lexer).val = yyDollar[1].f
}
case 2:
yyDollar = yyS[yypt-5 : yypt+1]
//line expressions.y:34
//line expressions.y:36
{
name, expr := yyDollar[2].name, yyDollar[4].f
yylex.(*lexer).val = func(ctx Context) interface{} {
@ -521,29 +531,60 @@ yydefault:
}
}
case 3:
yyDollar = yyS[yypt-2 : yypt+1]
//line expressions.y:41
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:43
{
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
}
}
case 4:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:53
{
yylex.(*lexer).val = yyDollar[2].f
}
case 4:
yyDollar = yyS[yypt-5 : yypt+1]
//line expressions.y:44
case 5:
yyDollar = yyS[yypt-2 : yypt+1]
//line expressions.y:57
{
yyVAL.arglist = append([]func(Context) interface{}{yyDollar[1].f}, yyDollar[2].arglist...)
}
case 6:
yyDollar = yyS[yypt-0 : yypt+1]
//line expressions.y:59
{
yyVAL.arglist = []func(Context) interface{}{}
}
case 7:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:60
{
yyVAL.arglist = append([]func(Context) interface{}{yyDollar[2].f}, yyDollar[3].arglist...)
}
case 8:
yyDollar = yyS[yypt-4 : yypt+1]
//line expressions.y:63
{
name, expr, mods := yyDollar[1].name, yyDollar[3].f, yyDollar[4].loopmods
yyVAL.f = func(ctx Context) interface{} {
return &Loop{name, expr(ctx), mods}
}
}
case 5:
case 9:
yyDollar = yyS[yypt-0 : yypt+1]
//line expressions.y:52
//line expressions.y:71
{
yyVAL.loopmods = loopModifiers{}
}
case 6:
case 10:
yyDollar = yyS[yypt-2 : yypt+1]
//line expressions.y:53
//line expressions.y:72
{
switch yyDollar[2].name {
case "reversed":
@ -553,9 +594,9 @@ yydefault:
}
yyVAL.loopmods = yyDollar[1].loopmods
}
case 7:
case 11:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:62
//line expressions.y:81
{ // TODO can this be a variable?
switch yyDollar[2].name {
case "limit":
@ -575,65 +616,65 @@ yydefault:
}
yyVAL.loopmods = yyDollar[1].loopmods
}
case 8:
case 12:
yyDollar = yyS[yypt-1 : yypt+1]
//line expressions.y:84
//line expressions.y:103
{
val := yyDollar[1].val
yyVAL.f = func(_ Context) interface{} { return val }
}
case 9:
case 13:
yyDollar = yyS[yypt-1 : yypt+1]
//line expressions.y:85
//line expressions.y:104
{
name := yyDollar[1].name
yyVAL.f = func(ctx Context) interface{} { return ctx.Get(name) }
}
case 10:
case 14:
yyDollar = yyS[yypt-2 : yypt+1]
//line expressions.y:86
//line expressions.y:105
{
yyVAL.f = makeObjectPropertyExpr(yyDollar[1].f, yyDollar[2].name)
}
case 11:
case 15:
yyDollar = yyS[yypt-4 : yypt+1]
//line expressions.y:87
//line expressions.y:106
{
yyVAL.f = makeIndexExpr(yyDollar[1].f, yyDollar[3].f)
}
case 12:
case 16:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:88
//line expressions.y:107
{
yyVAL.f = yyDollar[2].f
}
case 14:
case 18:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:93
//line expressions.y:112
{
yyVAL.f = makeFilter(yyDollar[1].f, yyDollar[3].name, nil)
}
case 15:
case 19:
yyDollar = yyS[yypt-4 : yypt+1]
//line expressions.y:94
//line expressions.y:113
{
yyVAL.f = makeFilter(yyDollar[1].f, yyDollar[3].name, yyDollar[4].filter_params)
}
case 16:
case 20:
yyDollar = yyS[yypt-1 : yypt+1]
//line expressions.y:98
//line expressions.y:117
{
yyVAL.filter_params = []valueFn{yyDollar[1].f}
}
case 17:
case 21:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:100
//line expressions.y:119
{
yyVAL.filter_params = append(yyDollar[1].filter_params, yyDollar[3].f)
}
case 19:
case 23:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:104
//line expressions.y:123
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -641,9 +682,9 @@ yydefault:
return evaluator.Equal(a, b)
}
}
case 20:
case 24:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:111
//line expressions.y:130
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -651,9 +692,9 @@ yydefault:
return !evaluator.Equal(a, b)
}
}
case 21:
case 25:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:118
//line expressions.y:137
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -661,9 +702,9 @@ yydefault:
return evaluator.Less(b, a)
}
}
case 22:
case 26:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:125
//line expressions.y:144
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -671,9 +712,9 @@ yydefault:
return evaluator.Less(a, b)
}
}
case 23:
case 27:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:132
//line expressions.y:151
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -681,9 +722,9 @@ yydefault:
return evaluator.Less(b, a) || evaluator.Equal(a, b)
}
}
case 24:
case 28:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:139
//line expressions.y:158
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -691,24 +732,24 @@ yydefault:
return evaluator.Less(a, b) || evaluator.Equal(a, b)
}
}
case 25:
case 29:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:146
//line expressions.y:165
{
yyVAL.f = makeContainsExpr(yyDollar[1].f, yyDollar[3].f)
}
case 27:
case 31:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:151
//line expressions.y:170
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
return evaluator.IsTrue(fa(ctx)) && evaluator.IsTrue(fb(ctx))
}
}
case 28:
case 32:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:157
//line expressions.y:176
{
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {

View File

@ -15,10 +15,10 @@ func addCompilerTestTags(s Config) {
}
var compilerErrorTests = []struct{ in, expected string }{
{"{% unknown_tag %}", "unknown tag"},
{`{% unknown_tag %}`, "unknown tag"},
{`{% block %}{% endblock %}`, "block compiler error"},
// {`{% tag %}`, "tag compiler error"},
// {"{%for syntax error%}{%endfor%}", "parse error"},
// {`{%for syntax error%}{%endfor%}`, "parse error"},
}
func TestCompileErrors(t *testing.T) {

View File

@ -9,21 +9,54 @@ import (
"github.com/osteele/liquid/render"
)
const forloopVarName = "forloop"
var errLoopContinueLoop = fmt.Errorf("continue outside a loop")
var errLoopBreak = fmt.Errorf("break outside a loop")
func breakTag(parameters string) (func(io.Writer, render.Context) error, error) {
func breakTag(string) (func(io.Writer, render.Context) error, error) {
return func(_ io.Writer, ctx render.Context) error {
return ctx.WrapError(errLoopBreak)
}, nil
}
func continueTag(parameters string) (func(io.Writer, render.Context) error, error) {
func continueTag(string) (func(io.Writer, render.Context) error, error) {
return func(_ io.Writer, ctx render.Context) error {
return ctx.WrapError(errLoopContinueLoop)
}, nil
}
func cycleTag(args string) (func(io.Writer, render.Context) error, error) {
return func(w io.Writer, ctx render.Context) error {
expr, err := expression.Parse("{%cycle " + args)
if err != nil {
return err
}
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")
}
// the next few lines could panic if the user spoofs us by creating their own loop object
// “C++ protects against accident, not against fraud.” Bjarne Stroustrup
loopRec := loopVar.(map[string]interface{})
cycleMap := loopRec[".cycles"].(map[string]int)
group := ""
n := cycleMap[group]
cycleMap[group] = n + 1
fmt.Println(cycleMap)
_, err = w.Write([]byte(fmt.Sprint(array[n%len(array)])))
return err
}, nil
}
func parseLoopExpression(source string) (expression.Expression, error) {
expr, err := expression.Parse("%loop " + source)
if err != nil {
@ -69,11 +102,11 @@ func loopTagParser(node render.BlockNode) (func(io.Writer, render.Context) error
length = rt.Len()
}
}
const forloopName = "forloop"
defer func(index, forloop interface{}) {
ctx.Set(forloopName, index)
ctx.Set(forloopVarName, index)
ctx.Set(loop.Variable, forloop)
}(ctx.Get(forloopName), ctx.Get(loop.Variable))
}(ctx.Get(forloopVarName), ctx.Get(loop.Variable))
cycleMap := map[string]int{}
loop:
for i := 0; i < length; i++ {
j := i
@ -81,7 +114,7 @@ func loopTagParser(node render.BlockNode) (func(io.Writer, render.Context) error
j = rt.Len() - 1 - i
}
ctx.Set(loop.Variable, rt.Index(j).Interface())
ctx.Set(forloopName, map[string]interface{}{
ctx.Set(forloopVarName, map[string]interface{}{
"first": i == 0,
"last": i == length-1,
"index": i + 1,
@ -89,6 +122,7 @@ func loopTagParser(node render.BlockNode) (func(io.Writer, render.Context) error
"rindex": length - i,
"rindex0": length - i - 1,
"length": length,
".cycles": cycleMap,
})
err := ctx.RenderChildren(w)
switch {

View File

@ -55,7 +55,11 @@ var loopTests = []struct{ in, expected string }{
{`{% for a in array %}{% if a == 'second' %}{% break %}{% endif %}{{ a }}{% endfor %}`, "first"},
{`{% for a in array %}{% if a == 'second' %}{% continue %}{% endif %}{{ a }}.{% endfor %}`, "first.third."},
// hash
{`{% for a in hash %}{{ a }}{% endfor %}`, "a"},
// cycle
{`{% for a in array %}{% cycle 'even', 'odd' %}.{% endfor %}`, "even.odd.even."},
}
var loopErrorTests = []struct{ in, expected string }{

View File

@ -19,6 +19,7 @@ func AddStandardTags(c render.Config) {
// but it ignores any syntax specified here.
c.AddTag("break", breakTag)
c.AddTag("continue", continueTag)
c.AddTag("cycle", cycleTag)
c.AddBlock("capture").Compiler(captureTagParser)
c.AddBlock("case").Clause("when").Clause("else").Compiler(caseTagParser)
c.AddBlock("comment")