liquid-c 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/liquid.yml +24 -2
- data/.gitignore +4 -0
- data/.rubocop.yml +14 -0
- data/Gemfile +14 -5
- 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 +19 -9
- 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 +16 -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 +59 -11
- data/test/liquid_test.rb +0 -11
data/ext/liquid_c/block.c
CHANGED
@@ -1,142 +1,570 @@
|
|
1
1
|
#include "liquid.h"
|
2
|
+
#include "block.h"
|
3
|
+
#include "intutil.h"
|
2
4
|
#include "tokenizer.h"
|
5
|
+
#include "stringutil.h"
|
6
|
+
#include "vm.h"
|
7
|
+
#include "variable.h"
|
8
|
+
#include "context.h"
|
9
|
+
#include "parse_context.h"
|
10
|
+
#include "vm_assembler.h"
|
3
11
|
#include <stdio.h>
|
4
12
|
|
5
13
|
static ID
|
6
14
|
intern_raise_missing_variable_terminator,
|
7
15
|
intern_raise_missing_tag_terminator,
|
8
|
-
intern_nodelist,
|
9
|
-
intern_blank,
|
10
16
|
intern_is_blank,
|
11
|
-
intern_clear,
|
12
|
-
intern_registered_tags,
|
13
17
|
intern_parse,
|
14
18
|
intern_square_brackets,
|
15
|
-
|
19
|
+
intern_unknown_tag_in_liquid_tag,
|
20
|
+
intern_ivar_nodelist;
|
16
21
|
|
17
|
-
static
|
22
|
+
static VALUE tag_registry;
|
23
|
+
static VALUE variable_placeholder = Qnil;
|
24
|
+
|
25
|
+
typedef struct tag_markup {
|
26
|
+
VALUE name;
|
27
|
+
VALUE markup;
|
28
|
+
} tag_markup_t;
|
29
|
+
|
30
|
+
typedef struct parse_context {
|
31
|
+
tokenizer_t *tokenizer;
|
32
|
+
VALUE tokenizer_obj;
|
33
|
+
VALUE ruby_obj;
|
34
|
+
} parse_context_t;
|
35
|
+
|
36
|
+
static void ensure_body_compiled(const block_body_t *body)
|
18
37
|
{
|
19
|
-
|
38
|
+
if (!body->compiled) {
|
39
|
+
rb_raise(rb_eRuntimeError, "Liquid::C::BlockBody has not been compiled");
|
40
|
+
}
|
20
41
|
}
|
21
42
|
|
22
|
-
|
43
|
+
static void block_body_mark(void *ptr)
|
23
44
|
{
|
24
|
-
|
25
|
-
|
45
|
+
block_body_t *body = ptr;
|
46
|
+
if (body->compiled) {
|
47
|
+
document_body_entry_mark(&body->as.compiled.document_body_entry);
|
48
|
+
rb_gc_mark(body->as.compiled.nodelist);
|
49
|
+
} else {
|
50
|
+
rb_gc_mark(body->as.intermediate.parse_context);
|
51
|
+
if (body->as.intermediate.vm_assembler_pool)
|
52
|
+
vm_assembler_pool_gc_mark(body->as.intermediate.vm_assembler_pool);
|
53
|
+
if (body->as.intermediate.code)
|
54
|
+
vm_assembler_gc_mark(body->as.intermediate.code);
|
55
|
+
}
|
26
56
|
}
|
27
57
|
|
28
|
-
|
58
|
+
static void block_body_free(void *ptr)
|
29
59
|
{
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
60
|
+
block_body_t *body = ptr;
|
61
|
+
if (!body->compiled && body->as.intermediate.code) {
|
62
|
+
// Free the assembler instead of recycling it because the vm_assembler_pool may have been GC'd
|
63
|
+
vm_assembler_pool_free_assembler(body->as.intermediate.code);
|
64
|
+
}
|
65
|
+
xfree(body);
|
34
66
|
}
|
35
67
|
|
36
|
-
static
|
68
|
+
static size_t block_body_memsize(const void *ptr)
|
37
69
|
{
|
38
|
-
|
39
|
-
|
70
|
+
const block_body_t *body = ptr;
|
71
|
+
if (!ptr) return 0;
|
72
|
+
if (body->compiled) {
|
73
|
+
return sizeof(block_body_t);
|
74
|
+
} else {
|
75
|
+
return sizeof(block_body_t) + vm_assembler_alloc_memsize(body->as.intermediate.code);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
const rb_data_type_t block_body_data_type = {
|
80
|
+
"liquid_block_body",
|
81
|
+
{ block_body_mark, block_body_free, block_body_memsize, },
|
82
|
+
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
|
83
|
+
};
|
40
84
|
|
85
|
+
#define BlockBody_Get_Struct(obj, sval) TypedData_Get_Struct(obj, block_body_t, &block_body_data_type, sval)
|
86
|
+
|
87
|
+
static VALUE block_body_allocate(VALUE klass)
|
88
|
+
{
|
89
|
+
block_body_t *body;
|
90
|
+
VALUE obj = TypedData_Make_Struct(klass, block_body_t, &block_body_data_type, body);
|
91
|
+
|
92
|
+
body->compiled = false;
|
93
|
+
body->obj = obj;
|
94
|
+
body->as.intermediate.blank = true;
|
95
|
+
body->as.intermediate.render_score = 0;
|
96
|
+
body->as.intermediate.vm_assembler_pool = NULL;
|
97
|
+
body->as.intermediate.code = NULL;
|
98
|
+
return obj;
|
99
|
+
}
|
100
|
+
|
101
|
+
static VALUE block_body_initialize(VALUE self, VALUE parse_context)
|
102
|
+
{
|
103
|
+
block_body_t *body;
|
104
|
+
BlockBody_Get_Struct(self, body);
|
105
|
+
|
106
|
+
body->as.intermediate.parse_context = parse_context;
|
107
|
+
body->as.intermediate.vm_assembler_pool = parse_context_get_vm_assembler_pool(parse_context);
|
108
|
+
body->as.intermediate.code = vm_assembler_pool_alloc_assembler(body->as.intermediate.vm_assembler_pool);
|
109
|
+
vm_assembler_add_leave(body->as.intermediate.code);
|
110
|
+
|
111
|
+
return Qnil;
|
112
|
+
}
|
113
|
+
|
114
|
+
static int is_id(int c)
|
115
|
+
{
|
116
|
+
return rb_isalnum(c) || c == '_';
|
117
|
+
}
|
118
|
+
|
119
|
+
static tag_markup_t internal_block_body_parse(block_body_t *body, parse_context_t *parse_context)
|
120
|
+
{
|
121
|
+
tokenizer_t *tokenizer = parse_context->tokenizer;
|
41
122
|
token_t token;
|
42
|
-
|
43
|
-
|
123
|
+
tag_markup_t unknown_tag = { Qnil, Qnil };
|
124
|
+
int render_score_increment = 0;
|
44
125
|
|
45
126
|
while (true) {
|
46
|
-
|
47
|
-
|
127
|
+
int token_start_line_number = tokenizer->line_number;
|
128
|
+
if (token_start_line_number != 0) {
|
129
|
+
rb_ivar_set(parse_context->ruby_obj, id_ivar_line_number, UINT2NUM(token_start_line_number));
|
48
130
|
}
|
49
131
|
tokenizer_next(tokenizer, &token);
|
50
132
|
|
51
133
|
switch (token.type) {
|
52
134
|
case TOKENIZER_TOKEN_NONE:
|
53
|
-
|
135
|
+
goto loop_break;
|
54
136
|
|
55
137
|
case TOKEN_INVALID:
|
56
138
|
{
|
57
|
-
VALUE str = rb_enc_str_new(token.
|
139
|
+
VALUE str = rb_enc_str_new(token.str_full, token.len_full, utf8_encoding);
|
58
140
|
|
59
141
|
ID raise_method_id = intern_raise_missing_variable_terminator;
|
60
|
-
if (token.
|
142
|
+
if (token.str_full[1] == '%') raise_method_id = intern_raise_missing_tag_terminator;
|
61
143
|
|
62
|
-
|
144
|
+
rb_funcall(cLiquidBlockBody, raise_method_id, 2, str, parse_context->ruby_obj);
|
145
|
+
goto loop_break;
|
63
146
|
}
|
64
147
|
case TOKEN_RAW:
|
65
148
|
{
|
66
|
-
const char *start = token.
|
149
|
+
const char *start = token.str_full, *end = token.str_full + token.len_full;
|
150
|
+
const char *token_start = start, *token_end = end;
|
67
151
|
|
68
|
-
if(token.lstrip)
|
152
|
+
if (token.lstrip)
|
69
153
|
token_start = read_while(start, end, rb_isspace);
|
70
|
-
if(token.rstrip)
|
71
|
-
token_end = read_while_end(token_start, end, rb_isspace);
|
72
|
-
|
73
|
-
VALUE str = rb_enc_str_new(token_start, token_end - token_start, utf8_encoding);
|
74
|
-
rb_ary_push(nodelist, str);
|
75
154
|
|
76
|
-
if (
|
77
|
-
|
155
|
+
if (token.rstrip) {
|
156
|
+
if (tokenizer->bug_compatible_whitespace_trimming) {
|
157
|
+
token_end = read_while_reverse(token_start + 1, end, rb_isspace);
|
158
|
+
} else {
|
159
|
+
token_end = read_while_reverse(token_start, end, rb_isspace);
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
// Skip token entirely if there is no data to be rendered.
|
164
|
+
if (token_start == token_end)
|
165
|
+
break;
|
166
|
+
|
167
|
+
vm_assembler_add_write_raw(body->as.intermediate.code, token_start, token_end - token_start);
|
168
|
+
render_score_increment += 1;
|
78
169
|
|
79
|
-
|
80
|
-
|
170
|
+
if (body->as.intermediate.blank) {
|
171
|
+
const char *end = token.str_full + token.len_full;
|
172
|
+
|
173
|
+
if (read_while(token.str_full, end, rb_isspace) < end)
|
174
|
+
body->as.intermediate.blank = false;
|
81
175
|
}
|
82
176
|
break;
|
83
177
|
}
|
84
178
|
case TOKEN_VARIABLE:
|
85
179
|
{
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
180
|
+
variable_parse_args_t parse_args = {
|
181
|
+
.markup = token.str_trimmed,
|
182
|
+
.markup_end = token.str_trimmed + token.len_trimmed,
|
183
|
+
.code = body->as.intermediate.code,
|
184
|
+
.code_obj = body->obj,
|
185
|
+
.parse_context = parse_context->ruby_obj,
|
186
|
+
};
|
187
|
+
internal_variable_compile(&parse_args, token_start_line_number);
|
188
|
+
render_score_increment += 1;
|
189
|
+
body->as.intermediate.blank = false;
|
90
190
|
break;
|
91
191
|
}
|
92
192
|
case TOKEN_TAG:
|
93
193
|
{
|
94
|
-
const char *start = token.
|
194
|
+
const char *start = token.str_trimmed, *end = token.str_trimmed + token.len_trimmed;
|
95
195
|
|
96
196
|
// Imitate \s*(\w+)\s*(.*)? regex
|
97
197
|
const char *name_start = read_while(start, end, rb_isspace);
|
98
198
|
const char *name_end = read_while(name_start, end, is_id);
|
199
|
+
long name_len = name_end - name_start;
|
99
200
|
|
100
|
-
|
201
|
+
if (name_len == 0) {
|
202
|
+
VALUE str = rb_enc_str_new(token.str_trimmed, token.len_trimmed, utf8_encoding);
|
203
|
+
unknown_tag = (tag_markup_t) { str, str };
|
204
|
+
goto loop_break;
|
205
|
+
}
|
101
206
|
|
102
|
-
if (
|
103
|
-
|
207
|
+
if (name_len == 6 && strncmp(name_start, "liquid", 6) == 0) {
|
208
|
+
const char *markup_start = read_while(name_end, end, rb_isspace);
|
209
|
+
int line_number = token_start_line_number;
|
210
|
+
if (line_number) {
|
211
|
+
line_number += count_newlines(token.str_full, markup_start);
|
212
|
+
}
|
104
213
|
|
105
|
-
|
214
|
+
tokenizer_t saved_tokenizer = *tokenizer;
|
215
|
+
tokenizer_setup_for_liquid_tag(tokenizer, markup_start, end, line_number);
|
216
|
+
unknown_tag = internal_block_body_parse(body, parse_context);
|
217
|
+
*tokenizer = saved_tokenizer;
|
218
|
+
if (unknown_tag.name != Qnil) {
|
219
|
+
rb_funcall(cLiquidBlockBody, intern_unknown_tag_in_liquid_tag, 2, unknown_tag.name, parse_context->ruby_obj);
|
220
|
+
goto loop_break;
|
221
|
+
}
|
222
|
+
break;
|
223
|
+
}
|
224
|
+
|
225
|
+
VALUE tag_name = rb_enc_str_new(name_start, name_end - name_start, utf8_encoding);
|
226
|
+
VALUE tag_class = rb_funcall(tag_registry, intern_square_brackets, 1, tag_name);
|
106
227
|
|
107
228
|
const char *markup_start = read_while(name_end, end, rb_isspace);
|
108
229
|
VALUE markup = rb_enc_str_new(markup_start, end - markup_start, utf8_encoding);
|
109
230
|
|
110
|
-
if (tag_class == Qnil)
|
111
|
-
|
231
|
+
if (tag_class == Qnil) {
|
232
|
+
unknown_tag = (tag_markup_t) { tag_name, markup };
|
233
|
+
goto loop_break;
|
234
|
+
}
|
112
235
|
|
113
|
-
VALUE new_tag = rb_funcall(tag_class, intern_parse, 4,
|
236
|
+
VALUE new_tag = rb_funcall(tag_class, intern_parse, 4,
|
237
|
+
tag_name, markup, parse_context->tokenizer_obj, parse_context->ruby_obj);
|
114
238
|
|
115
|
-
if (
|
116
|
-
|
239
|
+
if (body->as.intermediate.blank && !RTEST(rb_funcall(new_tag, intern_is_blank, 0)))
|
240
|
+
body->as.intermediate.blank = false;
|
241
|
+
|
242
|
+
if (tokenizer->raw_tag_body) {
|
243
|
+
if (tokenizer->raw_tag_body_len) {
|
244
|
+
vm_assembler_add_write_raw(body->as.intermediate.code, tokenizer->raw_tag_body,
|
245
|
+
tokenizer->raw_tag_body_len);
|
246
|
+
}
|
247
|
+
tokenizer->raw_tag_body = NULL;
|
248
|
+
tokenizer->raw_tag_body_len = 0;
|
249
|
+
} else {
|
250
|
+
vm_assembler_add_write_node(body->as.intermediate.code, new_tag);
|
251
|
+
}
|
117
252
|
|
118
|
-
|
253
|
+
render_score_increment += 1;
|
119
254
|
break;
|
120
255
|
}
|
256
|
+
case TOKEN_BLANK_LIQUID_TAG_LINE:
|
257
|
+
break;
|
258
|
+
}
|
259
|
+
}
|
260
|
+
loop_break:
|
261
|
+
body->as.intermediate.render_score += render_score_increment;
|
262
|
+
return unknown_tag;
|
263
|
+
}
|
264
|
+
|
265
|
+
static void ensure_intermediate(block_body_t *body)
|
266
|
+
{
|
267
|
+
if (body->compiled) {
|
268
|
+
rb_raise(rb_eRuntimeError, "Liquid::C::BlockBody is already compiled");
|
269
|
+
}
|
270
|
+
}
|
271
|
+
|
272
|
+
static void ensure_intermediate_not_parsing(block_body_t *body)
|
273
|
+
{
|
274
|
+
ensure_intermediate(body);
|
275
|
+
|
276
|
+
if (body->as.intermediate.code->parsing) {
|
277
|
+
rb_raise(rb_eRuntimeError, "Liquid::C::BlockBody is in a incompletely parsed state");
|
278
|
+
}
|
279
|
+
}
|
280
|
+
|
281
|
+
static VALUE block_body_parse(VALUE self, VALUE tokenizer_obj, VALUE parse_context_obj)
|
282
|
+
{
|
283
|
+
parse_context_t parse_context = {
|
284
|
+
.tokenizer_obj = tokenizer_obj,
|
285
|
+
.ruby_obj = parse_context_obj,
|
286
|
+
};
|
287
|
+
Tokenizer_Get_Struct(tokenizer_obj, parse_context.tokenizer);
|
288
|
+
block_body_t *body;
|
289
|
+
BlockBody_Get_Struct(self, body);
|
290
|
+
|
291
|
+
ensure_intermediate_not_parsing(body);
|
292
|
+
if (body->as.intermediate.parse_context != parse_context_obj) {
|
293
|
+
rb_raise(rb_eArgError, "Liquid::C::BlockBody#parse called with different parse context");
|
294
|
+
}
|
295
|
+
vm_assembler_remove_leave(body->as.intermediate.code); // to extend block
|
296
|
+
|
297
|
+
tag_markup_t unknown_tag = internal_block_body_parse(body, &parse_context);
|
298
|
+
vm_assembler_add_leave(body->as.intermediate.code);
|
299
|
+
|
300
|
+
return rb_yield_values(2, unknown_tag.name, unknown_tag.markup);
|
301
|
+
}
|
302
|
+
|
303
|
+
|
304
|
+
static VALUE block_body_freeze(VALUE self)
|
305
|
+
{
|
306
|
+
block_body_t *body;
|
307
|
+
BlockBody_Get_Struct(self, body);
|
308
|
+
|
309
|
+
if (body->compiled) return Qnil;
|
310
|
+
|
311
|
+
VALUE parse_context = body->as.intermediate.parse_context;
|
312
|
+
VALUE document_body = parse_context_get_document_body(parse_context);
|
313
|
+
rb_check_frozen(document_body);
|
314
|
+
|
315
|
+
vm_assembler_pool_t *assembler_pool = body->as.intermediate.vm_assembler_pool;
|
316
|
+
vm_assembler_t *assembler = body->as.intermediate.code;
|
317
|
+
bool blank = body->as.intermediate.blank;
|
318
|
+
uint32_t render_score = body->as.intermediate.render_score;
|
319
|
+
vm_assembler_t *code = body->as.intermediate.code;
|
320
|
+
body->as.compiled.document_body_entry = document_body_write_block_body(document_body, blank, render_score, code);
|
321
|
+
body->as.compiled.nodelist = Qundef;
|
322
|
+
body->compiled = true;
|
323
|
+
vm_assembler_pool_recycle_assembler(assembler_pool, assembler);
|
324
|
+
|
325
|
+
rb_call_super(0, NULL);
|
326
|
+
|
327
|
+
return Qnil;
|
328
|
+
}
|
329
|
+
|
330
|
+
static VALUE block_body_render_to_output_buffer(VALUE self, VALUE context, VALUE output)
|
331
|
+
{
|
332
|
+
Check_Type(output, T_STRING);
|
333
|
+
check_utf8_encoding(output, "output");
|
334
|
+
|
335
|
+
block_body_t *body;
|
336
|
+
BlockBody_Get_Struct(self, body);
|
337
|
+
ensure_body_compiled(body);
|
338
|
+
document_body_entry_t *entry = &body->as.compiled.document_body_entry;
|
339
|
+
document_body_ensure_compile_finished(entry->body);
|
340
|
+
|
341
|
+
liquid_vm_render(document_body_get_block_body_header_ptr(entry), document_body_get_constants_ptr(entry), context, output);
|
342
|
+
return output;
|
343
|
+
}
|
344
|
+
|
345
|
+
static VALUE block_body_blank_p(VALUE self)
|
346
|
+
{
|
347
|
+
block_body_t *body;
|
348
|
+
BlockBody_Get_Struct(self, body);
|
349
|
+
if (body->compiled) {
|
350
|
+
block_body_header_t *body_header = document_body_get_block_body_header_ptr(&body->as.compiled.document_body_entry);
|
351
|
+
return BLOCK_BODY_HEADER_BLANK_P(body_header) ? Qtrue : Qfalse;
|
352
|
+
} else {
|
353
|
+
return body->as.intermediate.blank ? Qtrue : Qfalse;
|
354
|
+
}
|
355
|
+
}
|
356
|
+
|
357
|
+
static VALUE block_body_remove_blank_strings(VALUE self)
|
358
|
+
{
|
359
|
+
block_body_t *body;
|
360
|
+
BlockBody_Get_Struct(self, body);
|
361
|
+
|
362
|
+
ensure_intermediate_not_parsing(body);
|
363
|
+
|
364
|
+
if (!body->as.intermediate.blank) {
|
365
|
+
rb_raise(rb_eRuntimeError, "remove_blank_strings only support being called on a blank block body");
|
366
|
+
}
|
367
|
+
|
368
|
+
uint8_t *ip = body->as.intermediate.code->instructions.data;
|
369
|
+
|
370
|
+
while (*ip != OP_LEAVE) {
|
371
|
+
if (*ip == OP_WRITE_RAW) {
|
372
|
+
if (ip[1]) { // if (size != 0)
|
373
|
+
ip[0] = OP_JUMP_FWD; // effectively a no-op
|
374
|
+
body->as.intermediate.render_score--;
|
375
|
+
}
|
376
|
+
} else if (*ip == OP_WRITE_RAW_W) {
|
377
|
+
if (ip[1] || ip[2] || ip[3]) { // if (size != 0)
|
378
|
+
ip[0] = OP_JUMP_FWD_W; // effectively a no-op
|
379
|
+
body->as.intermediate.render_score--;
|
380
|
+
}
|
121
381
|
}
|
382
|
+
liquid_vm_next_instruction((const uint8_t **)&ip);
|
122
383
|
}
|
384
|
+
|
123
385
|
return Qnil;
|
124
386
|
}
|
125
387
|
|
126
|
-
void
|
388
|
+
static void memoize_variable_placeholder(void)
|
389
|
+
{
|
390
|
+
if (variable_placeholder == Qnil) {
|
391
|
+
VALUE cLiquidCVariablePlaceholder = rb_const_get(mLiquidC, rb_intern("VariablePlaceholder"));
|
392
|
+
variable_placeholder = rb_class_new_instance(0, NULL, cLiquidCVariablePlaceholder);
|
393
|
+
}
|
394
|
+
}
|
395
|
+
|
396
|
+
// Deprecated: avoid using this for the love of performance
|
397
|
+
static VALUE block_body_nodelist(VALUE self)
|
398
|
+
{
|
399
|
+
block_body_t *body;
|
400
|
+
BlockBody_Get_Struct(self, body);
|
401
|
+
ensure_body_compiled(body);
|
402
|
+
document_body_entry_t *entry = &body->as.compiled.document_body_entry;
|
403
|
+
block_body_header_t *body_header = document_body_get_block_body_header_ptr(entry);
|
404
|
+
|
405
|
+
memoize_variable_placeholder();
|
406
|
+
|
407
|
+
if (body->as.compiled.nodelist != Qundef)
|
408
|
+
return body->as.compiled.nodelist;
|
409
|
+
|
410
|
+
VALUE nodelist = rb_ary_new_capa(body_header->render_score);
|
411
|
+
|
412
|
+
const VALUE *constants = &entry->body->constants;
|
413
|
+
const uint8_t *ip = block_body_instructions_ptr(body_header);
|
414
|
+
while (true) {
|
415
|
+
switch (*ip) {
|
416
|
+
case OP_LEAVE:
|
417
|
+
goto loop_break;
|
418
|
+
case OP_WRITE_RAW_W:
|
419
|
+
case OP_WRITE_RAW:
|
420
|
+
{
|
421
|
+
const char *text;
|
422
|
+
size_t size;
|
423
|
+
if (*ip == OP_WRITE_RAW_W) {
|
424
|
+
size = bytes_to_uint24(&ip[1]);
|
425
|
+
text = (const char *)&ip[4];
|
426
|
+
} else {
|
427
|
+
size = ip[1];
|
428
|
+
text = (const char *)&ip[2];
|
429
|
+
}
|
430
|
+
VALUE string = rb_enc_str_new(text, size, utf8_encoding);
|
431
|
+
rb_ary_push(nodelist, string);
|
432
|
+
break;
|
433
|
+
}
|
434
|
+
case OP_WRITE_NODE:
|
435
|
+
{
|
436
|
+
uint16_t constant_index = (ip[1] << 8) | ip[2];
|
437
|
+
VALUE node = RARRAY_AREF(*constants, constant_index);
|
438
|
+
rb_ary_push(nodelist, node);
|
439
|
+
break;
|
440
|
+
}
|
441
|
+
|
442
|
+
case OP_RENDER_VARIABLE_RESCUE:
|
443
|
+
rb_ary_push(nodelist, variable_placeholder);
|
444
|
+
break;
|
445
|
+
}
|
446
|
+
liquid_vm_next_instruction(&ip);
|
447
|
+
}
|
448
|
+
loop_break:
|
449
|
+
|
450
|
+
rb_ary_freeze(nodelist);
|
451
|
+
body->as.compiled.nodelist = nodelist;
|
452
|
+
return nodelist;
|
453
|
+
}
|
454
|
+
|
455
|
+
static VALUE block_body_disassemble(VALUE self)
|
456
|
+
{
|
457
|
+
block_body_t *body;
|
458
|
+
BlockBody_Get_Struct(self, body);
|
459
|
+
document_body_entry_t *entry = &body->as.compiled.document_body_entry;
|
460
|
+
block_body_header_t *header = document_body_get_block_body_header_ptr(entry);
|
461
|
+
const uint8_t *start_ip = block_body_instructions_ptr(header);
|
462
|
+
return vm_assembler_disassemble(
|
463
|
+
start_ip,
|
464
|
+
start_ip + header->instructions_bytes,
|
465
|
+
&entry->body->constants
|
466
|
+
);
|
467
|
+
}
|
468
|
+
|
469
|
+
|
470
|
+
static VALUE block_body_add_evaluate_expression(VALUE self, VALUE expression)
|
471
|
+
{
|
472
|
+
block_body_t *body;
|
473
|
+
BlockBody_Get_Struct(self, body);
|
474
|
+
ensure_intermediate(body);
|
475
|
+
vm_assembler_add_evaluate_expression_from_ruby(body->as.intermediate.code, self, expression);
|
476
|
+
return self;
|
477
|
+
}
|
478
|
+
|
479
|
+
static VALUE block_body_add_find_variable(VALUE self, VALUE expression)
|
480
|
+
{
|
481
|
+
block_body_t *body;
|
482
|
+
BlockBody_Get_Struct(self, body);
|
483
|
+
ensure_intermediate(body);
|
484
|
+
vm_assembler_add_find_variable_from_ruby(body->as.intermediate.code, self, expression);
|
485
|
+
return self;
|
486
|
+
}
|
487
|
+
|
488
|
+
static VALUE block_body_add_lookup_command(VALUE self, VALUE name)
|
489
|
+
{
|
490
|
+
block_body_t *body;
|
491
|
+
BlockBody_Get_Struct(self, body);
|
492
|
+
ensure_intermediate(body);
|
493
|
+
vm_assembler_add_lookup_command_from_ruby(body->as.intermediate.code, name);
|
494
|
+
return self;
|
495
|
+
}
|
496
|
+
|
497
|
+
static VALUE block_body_add_lookup_key(VALUE self, VALUE expression)
|
498
|
+
{
|
499
|
+
block_body_t *body;
|
500
|
+
BlockBody_Get_Struct(self, body);
|
501
|
+
ensure_intermediate(body);
|
502
|
+
vm_assembler_add_lookup_key_from_ruby(body->as.intermediate.code, self, expression);
|
503
|
+
return self;
|
504
|
+
}
|
505
|
+
|
506
|
+
static VALUE block_body_add_new_int_range(VALUE self)
|
507
|
+
{
|
508
|
+
block_body_t *body;
|
509
|
+
BlockBody_Get_Struct(self, body);
|
510
|
+
ensure_intermediate(body);
|
511
|
+
vm_assembler_add_new_int_range_from_ruby(body->as.intermediate.code);
|
512
|
+
return self;
|
513
|
+
}
|
514
|
+
|
515
|
+
static VALUE block_body_add_hash_new(VALUE self, VALUE hash_size)
|
516
|
+
{
|
517
|
+
block_body_t *body;
|
518
|
+
BlockBody_Get_Struct(self, body);
|
519
|
+
ensure_intermediate(body);
|
520
|
+
vm_assembler_add_hash_new_from_ruby(body->as.intermediate.code, hash_size);
|
521
|
+
return self;
|
522
|
+
}
|
523
|
+
|
524
|
+
static VALUE block_body_add_filter(VALUE self, VALUE filter_name, VALUE num_args)
|
525
|
+
{
|
526
|
+
block_body_t *body;
|
527
|
+
BlockBody_Get_Struct(self, body);
|
528
|
+
ensure_intermediate(body);
|
529
|
+
vm_assembler_add_filter_from_ruby(body->as.intermediate.code, filter_name, num_args);
|
530
|
+
return self;
|
531
|
+
}
|
532
|
+
|
533
|
+
|
534
|
+
void liquid_define_block_body(void)
|
127
535
|
{
|
128
536
|
intern_raise_missing_variable_terminator = rb_intern("raise_missing_variable_terminator");
|
129
537
|
intern_raise_missing_tag_terminator = rb_intern("raise_missing_tag_terminator");
|
130
|
-
intern_nodelist = rb_intern("@nodelist");
|
131
|
-
intern_blank = rb_intern("@blank");
|
132
538
|
intern_is_blank = rb_intern("blank?");
|
133
|
-
intern_clear = rb_intern("clear");
|
134
|
-
intern_registered_tags = rb_intern("registered_tags");
|
135
539
|
intern_parse = rb_intern("parse");
|
136
540
|
intern_square_brackets = rb_intern("[]");
|
137
|
-
|
541
|
+
intern_unknown_tag_in_liquid_tag = rb_intern("unknown_tag_in_liquid_tag");
|
542
|
+
intern_ivar_nodelist = rb_intern("@nodelist");
|
543
|
+
|
544
|
+
tag_registry = rb_funcall(cLiquidTemplate, rb_intern("tags"), 0);
|
545
|
+
rb_global_variable(&tag_registry);
|
546
|
+
|
547
|
+
VALUE cLiquidCBlockBody = rb_define_class_under(mLiquidC, "BlockBody", rb_cObject);
|
548
|
+
rb_define_alloc_func(cLiquidCBlockBody, block_body_allocate);
|
549
|
+
|
550
|
+
rb_define_method(cLiquidCBlockBody, "initialize", block_body_initialize, 1);
|
551
|
+
rb_define_method(cLiquidCBlockBody, "parse", block_body_parse, 2);
|
552
|
+
rb_define_method(cLiquidCBlockBody, "freeze", block_body_freeze, 0);
|
553
|
+
rb_define_method(cLiquidCBlockBody, "render_to_output_buffer", block_body_render_to_output_buffer, 2);
|
554
|
+
rb_define_method(cLiquidCBlockBody, "remove_blank_strings", block_body_remove_blank_strings, 0);
|
555
|
+
rb_define_method(cLiquidCBlockBody, "blank?", block_body_blank_p, 0);
|
556
|
+
rb_define_method(cLiquidCBlockBody, "nodelist", block_body_nodelist, 0);
|
557
|
+
rb_define_method(cLiquidCBlockBody, "disassemble", block_body_disassemble, 0);
|
558
|
+
|
559
|
+
rb_define_method(cLiquidCBlockBody, "add_evaluate_expression", block_body_add_evaluate_expression, 1);
|
560
|
+
rb_define_method(cLiquidCBlockBody, "add_find_variable", block_body_add_find_variable, 1);
|
561
|
+
rb_define_method(cLiquidCBlockBody, "add_lookup_command", block_body_add_lookup_command, 1);
|
562
|
+
rb_define_method(cLiquidCBlockBody, "add_lookup_key", block_body_add_lookup_key, 1);
|
563
|
+
rb_define_method(cLiquidCBlockBody, "add_new_int_range", block_body_add_new_int_range, 0);
|
564
|
+
|
565
|
+
rb_define_method(cLiquidCBlockBody, "add_hash_new", block_body_add_hash_new, 1);
|
566
|
+
rb_define_method(cLiquidCBlockBody, "add_filter", block_body_add_filter, 2);
|
138
567
|
|
139
|
-
|
140
|
-
rb_define_method(cLiquidBlockBody, "c_parse", rb_block_parse, 2);
|
568
|
+
rb_global_variable(&variable_placeholder);
|
141
569
|
}
|
142
570
|
|
data/ext/liquid_c/block.h
CHANGED
@@ -1,7 +1,33 @@
|
|
1
1
|
#if !defined(LIQUID_BLOCK_H)
|
2
2
|
#define LIQUID_BLOCK_H
|
3
3
|
|
4
|
-
|
4
|
+
#include "document_body.h"
|
5
|
+
#include "vm_assembler_pool.h"
|
5
6
|
|
6
|
-
|
7
|
+
typedef struct block_body {
|
8
|
+
bool compiled;
|
9
|
+
VALUE obj;
|
10
|
+
|
11
|
+
union {
|
12
|
+
struct {
|
13
|
+
document_body_entry_t document_body_entry;
|
14
|
+
VALUE nodelist;
|
15
|
+
} compiled;
|
16
|
+
struct {
|
17
|
+
VALUE parse_context;
|
18
|
+
vm_assembler_pool_t *vm_assembler_pool;
|
19
|
+
bool blank;
|
20
|
+
unsigned int render_score;
|
21
|
+
vm_assembler_t *code;
|
22
|
+
} intermediate;
|
23
|
+
} as;
|
24
|
+
} block_body_t;
|
25
|
+
|
26
|
+
void liquid_define_block_body(void);
|
7
27
|
|
28
|
+
static inline uint8_t *block_body_instructions_ptr(block_body_header_t *body)
|
29
|
+
{
|
30
|
+
return ((uint8_t *)body) + body->instructions_offset;
|
31
|
+
}
|
32
|
+
|
33
|
+
#endif
|