1
0
mirror of https://github.com/danog/liquid.git synced 2024-12-02 14:57:49 +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: 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 %}` - `{% when a or b %}`
- Loop ranges `{% for a in 1...10 %}` - Loop ranges `{% for a in 1...10 %}`
- Error modes - Error modes

View File

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

View File

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

View File

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

View File

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

View File

@ -9,21 +9,54 @@ import (
"github.com/osteele/liquid/render" "github.com/osteele/liquid/render"
) )
const forloopVarName = "forloop"
var errLoopContinueLoop = fmt.Errorf("continue outside a loop") var errLoopContinueLoop = fmt.Errorf("continue outside a loop")
var errLoopBreak = fmt.Errorf("break 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 func(_ io.Writer, ctx render.Context) error {
return ctx.WrapError(errLoopBreak) return ctx.WrapError(errLoopBreak)
}, nil }, 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 func(_ io.Writer, ctx render.Context) error {
return ctx.WrapError(errLoopContinueLoop) return ctx.WrapError(errLoopContinueLoop)
}, nil }, 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) { func parseLoopExpression(source string) (expression.Expression, error) {
expr, err := expression.Parse("%loop " + source) expr, err := expression.Parse("%loop " + source)
if err != nil { if err != nil {
@ -69,11 +102,11 @@ func loopTagParser(node render.BlockNode) (func(io.Writer, render.Context) error
length = rt.Len() length = rt.Len()
} }
} }
const forloopName = "forloop"
defer func(index, forloop interface{}) { defer func(index, forloop interface{}) {
ctx.Set(forloopName, index) ctx.Set(forloopVarName, index)
ctx.Set(loop.Variable, forloop) ctx.Set(loop.Variable, forloop)
}(ctx.Get(forloopName), ctx.Get(loop.Variable)) }(ctx.Get(forloopVarName), ctx.Get(loop.Variable))
cycleMap := map[string]int{}
loop: loop:
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
j := i j := i
@ -81,7 +114,7 @@ func loopTagParser(node render.BlockNode) (func(io.Writer, render.Context) error
j = rt.Len() - 1 - i j = rt.Len() - 1 - i
} }
ctx.Set(loop.Variable, rt.Index(j).Interface()) ctx.Set(loop.Variable, rt.Index(j).Interface())
ctx.Set(forloopName, map[string]interface{}{ ctx.Set(forloopVarName, map[string]interface{}{
"first": i == 0, "first": i == 0,
"last": i == length-1, "last": i == length-1,
"index": i + 1, "index": i + 1,
@ -89,6 +122,7 @@ func loopTagParser(node render.BlockNode) (func(io.Writer, render.Context) error
"rindex": length - i, "rindex": length - i,
"rindex0": length - i - 1, "rindex0": length - i - 1,
"length": length, "length": length,
".cycles": cycleMap,
}) })
err := ctx.RenderChildren(w) err := ctx.RenderChildren(w)
switch { 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' %}{% break %}{% endif %}{{ a }}{% endfor %}`, "first"},
{`{% for a in array %}{% if a == 'second' %}{% continue %}{% endif %}{{ a }}.{% endfor %}`, "first.third."}, {`{% for a in array %}{% if a == 'second' %}{% continue %}{% endif %}{{ a }}.{% endfor %}`, "first.third."},
// hash
{`{% for a in hash %}{{ a }}{% endfor %}`, "a"}, {`{% 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 }{ var loopErrorTests = []struct{ in, expected string }{

View File

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