liquid-c 4.0.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/liquid.yml +45 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +14 -0
- data/Gemfile +14 -6
- data/README.md +29 -5
- data/Rakefile +13 -62
- data/ext/liquid_c/block.c +488 -60
- data/ext/liquid_c/block.h +28 -2
- data/ext/liquid_c/c_buffer.c +42 -0
- data/ext/liquid_c/c_buffer.h +76 -0
- data/ext/liquid_c/context.c +233 -0
- data/ext/liquid_c/context.h +70 -0
- data/ext/liquid_c/document_body.c +89 -0
- data/ext/liquid_c/document_body.h +59 -0
- data/ext/liquid_c/expression.c +116 -0
- data/ext/liquid_c/expression.h +24 -0
- data/ext/liquid_c/extconf.rb +22 -6
- data/ext/liquid_c/intutil.h +22 -0
- data/ext/liquid_c/lexer.c +6 -2
- data/ext/liquid_c/lexer.h +18 -3
- data/ext/liquid_c/liquid.c +76 -6
- data/ext/liquid_c/liquid.h +24 -1
- data/ext/liquid_c/parse_context.c +76 -0
- data/ext/liquid_c/parse_context.h +13 -0
- data/ext/liquid_c/parser.c +141 -65
- data/ext/liquid_c/parser.h +4 -2
- data/ext/liquid_c/raw.c +110 -0
- data/ext/liquid_c/raw.h +6 -0
- data/ext/liquid_c/resource_limits.c +279 -0
- data/ext/liquid_c/resource_limits.h +23 -0
- data/ext/liquid_c/stringutil.h +44 -0
- data/ext/liquid_c/tokenizer.c +149 -35
- data/ext/liquid_c/tokenizer.h +20 -9
- data/ext/liquid_c/usage.c +18 -0
- data/ext/liquid_c/usage.h +9 -0
- data/ext/liquid_c/variable.c +196 -20
- data/ext/liquid_c/variable.h +18 -1
- data/ext/liquid_c/variable_lookup.c +44 -0
- data/ext/liquid_c/variable_lookup.h +8 -0
- data/ext/liquid_c/vm.c +588 -0
- data/ext/liquid_c/vm.h +25 -0
- data/ext/liquid_c/vm_assembler.c +491 -0
- data/ext/liquid_c/vm_assembler.h +240 -0
- data/ext/liquid_c/vm_assembler_pool.c +97 -0
- data/ext/liquid_c/vm_assembler_pool.h +27 -0
- data/lib/liquid/c/compile_ext.rb +44 -0
- data/lib/liquid/c/version.rb +3 -1
- data/lib/liquid/c.rb +225 -46
- data/liquid-c.gemspec +18 -10
- data/performance/c_profile.rb +23 -0
- data/performance.rb +6 -4
- data/rakelib/compile.rake +15 -0
- data/rakelib/integration_test.rake +43 -0
- data/rakelib/performance.rake +43 -0
- data/rakelib/rubocop.rake +6 -0
- data/rakelib/unit_test.rake +14 -0
- data/test/integration_test.rb +11 -0
- data/test/liquid_test_helper.rb +21 -0
- data/test/test_helper.rb +14 -2
- data/test/unit/block_test.rb +130 -0
- data/test/unit/context_test.rb +83 -0
- data/test/unit/expression_test.rb +186 -0
- data/test/unit/gc_stress_test.rb +28 -0
- data/test/unit/raw_test.rb +19 -0
- data/test/unit/resource_limits_test.rb +50 -0
- data/test/unit/tokenizer_test.rb +90 -20
- data/test/unit/variable_test.rb +212 -60
- metadata +64 -16
- data/.travis.yml +0 -11
- data/test/liquid_test.rb +0 -11
data/ext/liquid_c/tokenizer.c
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
#include <assert.h>
|
1
2
|
#include "liquid.h"
|
2
3
|
#include "tokenizer.h"
|
4
|
+
#include "stringutil.h"
|
3
5
|
|
4
6
|
VALUE cLiquidTokenizer;
|
5
7
|
|
@@ -35,40 +37,89 @@ static VALUE tokenizer_allocate(VALUE klass)
|
|
35
37
|
|
36
38
|
obj = TypedData_Make_Struct(klass, tokenizer_t, &tokenizer_data_type, tokenizer);
|
37
39
|
tokenizer->source = Qnil;
|
40
|
+
tokenizer->bug_compatible_whitespace_trimming = false;
|
41
|
+
tokenizer->raw_tag_body = NULL;
|
42
|
+
tokenizer->raw_tag_body_len = 0;
|
38
43
|
return obj;
|
39
44
|
}
|
40
45
|
|
41
|
-
static VALUE tokenizer_initialize_method(VALUE self, VALUE source, VALUE
|
46
|
+
static VALUE tokenizer_initialize_method(VALUE self, VALUE source, VALUE start_line_number, VALUE for_liquid_tag)
|
42
47
|
{
|
43
48
|
tokenizer_t *tokenizer;
|
44
49
|
|
45
50
|
Check_Type(source, T_STRING);
|
51
|
+
check_utf8_encoding(source, "source");
|
52
|
+
|
53
|
+
#define MAX_SOURCE_CODE_BYTES ((1 << 24) - 1)
|
54
|
+
if (RSTRING_LEN(source) > MAX_SOURCE_CODE_BYTES) {
|
55
|
+
rb_enc_raise(utf8_encoding, rb_eArgError, "Source too large, max %d bytes", MAX_SOURCE_CODE_BYTES);
|
56
|
+
}
|
57
|
+
#undef MAX_SOURCE_CODE_BYTES
|
58
|
+
|
46
59
|
Tokenizer_Get_Struct(self, tokenizer);
|
47
60
|
source = rb_str_dup_frozen(source);
|
48
61
|
tokenizer->source = source;
|
49
62
|
tokenizer->cursor = RSTRING_PTR(source);
|
50
|
-
tokenizer->
|
51
|
-
tokenizer->lstrip_flag =
|
63
|
+
tokenizer->cursor_end = tokenizer->cursor + RSTRING_LEN(source);
|
64
|
+
tokenizer->lstrip_flag = false;
|
52
65
|
// tokenizer->line_number keeps track of the current line number or it is 0
|
53
66
|
// to indicate that line numbers aren't being calculated
|
54
|
-
tokenizer->line_number =
|
67
|
+
tokenizer->line_number = FIX2UINT(start_line_number);
|
68
|
+
tokenizer->for_liquid_tag = RTEST(for_liquid_tag);
|
55
69
|
return Qnil;
|
56
70
|
}
|
57
71
|
|
58
|
-
|
72
|
+
// Internal function to setup an existing tokenizer from C for a liquid tag.
|
73
|
+
// This overwrites the passed in tokenizer, so a copy of the struct should
|
74
|
+
// be used to reset the tokenizer after parsing the liquid tag.
|
75
|
+
void tokenizer_setup_for_liquid_tag(tokenizer_t *tokenizer, const char *cursor, const char *cursor_end, int line_number)
|
59
76
|
{
|
60
|
-
|
61
|
-
|
62
|
-
|
77
|
+
tokenizer->cursor = cursor;
|
78
|
+
tokenizer->cursor_end = cursor_end;
|
79
|
+
tokenizer->lstrip_flag = false;
|
80
|
+
tokenizer->line_number = line_number;
|
81
|
+
tokenizer->for_liquid_tag = true;
|
82
|
+
}
|
83
|
+
|
84
|
+
// Tokenizes contents of {% liquid ... %}
|
85
|
+
static void tokenizer_next_for_liquid_tag(tokenizer_t *tokenizer, token_t *token)
|
86
|
+
{
|
87
|
+
const char *end = tokenizer->cursor_end;
|
88
|
+
const char *start = tokenizer->cursor;
|
89
|
+
const char *start_trimmed = read_while(start, end, is_non_newline_space);
|
90
|
+
|
91
|
+
token->str_full = start;
|
92
|
+
token->str_trimmed = start_trimmed;
|
93
|
+
|
94
|
+
const char *end_full = read_while(start_trimmed, end, not_newline);
|
95
|
+
if (end_full < end) {
|
96
|
+
tokenizer->cursor = end_full + 1;
|
97
|
+
if (tokenizer->line_number)
|
98
|
+
tokenizer->line_number++;
|
99
|
+
} else {
|
100
|
+
tokenizer->cursor = end_full;
|
63
101
|
}
|
64
102
|
|
103
|
+
const char *end_trimmed = read_while_reverse(start_trimmed, end_full, rb_isspace);
|
104
|
+
|
105
|
+
token->len_trimmed = end_trimmed - start_trimmed;
|
106
|
+
token->len_full = end_full - token->str_full;
|
107
|
+
|
108
|
+
if (token->len_trimmed == 0) {
|
109
|
+
token->type = TOKEN_BLANK_LIQUID_TAG_LINE;
|
110
|
+
} else {
|
111
|
+
token->type = TOKEN_TAG;
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
// Tokenizes contents of a full Liquid template
|
116
|
+
static void tokenizer_next_for_template(tokenizer_t *tokenizer, token_t *token)
|
117
|
+
{
|
65
118
|
const char *cursor = tokenizer->cursor;
|
66
|
-
const char *last =
|
119
|
+
const char *last = tokenizer->cursor_end - 1;
|
67
120
|
|
68
|
-
token->
|
121
|
+
token->str_full = cursor;
|
69
122
|
token->type = TOKEN_RAW;
|
70
|
-
token->lstrip = 0;
|
71
|
-
token->rstrip = 0;
|
72
123
|
|
73
124
|
while (cursor < last) {
|
74
125
|
if (*cursor++ != '{')
|
@@ -78,17 +129,17 @@ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
|
|
78
129
|
if (c != '%' && c != '{')
|
79
130
|
continue;
|
80
131
|
if (cursor <= last && *cursor == '-') {
|
81
|
-
|
82
|
-
|
132
|
+
cursor++;
|
133
|
+
token->rstrip = 1;
|
83
134
|
}
|
84
135
|
if (cursor - tokenizer->cursor > (ptrdiff_t)(2 + token->rstrip)) {
|
85
136
|
token->type = TOKEN_RAW;
|
86
137
|
cursor -= 2 + token->rstrip;
|
87
138
|
token->lstrip = tokenizer->lstrip_flag;
|
88
|
-
tokenizer->lstrip_flag =
|
139
|
+
tokenizer->lstrip_flag = false;
|
89
140
|
goto found;
|
90
141
|
}
|
91
|
-
tokenizer->lstrip_flag =
|
142
|
+
tokenizer->lstrip_flag = false;
|
92
143
|
token->type = TOKEN_INVALID;
|
93
144
|
token->lstrip = token->rstrip;
|
94
145
|
token->rstrip = 0;
|
@@ -103,12 +154,12 @@ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
|
|
103
154
|
continue;
|
104
155
|
token->type = TOKEN_TAG;
|
105
156
|
if(cursor[-3] == '-')
|
106
|
-
token->rstrip = tokenizer->lstrip_flag =
|
157
|
+
token->rstrip = tokenizer->lstrip_flag = true;
|
107
158
|
goto found;
|
108
159
|
}
|
109
160
|
// unterminated tag
|
110
161
|
cursor = tokenizer->cursor + 2;
|
111
|
-
tokenizer->lstrip_flag =
|
162
|
+
tokenizer->lstrip_flag = false;
|
112
163
|
goto found;
|
113
164
|
} else {
|
114
165
|
while (cursor < last) {
|
@@ -121,31 +172,51 @@ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
|
|
121
172
|
}
|
122
173
|
token->type = TOKEN_VARIABLE;
|
123
174
|
if(cursor[-3] == '-')
|
124
|
-
token->rstrip = tokenizer->lstrip_flag =
|
175
|
+
token->rstrip = tokenizer->lstrip_flag = true;
|
125
176
|
goto found;
|
126
177
|
}
|
127
178
|
// unterminated variable
|
128
179
|
cursor = tokenizer->cursor + 2;
|
129
|
-
tokenizer->lstrip_flag =
|
180
|
+
tokenizer->lstrip_flag = false;
|
130
181
|
goto found;
|
131
182
|
}
|
132
183
|
}
|
133
184
|
cursor = last + 1;
|
134
185
|
token->lstrip = tokenizer->lstrip_flag;
|
135
|
-
tokenizer->lstrip_flag =
|
186
|
+
tokenizer->lstrip_flag = false;
|
136
187
|
found:
|
137
|
-
token->
|
138
|
-
|
139
|
-
|
188
|
+
token->len_full = cursor - token->str_full;
|
189
|
+
|
190
|
+
token->str_trimmed = token->str_full;
|
191
|
+
token->len_trimmed = token->len_full;
|
192
|
+
|
193
|
+
if (token->type == TOKEN_VARIABLE || token->type == TOKEN_TAG) {
|
194
|
+
token->str_trimmed += 2 + token->lstrip;
|
195
|
+
token->len_trimmed -= 2 + token->lstrip + 2;
|
196
|
+
if (token->rstrip && token->len_trimmed)
|
197
|
+
token->len_trimmed--;
|
198
|
+
}
|
199
|
+
|
200
|
+
assert(token->len_trimmed >= 0);
|
201
|
+
|
202
|
+
tokenizer->cursor += token->len_full;
|
140
203
|
|
141
204
|
if (tokenizer->line_number) {
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
205
|
+
tokenizer->line_number += count_newlines(token->str_full, token->str_full + token->len_full);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
|
210
|
+
{
|
211
|
+
memset(token, 0, sizeof(*token));
|
212
|
+
|
213
|
+
if (tokenizer->cursor >= tokenizer->cursor_end) {
|
214
|
+
return;
|
215
|
+
}
|
216
|
+
if (tokenizer->for_liquid_tag) {
|
217
|
+
tokenizer_next_for_liquid_tag(tokenizer, token);
|
218
|
+
} else {
|
219
|
+
tokenizer_next_for_template(tokenizer, token);
|
149
220
|
}
|
150
221
|
}
|
151
222
|
|
@@ -159,7 +230,25 @@ static VALUE tokenizer_shift_method(VALUE self)
|
|
159
230
|
if (!token.type)
|
160
231
|
return Qnil;
|
161
232
|
|
162
|
-
|
233
|
+
// When sent back to Ruby, tokens are the raw string including whitespace
|
234
|
+
// and tag delimiters. It should be possible to reconstruct the exact
|
235
|
+
// template from the tokens.
|
236
|
+
return rb_enc_str_new(token.str_full, token.len_full, utf8_encoding);
|
237
|
+
}
|
238
|
+
|
239
|
+
static VALUE tokenizer_shift_trimmed_method(VALUE self)
|
240
|
+
{
|
241
|
+
tokenizer_t *tokenizer;
|
242
|
+
Tokenizer_Get_Struct(self, tokenizer);
|
243
|
+
|
244
|
+
token_t token;
|
245
|
+
tokenizer_next(tokenizer, &token);
|
246
|
+
if (!token.type)
|
247
|
+
return Qnil;
|
248
|
+
|
249
|
+
// This method doesn't include whitespace and tag delimiters. It allows for
|
250
|
+
// testing the output of tokenizer_next as used by rb_block_parse.
|
251
|
+
return rb_enc_str_new(token.str_trimmed, token.len_trimmed, utf8_encoding);
|
163
252
|
}
|
164
253
|
|
165
254
|
static VALUE tokenizer_line_number_method(VALUE self)
|
@@ -173,12 +262,37 @@ static VALUE tokenizer_line_number_method(VALUE self)
|
|
173
262
|
return UINT2NUM(tokenizer->line_number);
|
174
263
|
}
|
175
264
|
|
176
|
-
|
265
|
+
static VALUE tokenizer_for_liquid_tag_method(VALUE self)
|
266
|
+
{
|
267
|
+
tokenizer_t *tokenizer;
|
268
|
+
Tokenizer_Get_Struct(self, tokenizer);
|
269
|
+
|
270
|
+
return tokenizer->for_liquid_tag ? Qtrue : Qfalse;
|
271
|
+
}
|
272
|
+
|
273
|
+
|
274
|
+
// Temporary to test rollout of the fix for this bug
|
275
|
+
static VALUE tokenizer_bug_compatible_whitespace_trimming(VALUE self) {
|
276
|
+
tokenizer_t *tokenizer;
|
277
|
+
Tokenizer_Get_Struct(self, tokenizer);
|
278
|
+
|
279
|
+
tokenizer->bug_compatible_whitespace_trimming = true;
|
280
|
+
return Qnil;
|
281
|
+
}
|
282
|
+
|
283
|
+
void liquid_define_tokenizer(void)
|
177
284
|
{
|
178
285
|
cLiquidTokenizer = rb_define_class_under(mLiquidC, "Tokenizer", rb_cObject);
|
286
|
+
rb_global_variable(&cLiquidTokenizer);
|
287
|
+
|
179
288
|
rb_define_alloc_func(cLiquidTokenizer, tokenizer_allocate);
|
180
|
-
rb_define_method(cLiquidTokenizer, "initialize", tokenizer_initialize_method,
|
181
|
-
rb_define_method(cLiquidTokenizer, "shift", tokenizer_shift_method, 0);
|
289
|
+
rb_define_method(cLiquidTokenizer, "initialize", tokenizer_initialize_method, 3);
|
182
290
|
rb_define_method(cLiquidTokenizer, "line_number", tokenizer_line_number_method, 0);
|
291
|
+
rb_define_method(cLiquidTokenizer, "for_liquid_tag", tokenizer_for_liquid_tag_method, 0);
|
292
|
+
rb_define_method(cLiquidTokenizer, "bug_compatible_whitespace_trimming!", tokenizer_bug_compatible_whitespace_trimming, 0);
|
293
|
+
|
294
|
+
// For testing the internal token representation.
|
295
|
+
rb_define_private_method(cLiquidTokenizer, "shift", tokenizer_shift_method, 0);
|
296
|
+
rb_define_private_method(cLiquidTokenizer, "shift_trimmed", tokenizer_shift_trimmed_method, 0);
|
183
297
|
}
|
184
298
|
|
data/ext/liquid_c/tokenizer.h
CHANGED
@@ -6,31 +6,42 @@ enum token_type {
|
|
6
6
|
TOKEN_INVALID,
|
7
7
|
TOKEN_RAW,
|
8
8
|
TOKEN_TAG,
|
9
|
-
TOKEN_VARIABLE
|
9
|
+
TOKEN_VARIABLE,
|
10
|
+
TOKEN_BLANK_LIQUID_TAG_LINE
|
10
11
|
};
|
11
12
|
|
12
13
|
typedef struct token {
|
13
14
|
enum token_type type;
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
|
16
|
+
// str_trimmed contains no tag delimiters
|
17
|
+
const char *str_trimmed, *str_full;
|
18
|
+
long len_trimmed, len_full;
|
19
|
+
|
20
|
+
bool lstrip, rstrip;
|
18
21
|
} token_t;
|
19
22
|
|
20
23
|
typedef struct tokenizer {
|
21
24
|
VALUE source;
|
22
|
-
const char *cursor;
|
23
|
-
long length;
|
25
|
+
const char *cursor, *cursor_end;
|
24
26
|
unsigned int line_number;
|
25
|
-
|
27
|
+
bool lstrip_flag;
|
28
|
+
bool for_liquid_tag;
|
29
|
+
|
30
|
+
// Temporary to test rollout of the fix for this bug
|
31
|
+
bool bug_compatible_whitespace_trimming;
|
32
|
+
|
33
|
+
char *raw_tag_body;
|
34
|
+
unsigned int raw_tag_body_len;
|
26
35
|
} tokenizer_t;
|
27
36
|
|
28
37
|
extern VALUE cLiquidTokenizer;
|
29
38
|
extern const rb_data_type_t tokenizer_data_type;
|
30
39
|
#define Tokenizer_Get_Struct(obj, sval) TypedData_Get_Struct(obj, tokenizer_t, &tokenizer_data_type, sval)
|
31
40
|
|
32
|
-
void
|
41
|
+
void liquid_define_tokenizer(void);
|
33
42
|
void tokenizer_next(tokenizer_t *tokenizer, token_t *token);
|
34
43
|
|
44
|
+
void tokenizer_setup_for_liquid_tag(tokenizer_t *tokenizer, const char *cursor, const char *cursor_end, int line_number);
|
45
|
+
|
35
46
|
#endif
|
36
47
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#include "usage.h"
|
2
|
+
|
3
|
+
static VALUE cLiquidUsage;
|
4
|
+
static ID id_increment;
|
5
|
+
|
6
|
+
void usage_increment(const char *name)
|
7
|
+
{
|
8
|
+
VALUE name_str = rb_str_new_cstr(name);
|
9
|
+
rb_funcall(cLiquidUsage, id_increment, 1, name_str);
|
10
|
+
}
|
11
|
+
|
12
|
+
void liquid_define_usage(void)
|
13
|
+
{
|
14
|
+
cLiquidUsage = rb_const_get(mLiquid, rb_intern("Usage"));
|
15
|
+
rb_global_variable(&cLiquidUsage);
|
16
|
+
|
17
|
+
id_increment = rb_intern("increment");
|
18
|
+
}
|
data/ext/liquid_c/variable.c
CHANGED
@@ -1,25 +1,43 @@
|
|
1
1
|
#include "liquid.h"
|
2
2
|
#include "variable.h"
|
3
3
|
#include "parser.h"
|
4
|
+
#include "expression.h"
|
5
|
+
#include "vm.h"
|
6
|
+
|
4
7
|
#include <stdio.h>
|
5
8
|
|
6
|
-
static
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
static ID id_rescue_strict_parse_syntax_error;
|
10
|
+
|
11
|
+
static ID id_ivar_parse_context;
|
12
|
+
static ID id_ivar_name;
|
13
|
+
static ID id_ivar_filters;
|
10
14
|
|
15
|
+
VALUE cLiquidCVariableExpression;
|
16
|
+
|
17
|
+
static VALUE frozen_empty_array;
|
18
|
+
|
19
|
+
static VALUE try_variable_strict_parse(VALUE uncast_args)
|
20
|
+
{
|
21
|
+
variable_parse_args_t *parse_args = (void *)uncast_args;
|
11
22
|
parser_t p;
|
12
|
-
init_parser(&p,
|
23
|
+
init_parser(&p, parse_args->markup, parse_args->markup_end);
|
24
|
+
vm_assembler_t *code = parse_args->code;
|
13
25
|
|
14
|
-
if (p.cur.type == TOKEN_EOS)
|
26
|
+
if (p.cur.type == TOKEN_EOS) {
|
27
|
+
vm_assembler_add_push_nil(code);
|
15
28
|
return Qnil;
|
29
|
+
}
|
16
30
|
|
17
|
-
|
31
|
+
parse_and_compile_expression(&p, code);
|
18
32
|
|
19
33
|
while (parser_consume(&p, TOKEN_PIPE).type) {
|
20
|
-
lexer_token_t
|
34
|
+
lexer_token_t filter_name_token = parser_must_consume(&p, TOKEN_IDENTIFIER);
|
35
|
+
VALUE filter_name = token_to_rsym(filter_name_token);
|
21
36
|
|
22
|
-
|
37
|
+
size_t arg_count = 0;
|
38
|
+
size_t keyword_arg_count = 0;
|
39
|
+
VALUE push_keywords_obj = Qnil;
|
40
|
+
vm_assembler_t *push_keywords_code = NULL;
|
23
41
|
|
24
42
|
if (parser_consume(&p, TOKEN_COLON).type) {
|
25
43
|
do {
|
@@ -27,28 +45,186 @@ static VALUE rb_variable_parse(VALUE self, VALUE markup, VALUE filters)
|
|
27
45
|
VALUE key = token_to_rstr(parser_consume_any(&p));
|
28
46
|
parser_consume_any(&p);
|
29
47
|
|
30
|
-
|
31
|
-
|
48
|
+
keyword_arg_count++;
|
49
|
+
|
50
|
+
if (push_keywords_obj == Qnil) {
|
51
|
+
expression_t *push_keywords_expr;
|
52
|
+
// use an object to automatically free on an exception
|
53
|
+
push_keywords_obj = expression_new(cLiquidCExpression, &push_keywords_expr);
|
54
|
+
rb_obj_hide(push_keywords_obj);
|
55
|
+
push_keywords_code = &push_keywords_expr->code;
|
56
|
+
}
|
57
|
+
|
58
|
+
vm_assembler_add_push_const(push_keywords_code, key);
|
59
|
+
parse_and_compile_expression(&p, push_keywords_code);
|
32
60
|
} else {
|
33
|
-
|
61
|
+
parse_and_compile_expression(&p, code);
|
62
|
+
arg_count++;
|
34
63
|
}
|
35
64
|
} while (parser_consume(&p, TOKEN_COMMA).type);
|
36
65
|
}
|
37
66
|
|
38
|
-
if (
|
39
|
-
|
40
|
-
|
41
|
-
|
67
|
+
if (keyword_arg_count) {
|
68
|
+
arg_count++;
|
69
|
+
if (keyword_arg_count > 255)
|
70
|
+
rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "Too many filter keyword arguments");
|
71
|
+
|
72
|
+
vm_assembler_concat(code, push_keywords_code);
|
73
|
+
vm_assembler_add_hash_new(code, keyword_arg_count);
|
74
|
+
|
75
|
+
RB_GC_GUARD(push_keywords_obj);
|
42
76
|
}
|
43
|
-
|
77
|
+
vm_assembler_add_filter(code, filter_name, arg_count);
|
44
78
|
}
|
45
79
|
|
46
80
|
parser_must_consume(&p, TOKEN_EOS);
|
47
|
-
|
81
|
+
|
82
|
+
return Qnil;
|
83
|
+
}
|
84
|
+
|
85
|
+
typedef struct variable_strict_parse_rescue {
|
86
|
+
variable_parse_args_t *parse_args;
|
87
|
+
size_t instructions_size;
|
88
|
+
size_t constants_size;
|
89
|
+
size_t stack_size;
|
90
|
+
} variable_strict_parse_rescue_t;
|
91
|
+
|
92
|
+
static VALUE variable_strict_parse_rescue(VALUE uncast_args, VALUE exception)
|
93
|
+
{
|
94
|
+
variable_strict_parse_rescue_t *rescue_args = (void *)uncast_args;
|
95
|
+
variable_parse_args_t *parse_args = rescue_args->parse_args;
|
96
|
+
vm_assembler_t *code = parse_args->code;
|
97
|
+
|
98
|
+
// undo partial strict parse
|
99
|
+
uint8_t *last_constants_data_end = (uint8_t *)code->constants.data + rescue_args->constants_size;
|
100
|
+
VALUE *const_ptr = (VALUE *)last_constants_data_end;
|
101
|
+
st_table *constants_table = code->constants_table;
|
102
|
+
|
103
|
+
while((uint8_t *)const_ptr < code->constants.data_end) {
|
104
|
+
st_data_t key = (st_data_t)const_ptr[0];
|
105
|
+
st_delete(constants_table, &key, 0);
|
106
|
+
const_ptr++;
|
107
|
+
}
|
108
|
+
|
109
|
+
code->instructions.data_end = code->instructions.data + rescue_args->instructions_size;
|
110
|
+
code->constants.data_end = last_constants_data_end;
|
111
|
+
code->stack_size = rescue_args->stack_size;
|
112
|
+
|
113
|
+
if (rb_obj_is_kind_of(exception, cLiquidSyntaxError) == Qfalse)
|
114
|
+
rb_exc_raise(exception);
|
115
|
+
|
116
|
+
VALUE markup_obj = rb_enc_str_new(parse_args->markup, parse_args->markup_end - parse_args->markup, utf8_encoding);
|
117
|
+
VALUE variable_obj = rb_funcall(
|
118
|
+
cLiquidVariable, id_rescue_strict_parse_syntax_error, 3,
|
119
|
+
exception, markup_obj, parse_args->parse_context
|
120
|
+
);
|
121
|
+
|
122
|
+
// lax parse
|
123
|
+
code->protected_stack_size = code->stack_size;
|
124
|
+
rb_funcall(variable_obj, id_compile_evaluate, 1, parse_args->code_obj);
|
125
|
+
if (code->stack_size != code->protected_stack_size + 1) {
|
126
|
+
rb_raise(rb_eRuntimeError, "Liquid::Variable#compile_evaluate didn't leave exactly 1 new element on the stack");
|
127
|
+
}
|
128
|
+
|
129
|
+
return Qnil;
|
130
|
+
}
|
131
|
+
|
132
|
+
void internal_variable_compile_evaluate(variable_parse_args_t *parse_args)
|
133
|
+
{
|
134
|
+
vm_assembler_t *code = parse_args->code;
|
135
|
+
variable_strict_parse_rescue_t rescue_args = {
|
136
|
+
.parse_args = parse_args,
|
137
|
+
.instructions_size = c_buffer_size(&code->instructions),
|
138
|
+
.constants_size = c_buffer_size(&code->constants),
|
139
|
+
.stack_size = code->stack_size,
|
140
|
+
};
|
141
|
+
rb_rescue(try_variable_strict_parse, (VALUE)parse_args, variable_strict_parse_rescue, (VALUE)&rescue_args);
|
48
142
|
}
|
49
143
|
|
50
|
-
void
|
144
|
+
void internal_variable_compile(variable_parse_args_t *parse_args, unsigned int line_number)
|
51
145
|
{
|
52
|
-
|
146
|
+
vm_assembler_t *code = parse_args->code;
|
147
|
+
vm_assembler_add_render_variable_rescue(code, line_number);
|
148
|
+
internal_variable_compile_evaluate(parse_args);
|
149
|
+
vm_assembler_add_pop_write(code);
|
150
|
+
}
|
151
|
+
|
152
|
+
static VALUE variable_strict_parse_method(VALUE self, VALUE markup)
|
153
|
+
{
|
154
|
+
StringValue(markup);
|
155
|
+
check_utf8_encoding(markup, "markup");
|
156
|
+
|
157
|
+
VALUE parse_context = rb_ivar_get(self, id_ivar_parse_context);
|
158
|
+
|
159
|
+
expression_t *expression;
|
160
|
+
VALUE expression_obj = expression_new(cLiquidCVariableExpression, &expression);
|
161
|
+
|
162
|
+
variable_parse_args_t parse_args = {
|
163
|
+
.markup = RSTRING_PTR(markup),
|
164
|
+
.markup_end = RSTRING_END(markup),
|
165
|
+
.code = &expression->code,
|
166
|
+
.code_obj = expression_obj,
|
167
|
+
.parse_context = parse_context,
|
168
|
+
};
|
169
|
+
try_variable_strict_parse((VALUE)&parse_args);
|
170
|
+
RB_GC_GUARD(markup);
|
171
|
+
assert(expression->code.stack_size == 1);
|
172
|
+
vm_assembler_add_leave(&expression->code);
|
173
|
+
|
174
|
+
rb_ivar_set(self, id_ivar_name, expression_obj);
|
175
|
+
rb_ivar_set(self, id_ivar_filters, frozen_empty_array);
|
176
|
+
|
177
|
+
return Qnil;
|
178
|
+
}
|
179
|
+
|
180
|
+
typedef struct {
|
181
|
+
expression_t *expression;
|
182
|
+
VALUE context;
|
183
|
+
} variable_expression_evaluate_args_t;
|
184
|
+
|
185
|
+
static VALUE try_variable_expression_evaluate(VALUE uncast_args)
|
186
|
+
{
|
187
|
+
variable_expression_evaluate_args_t *args = (void *)uncast_args;
|
188
|
+
return liquid_vm_evaluate(args->context, &args->expression->code);
|
189
|
+
}
|
190
|
+
|
191
|
+
static VALUE rescue_variable_expression_evaluate(VALUE uncast_args, VALUE exception)
|
192
|
+
{
|
193
|
+
variable_expression_evaluate_args_t *args = (void *)uncast_args;
|
194
|
+
vm_t *vm = vm_from_context(args->context);
|
195
|
+
exception = vm_translate_if_filter_argument_error(vm, exception);
|
196
|
+
rb_exc_raise(exception);
|
197
|
+
}
|
198
|
+
|
199
|
+
VALUE internal_variable_expression_evaluate(expression_t *expression, VALUE context)
|
200
|
+
{
|
201
|
+
variable_expression_evaluate_args_t args = { expression, context };
|
202
|
+
return rb_rescue(try_variable_expression_evaluate, (VALUE)&args, rescue_variable_expression_evaluate, (VALUE)&args);
|
203
|
+
}
|
204
|
+
|
205
|
+
static VALUE variable_expression_evaluate_method(VALUE self, VALUE context)
|
206
|
+
{
|
207
|
+
expression_t *expression;
|
208
|
+
Expression_Get_Struct(self, expression);
|
209
|
+
return internal_variable_expression_evaluate(expression, context);
|
210
|
+
}
|
211
|
+
|
212
|
+
void liquid_define_variable(void)
|
213
|
+
{
|
214
|
+
id_rescue_strict_parse_syntax_error = rb_intern("rescue_strict_parse_syntax_error");
|
215
|
+
|
216
|
+
id_ivar_parse_context = rb_intern("@parse_context");
|
217
|
+
id_ivar_name = rb_intern("@name");
|
218
|
+
id_ivar_filters = rb_intern("@filters");
|
219
|
+
|
220
|
+
frozen_empty_array = rb_ary_new();
|
221
|
+
rb_ary_freeze(frozen_empty_array);
|
222
|
+
rb_global_variable(&frozen_empty_array);
|
223
|
+
|
224
|
+
rb_define_method(cLiquidVariable, "c_strict_parse", variable_strict_parse_method, 1);
|
225
|
+
|
226
|
+
cLiquidCVariableExpression = rb_define_class_under(mLiquidC, "VariableExpression", cLiquidCExpression);
|
227
|
+
rb_global_variable(&cLiquidCVariableExpression);
|
228
|
+
rb_define_method(cLiquidCVariableExpression, "evaluate", variable_expression_evaluate_method, 1);
|
53
229
|
}
|
54
230
|
|
data/ext/liquid_c/variable.h
CHANGED
@@ -1,7 +1,24 @@
|
|
1
1
|
#if !defined(LIQUID_VARIABLE_H)
|
2
2
|
#define LIQUID_VARIABLE_H
|
3
3
|
|
4
|
-
|
4
|
+
#include "vm_assembler.h"
|
5
|
+
#include "block.h"
|
6
|
+
#include "expression.h"
|
7
|
+
|
8
|
+
extern VALUE cLiquidCVariableExpression;
|
9
|
+
|
10
|
+
typedef struct variable_parse_args {
|
11
|
+
const char *markup;
|
12
|
+
const char *markup_end;
|
13
|
+
vm_assembler_t *code;
|
14
|
+
VALUE code_obj;
|
15
|
+
VALUE parse_context;
|
16
|
+
} variable_parse_args_t;
|
17
|
+
|
18
|
+
void liquid_define_variable(void);
|
19
|
+
void internal_variable_compile(variable_parse_args_t *parse_args, unsigned int line_number);
|
20
|
+
void internal_variable_compile_evaluate(variable_parse_args_t *parse_args);
|
21
|
+
VALUE internal_variable_expression_evaluate(expression_t *expression, VALUE context);
|
5
22
|
|
6
23
|
#endif
|
7
24
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#include "liquid.h"
|
2
|
+
#include "context.h"
|
3
|
+
|
4
|
+
static ID id_has_key, id_aref, id_fetch, id_to_liquid_value;
|
5
|
+
|
6
|
+
VALUE variable_lookup_key(VALUE context, VALUE object, VALUE key, bool is_command)
|
7
|
+
{
|
8
|
+
if (rb_obj_class(key) != rb_cString) {
|
9
|
+
VALUE key_value = rb_check_funcall(key, id_to_liquid_value, 0, 0);
|
10
|
+
|
11
|
+
if (key_value != Qundef) {
|
12
|
+
key = key_value;
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
if (rb_respond_to(object, id_aref) && (
|
17
|
+
(rb_respond_to(object, id_has_key) && rb_funcall(object, id_has_key, 1, key)) ||
|
18
|
+
(rb_obj_is_kind_of(key, rb_cInteger) && rb_respond_to(object, id_fetch))
|
19
|
+
)) {
|
20
|
+
VALUE next_object = rb_funcall(object, id_aref, 1, key);
|
21
|
+
next_object = materialize_proc(context, object, key, next_object);
|
22
|
+
return value_to_liquid_and_set_context(next_object, context);
|
23
|
+
}
|
24
|
+
|
25
|
+
if (is_command) {
|
26
|
+
Check_Type(key, T_STRING);
|
27
|
+
ID intern_key = rb_intern(RSTRING_PTR(key));
|
28
|
+
if (rb_respond_to(object, intern_key)) {
|
29
|
+
VALUE next_object = rb_funcall(object, intern_key, 0);
|
30
|
+
return value_to_liquid_and_set_context(next_object, context);
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
context_maybe_raise_undefined_variable(context, key);
|
35
|
+
return Qnil;
|
36
|
+
}
|
37
|
+
|
38
|
+
void liquid_define_variable_lookup(void)
|
39
|
+
{
|
40
|
+
id_has_key = rb_intern("key?");
|
41
|
+
id_aref = rb_intern("[]");
|
42
|
+
id_fetch = rb_intern("fetch");
|
43
|
+
id_to_liquid_value = rb_intern("to_liquid_value");
|
44
|
+
}
|