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.
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