liquid-c 4.0.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/liquid.yml +24 -2
  3. data/.gitignore +4 -0
  4. data/.rubocop.yml +14 -0
  5. data/Gemfile +14 -5
  6. data/README.md +29 -5
  7. data/Rakefile +13 -62
  8. data/ext/liquid_c/block.c +488 -60
  9. data/ext/liquid_c/block.h +28 -2
  10. data/ext/liquid_c/c_buffer.c +42 -0
  11. data/ext/liquid_c/c_buffer.h +76 -0
  12. data/ext/liquid_c/context.c +233 -0
  13. data/ext/liquid_c/context.h +70 -0
  14. data/ext/liquid_c/document_body.c +89 -0
  15. data/ext/liquid_c/document_body.h +59 -0
  16. data/ext/liquid_c/expression.c +116 -0
  17. data/ext/liquid_c/expression.h +24 -0
  18. data/ext/liquid_c/extconf.rb +19 -9
  19. data/ext/liquid_c/intutil.h +22 -0
  20. data/ext/liquid_c/lexer.c +6 -2
  21. data/ext/liquid_c/lexer.h +18 -3
  22. data/ext/liquid_c/liquid.c +76 -6
  23. data/ext/liquid_c/liquid.h +24 -1
  24. data/ext/liquid_c/parse_context.c +76 -0
  25. data/ext/liquid_c/parse_context.h +13 -0
  26. data/ext/liquid_c/parser.c +141 -65
  27. data/ext/liquid_c/parser.h +4 -2
  28. data/ext/liquid_c/raw.c +110 -0
  29. data/ext/liquid_c/raw.h +6 -0
  30. data/ext/liquid_c/resource_limits.c +279 -0
  31. data/ext/liquid_c/resource_limits.h +23 -0
  32. data/ext/liquid_c/stringutil.h +44 -0
  33. data/ext/liquid_c/tokenizer.c +149 -35
  34. data/ext/liquid_c/tokenizer.h +20 -9
  35. data/ext/liquid_c/usage.c +18 -0
  36. data/ext/liquid_c/usage.h +9 -0
  37. data/ext/liquid_c/variable.c +196 -20
  38. data/ext/liquid_c/variable.h +18 -1
  39. data/ext/liquid_c/variable_lookup.c +44 -0
  40. data/ext/liquid_c/variable_lookup.h +8 -0
  41. data/ext/liquid_c/vm.c +588 -0
  42. data/ext/liquid_c/vm.h +25 -0
  43. data/ext/liquid_c/vm_assembler.c +491 -0
  44. data/ext/liquid_c/vm_assembler.h +240 -0
  45. data/ext/liquid_c/vm_assembler_pool.c +97 -0
  46. data/ext/liquid_c/vm_assembler_pool.h +27 -0
  47. data/lib/liquid/c/compile_ext.rb +44 -0
  48. data/lib/liquid/c/version.rb +3 -1
  49. data/lib/liquid/c.rb +225 -46
  50. data/liquid-c.gemspec +16 -10
  51. data/performance/c_profile.rb +23 -0
  52. data/performance.rb +6 -4
  53. data/rakelib/compile.rake +15 -0
  54. data/rakelib/integration_test.rake +43 -0
  55. data/rakelib/performance.rake +43 -0
  56. data/rakelib/rubocop.rake +6 -0
  57. data/rakelib/unit_test.rake +14 -0
  58. data/test/integration_test.rb +11 -0
  59. data/test/liquid_test_helper.rb +21 -0
  60. data/test/test_helper.rb +14 -2
  61. data/test/unit/block_test.rb +130 -0
  62. data/test/unit/context_test.rb +83 -0
  63. data/test/unit/expression_test.rb +186 -0
  64. data/test/unit/gc_stress_test.rb +28 -0
  65. data/test/unit/raw_test.rb +19 -0
  66. data/test/unit/resource_limits_test.rb +50 -0
  67. data/test/unit/tokenizer_test.rb +90 -20
  68. data/test/unit/variable_test.rb +212 -60
  69. metadata +59 -11
  70. 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
- intern_set_line_number;
19
+ intern_unknown_tag_in_liquid_tag,
20
+ intern_ivar_nodelist;
16
21
 
17
- static int is_id(int c)
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
- return rb_isalnum(c) || c == '_';
38
+ if (!body->compiled) {
39
+ rb_raise(rb_eRuntimeError, "Liquid::C::BlockBody has not been compiled");
40
+ }
20
41
  }
21
42
 
22
- inline static const char *read_while(const char *start, const char *end, int (func)(int))
43
+ static void block_body_mark(void *ptr)
23
44
  {
24
- while (start < end && func((unsigned char) *start)) start++;
25
- return start;
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
- inline static const char *read_while_end(const char *start, const char *end, int (func)(int))
58
+ static void block_body_free(void *ptr)
29
59
  {
30
- end--;
31
- while (start < end && func((unsigned char) *end)) end--;
32
- end++;
33
- return end;
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 VALUE rb_block_parse(VALUE self, VALUE tokens, VALUE options)
68
+ static size_t block_body_memsize(const void *ptr)
37
69
  {
38
- tokenizer_t *tokenizer;
39
- Tokenizer_Get_Struct(tokens, tokenizer);
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
- VALUE tags = Qnil;
43
- VALUE nodelist = rb_ivar_get(self, intern_nodelist);
123
+ tag_markup_t unknown_tag = { Qnil, Qnil };
124
+ int render_score_increment = 0;
44
125
 
45
126
  while (true) {
46
- if (tokenizer->line_number != 0) {
47
- rb_funcall(options, intern_set_line_number, 1, UINT2NUM(tokenizer->line_number));
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
- return rb_yield_values(2, Qnil, Qnil);
135
+ goto loop_break;
54
136
 
55
137
  case TOKEN_INVALID:
56
138
  {
57
- VALUE str = rb_enc_str_new(token.str, token.length, utf8_encoding);
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.str[1] == '%') raise_method_id = intern_raise_missing_tag_terminator;
142
+ if (token.str_full[1] == '%') raise_method_id = intern_raise_missing_tag_terminator;
61
143
 
62
- return rb_funcall(self, raise_method_id, 2, str, options);
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.str, *end = token.str + token.length, *token_start = start, *token_end = end;
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 (rb_ivar_get(self, intern_blank) == Qtrue) {
77
- const char *end = token.str + token.length;
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
- if (read_while(token.str, end, rb_isspace) < end)
80
- rb_ivar_set(self, intern_blank, Qfalse);
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
- VALUE args[2] = {rb_enc_str_new(token.str + 2 + token.lstrip, token.length - 4 - token.lstrip - token.rstrip, utf8_encoding), options};
87
- VALUE var = rb_class_new_instance(2, args, cLiquidVariable);
88
- rb_ary_push(nodelist, var);
89
- rb_ivar_set(self, intern_blank, Qfalse);
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.str + 2 + token.lstrip, *end = token.str + token.length - 2 - token.rstrip;
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
- VALUE tag_name = rb_enc_str_new(name_start, name_end - name_start, utf8_encoding);
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 (tags == Qnil)
103
- tags = rb_funcall(self, intern_registered_tags, 0);
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
- VALUE tag_class = rb_funcall(tags, intern_square_brackets, 1, tag_name);
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
- return rb_yield_values(2, tag_name, markup);
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, tag_name, markup, tokens, options);
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 (rb_ivar_get(self, intern_blank) == Qtrue && !RTEST(rb_funcall(new_tag, intern_is_blank, 0)))
116
- rb_ivar_set(self, intern_blank, Qfalse);
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
- rb_ary_push(nodelist, new_tag);
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 init_liquid_block()
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
- intern_set_line_number = rb_intern("line_number=");
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
- VALUE cLiquidBlockBody = rb_const_get(mLiquid, rb_intern("BlockBody"));
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
- void init_liquid_block();
4
+ #include "document_body.h"
5
+ #include "vm_assembler_pool.h"
5
6
 
6
- #endif
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