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
@@ -0,0 +1,42 @@
1
+ #include "c_buffer.h"
2
+
3
+ static void c_buffer_expand_for_write(c_buffer_t *buffer, size_t write_size)
4
+ {
5
+ size_t capacity = c_buffer_capacity(buffer);
6
+ size_t size = c_buffer_size(buffer);
7
+ size_t required_capacity = size + write_size;
8
+
9
+ if (capacity < 1)
10
+ capacity = 1;
11
+ do {
12
+ capacity *= 2;
13
+ } while (capacity < required_capacity);
14
+ buffer->data = xrealloc(buffer->data, capacity);
15
+ buffer->data_end = buffer->data + size;
16
+ buffer->capacity_end = buffer->data + capacity;
17
+ }
18
+
19
+ void c_buffer_zero_pad_for_alignment(c_buffer_t *buffer, size_t alignment)
20
+ {
21
+ size_t unaligned_bytes = c_buffer_size(buffer) % alignment;
22
+ if (unaligned_bytes) {
23
+ size_t pad_size = alignment - unaligned_bytes;
24
+ uint8_t *padding = c_buffer_extend_for_write(buffer, pad_size);
25
+ memset(padding, 0, pad_size);
26
+ }
27
+ }
28
+
29
+ void c_buffer_reserve_for_write(c_buffer_t *buffer, size_t write_size)
30
+ {
31
+ uint8_t *write_end = buffer->data_end + write_size;
32
+ if (write_end > buffer->capacity_end) {
33
+ c_buffer_expand_for_write(buffer, write_size);
34
+ }
35
+ }
36
+
37
+ void c_buffer_write(c_buffer_t *buffer, void *write_data, size_t write_size)
38
+ {
39
+ c_buffer_reserve_for_write(buffer, write_size);
40
+ memcpy(buffer->data_end, write_data, write_size);
41
+ buffer->data_end += write_size;
42
+ }
@@ -0,0 +1,76 @@
1
+ #ifndef LIQUID_C_BUFFER_H
2
+ #define LIQUID_C_BUFFER_H
3
+
4
+ #include <ruby.h>
5
+
6
+ typedef struct c_buffer {
7
+ uint8_t *data;
8
+ uint8_t *data_end;
9
+ uint8_t *capacity_end;
10
+ } c_buffer_t;
11
+
12
+ static inline c_buffer_t c_buffer_init(void)
13
+ {
14
+ return (c_buffer_t) { NULL, NULL, NULL };
15
+ }
16
+
17
+ static inline c_buffer_t c_buffer_allocate(size_t capacity)
18
+ {
19
+ uint8_t *data = xmalloc(capacity);
20
+ return (c_buffer_t) { data, data, data + capacity };
21
+ }
22
+
23
+ static inline void c_buffer_free(c_buffer_t *buffer)
24
+ {
25
+ xfree(buffer->data);
26
+ }
27
+
28
+ static inline void c_buffer_reset(c_buffer_t *buffer)
29
+ {
30
+ buffer->data_end = buffer->data;
31
+ }
32
+
33
+ static inline size_t c_buffer_size(const c_buffer_t *buffer)
34
+ {
35
+ return buffer->data_end - buffer->data;
36
+ }
37
+
38
+ static inline size_t c_buffer_capacity(const c_buffer_t *buffer)
39
+ {
40
+ return buffer->capacity_end - buffer->data;
41
+ }
42
+
43
+ void c_buffer_zero_pad_for_alignment(c_buffer_t *buffer, size_t alignment);
44
+
45
+ void c_buffer_reserve_for_write(c_buffer_t *buffer, size_t write_size);
46
+ void c_buffer_write(c_buffer_t *buffer, void *data, size_t size);
47
+
48
+ static inline void *c_buffer_extend_for_write(c_buffer_t *buffer, size_t write_size) {
49
+ c_buffer_reserve_for_write(buffer, write_size);
50
+ void *write_ptr = buffer->data_end;
51
+ buffer->data_end += write_size;
52
+ return write_ptr;
53
+ }
54
+
55
+ static inline void c_buffer_write_byte(c_buffer_t *buffer, uint8_t byte) {
56
+ c_buffer_write(buffer, &byte, 1);
57
+ }
58
+
59
+ static inline void c_buffer_write_ruby_value(c_buffer_t *buffer, VALUE value) {
60
+ c_buffer_write(buffer, &value, sizeof(VALUE));
61
+ }
62
+
63
+ static inline void c_buffer_rb_gc_mark(c_buffer_t *buffer)
64
+ {
65
+ VALUE *buffer_end = (VALUE *)buffer->data_end;
66
+ for (VALUE *obj_ptr = (VALUE *)buffer->data; obj_ptr < buffer_end; obj_ptr++) {
67
+ rb_gc_mark(*obj_ptr);
68
+ }
69
+ }
70
+
71
+ static inline void c_buffer_concat(c_buffer_t *dest, c_buffer_t *src)
72
+ {
73
+ c_buffer_write(dest, src->data, c_buffer_size(src));
74
+ }
75
+
76
+ #endif
@@ -0,0 +1,233 @@
1
+ #include "liquid.h"
2
+ #include "context.h"
3
+ #include "variable_lookup.h"
4
+ #include "variable.h"
5
+ #include "vm.h"
6
+ #include "expression.h"
7
+ #include "document_body.h"
8
+
9
+ static VALUE cLiquidUndefinedVariable;
10
+ ID id_aset, id_set_context;
11
+ static ID id_has_key, id_aref, id_strainer, id_filter_methods_hash, id_strict_filters, id_global_filter;
12
+ static ID id_ivar_scopes, id_ivar_environments, id_ivar_static_environments, id_ivar_strict_variables, id_ivar_interrupts, id_ivar_resource_limits, id_ivar_document_body;
13
+
14
+ void context_internal_init(VALUE context_obj, context_t *context)
15
+ {
16
+ context->self = context_obj;
17
+
18
+ context->environments = rb_ivar_get(context_obj, id_ivar_environments);
19
+ Check_Type(context->environments, T_ARRAY);
20
+
21
+ context->static_environments = rb_ivar_get(context_obj, id_ivar_static_environments);
22
+ Check_Type(context->static_environments, T_ARRAY);
23
+
24
+ context->scopes = rb_ivar_get(context_obj, id_ivar_scopes);
25
+ Check_Type(context->scopes, T_ARRAY);
26
+
27
+ context->strainer = rb_funcall(context->self, id_strainer, 0);
28
+ Check_Type(context->strainer, T_OBJECT);
29
+
30
+ context->filter_methods = rb_funcall(RBASIC_CLASS(context->strainer), id_filter_methods_hash, 0);
31
+ Check_Type(context->filter_methods, T_HASH);
32
+
33
+ context->interrupts = rb_ivar_get(context->self, id_ivar_interrupts);
34
+ Check_Type(context->interrupts, T_ARRAY);
35
+
36
+ context->resource_limits_obj = rb_ivar_get(context->self, id_ivar_resource_limits);;
37
+ ResourceLimits_Get_Struct(context->resource_limits_obj, context->resource_limits);
38
+
39
+ context->strict_variables = false;
40
+ context->strict_filters = RTEST(rb_funcall(context->self, id_strict_filters, 0));
41
+ context->global_filter = rb_funcall(context->self, id_global_filter, 0);
42
+ }
43
+
44
+ void context_mark(context_t *context)
45
+ {
46
+ rb_gc_mark(context->self);
47
+ rb_gc_mark(context->environments);
48
+ rb_gc_mark(context->static_environments);
49
+ rb_gc_mark(context->scopes);
50
+ rb_gc_mark(context->strainer);
51
+ rb_gc_mark(context->filter_methods);
52
+ rb_gc_mark(context->interrupts);
53
+ rb_gc_mark(context->resource_limits_obj);
54
+ rb_gc_mark(context->global_filter);
55
+ }
56
+
57
+ static context_t *context_from_obj(VALUE self)
58
+ {
59
+ return &vm_from_context(self)->context;
60
+ }
61
+
62
+ static VALUE context_evaluate(VALUE self, VALUE expression)
63
+ {
64
+ // Scalar type stored directly in the VALUE, this needs to be checked anyways to use RB_BUILTIN_TYPE
65
+ if (RB_SPECIAL_CONST_P(expression))
66
+ return expression;
67
+
68
+ switch (RB_BUILTIN_TYPE(expression)) {
69
+ case T_DATA:
70
+ {
71
+ if (RTYPEDDATA_P(expression) && RTYPEDDATA_TYPE(expression) == &expression_data_type) {
72
+ if (RBASIC_CLASS(expression) == cLiquidCExpression) {
73
+ return internal_expression_evaluate(DATA_PTR(expression), self);
74
+ } else {
75
+ assert(RBASIC_CLASS(expression) == cLiquidCVariableExpression);
76
+ return internal_variable_expression_evaluate(DATA_PTR(expression), self);
77
+ }
78
+ }
79
+ break; // e.g. BigDecimal
80
+ }
81
+ case T_OBJECT: // may be Liquid::VariableLookup or Liquid::RangeLookup
82
+ {
83
+ VALUE result = rb_check_funcall(expression, id_evaluate, 1, &self);
84
+ return RB_LIKELY(result != Qundef) ? result : expression;
85
+ }
86
+ default:
87
+ break;
88
+ }
89
+ return expression;
90
+ }
91
+
92
+ void context_maybe_raise_undefined_variable(VALUE self, VALUE key)
93
+ {
94
+ context_t *context = context_from_obj(self);
95
+ if (context->strict_variables) {
96
+ Check_Type(key, T_STRING);
97
+ rb_enc_raise(utf8_encoding, cLiquidUndefinedVariable, "undefined variable %s", RSTRING_PTR(key));
98
+ }
99
+ }
100
+
101
+ static bool environments_find_variable(VALUE environments, VALUE key, bool strict_variables, VALUE raise_on_not_found, VALUE *scope_out, VALUE *variable_out) {
102
+ VALUE variable = Qnil;
103
+ Check_Type(environments, T_ARRAY);
104
+
105
+ for (long i = 0; i < RARRAY_LEN(environments); i++) {
106
+ VALUE this_environ = RARRAY_AREF(environments, i);
107
+ if (RB_LIKELY(TYPE(this_environ) == T_HASH)) {
108
+ // Does not invoke any default value proc, this is equivalent in
109
+ // cost and semantics to #key? but loads the value as well
110
+ variable = rb_hash_lookup2(this_environ, key, Qundef);
111
+ if (variable != Qundef) {
112
+ *variable_out = variable;
113
+ *scope_out = this_environ;
114
+ return true;
115
+ }
116
+
117
+ if (!(RTEST(raise_on_not_found) && strict_variables)) {
118
+ // If we aren't running strictly, we need to invoke the default
119
+ // value proc, rb_hash_aref does this
120
+ variable = rb_hash_aref(this_environ, key);
121
+ if (variable != Qnil) {
122
+ *variable_out = variable;
123
+ *scope_out = this_environ;
124
+ return true;
125
+ }
126
+ }
127
+ } else if (RTEST(rb_funcall(this_environ, id_has_key, 1, key))) {
128
+ // Slow path: It is valid to pass a non-hash value to Liquid as an
129
+ // environment if it supports #key? and #[]
130
+ *variable_out = rb_funcall(this_environ, id_aref, 1, key);
131
+ *scope_out = this_environ;
132
+ return true;
133
+ }
134
+ }
135
+ return false;
136
+ }
137
+
138
+ VALUE context_find_variable(context_t *context, VALUE key, VALUE raise_on_not_found)
139
+ {
140
+ VALUE self = context->self;
141
+ VALUE scope = Qnil, variable = Qnil;
142
+
143
+ VALUE scopes = context->scopes;
144
+ for (long i = 0; i < RARRAY_LEN(scopes); i++) {
145
+ VALUE this_scope = RARRAY_AREF(scopes, i);
146
+ if (RB_LIKELY(TYPE(this_scope) == T_HASH)) {
147
+ // Does not invoke any default value proc, this is equivalent in
148
+ // cost and semantics to #key? but loads the value as well
149
+ variable = rb_hash_lookup2(this_scope, key, Qundef);
150
+ if (variable != Qundef) {
151
+ scope = this_scope;
152
+ goto variable_found;
153
+ }
154
+ } else if (RTEST(rb_funcall(this_scope, id_has_key, 1, key))) {
155
+ // Slow path: It is valid to pass a non-hash value to Liquid as a
156
+ // scope if it supports #key? and #[]
157
+ variable = rb_funcall(this_scope, id_aref, 1, key);
158
+ goto variable_found;
159
+ }
160
+ }
161
+
162
+ if (environments_find_variable(context->environments, key, context->strict_variables, raise_on_not_found,
163
+ &scope, &variable))
164
+ goto variable_found;
165
+
166
+ if (environments_find_variable(context->static_environments, key, context->strict_variables, raise_on_not_found,
167
+ &scope, &variable))
168
+ goto variable_found;
169
+
170
+ if (RTEST(raise_on_not_found)) {
171
+ context_maybe_raise_undefined_variable(self, key);
172
+ }
173
+ variable = Qnil;
174
+
175
+ variable_found:
176
+ variable = materialize_proc(self, scope, key, variable);
177
+ variable = value_to_liquid_and_set_context(variable, self);
178
+
179
+ return variable;
180
+ }
181
+
182
+ static VALUE context_find_variable_method(VALUE self, VALUE key, VALUE raise_on_not_found)
183
+ {
184
+ return context_find_variable(context_from_obj(self), key, raise_on_not_found);
185
+ }
186
+
187
+ static VALUE context_set_strict_variables(VALUE self, VALUE strict_variables)
188
+ {
189
+ context_t *context = context_from_obj(self);
190
+ context->strict_variables = RTEST(strict_variables);
191
+ rb_ivar_set(self, id_ivar_strict_variables, strict_variables);
192
+ return Qnil;
193
+ }
194
+
195
+ // Shopify requires checking if we are filtering, so provide a
196
+ // way to do that in liquid-c until we figure out how we want to
197
+ // support that longer term.
198
+ VALUE context_filtering_p(VALUE self)
199
+ {
200
+ return liquid_vm_filtering(self) ? Qtrue : Qfalse;
201
+ }
202
+
203
+ void liquid_define_context(void)
204
+ {
205
+ id_has_key = rb_intern("key?");
206
+ id_aset = rb_intern("[]=");
207
+ id_aref = rb_intern("[]");
208
+ id_set_context = rb_intern("context=");
209
+ id_strainer = rb_intern("strainer");
210
+ id_filter_methods_hash = rb_intern("filter_methods_hash");
211
+ id_strict_filters = rb_intern("strict_filters");
212
+ id_global_filter = rb_intern("global_filter");
213
+
214
+ id_ivar_scopes = rb_intern("@scopes");
215
+ id_ivar_environments = rb_intern("@environments");
216
+ id_ivar_static_environments = rb_intern("@static_environments");
217
+ id_ivar_strict_variables = rb_intern("@strict_variables");
218
+ id_ivar_interrupts = rb_intern("@interrupts");
219
+ id_ivar_resource_limits = rb_intern("@resource_limits");
220
+ id_ivar_document_body = rb_intern("@document_body");
221
+
222
+ cLiquidVariableLookup = rb_const_get(mLiquid, rb_intern("VariableLookup"));
223
+ rb_global_variable(&cLiquidVariableLookup);
224
+
225
+ cLiquidUndefinedVariable = rb_const_get(mLiquid, rb_intern("UndefinedVariable"));
226
+ rb_global_variable(&cLiquidUndefinedVariable);
227
+
228
+ VALUE cLiquidContext = rb_const_get(mLiquid, rb_intern("Context"));
229
+ rb_define_method(cLiquidContext, "c_evaluate", context_evaluate, 1);
230
+ rb_define_method(cLiquidContext, "c_find_variable", context_find_variable_method, 2);
231
+ rb_define_method(cLiquidContext, "c_strict_variables=", context_set_strict_variables, 1);
232
+ rb_define_private_method(cLiquidContext, "c_filtering?", context_filtering_p, 0);
233
+ }
@@ -0,0 +1,70 @@
1
+ #if !defined(LIQUID_CONTEXT_H)
2
+ #define LIQUID_CONTEXT_H
3
+
4
+ #include "resource_limits.h"
5
+
6
+ typedef struct context {
7
+ VALUE self;
8
+ VALUE environments;
9
+ VALUE static_environments;
10
+ VALUE scopes;
11
+ VALUE strainer;
12
+ VALUE filter_methods;
13
+ VALUE interrupts;
14
+ VALUE resource_limits_obj;
15
+ resource_limits_t *resource_limits;
16
+ VALUE global_filter;
17
+ bool strict_variables;
18
+ bool strict_filters;
19
+ } context_t;
20
+
21
+ void liquid_define_context(void);
22
+ void context_internal_init(VALUE context_obj, context_t *context);
23
+ void context_mark(context_t *context);
24
+ VALUE context_find_variable(context_t *context, VALUE key, VALUE raise_on_not_found);
25
+ void context_maybe_raise_undefined_variable(VALUE self, VALUE key);
26
+
27
+ extern ID id_aset, id_set_context;
28
+
29
+ #ifndef RB_SPECIAL_CONST_P
30
+ // RB_SPECIAL_CONST_P added in Ruby 2.3
31
+ #define RB_SPECIAL_CONST_P SPECIAL_CONST_P
32
+ #endif
33
+
34
+ inline static VALUE value_to_liquid_and_set_context(VALUE value, VALUE context_to_set)
35
+ {
36
+ // Scalar type stored directly in the VALUE, these all have a #to_liquid
37
+ // that returns self, and should have no #context= method
38
+ if (RB_SPECIAL_CONST_P(value))
39
+ return value;
40
+
41
+ VALUE klass = RBASIC(value)->klass;
42
+
43
+ // More basic types having #to_liquid of self and no #context=
44
+ if (klass == rb_cString || klass == rb_cArray || klass == rb_cHash)
45
+ return value;
46
+
47
+ value = rb_funcall(value, id_to_liquid, 0);
48
+
49
+ if (rb_respond_to(value, id_set_context))
50
+ rb_funcall(value, id_set_context, 1, context_to_set);
51
+
52
+ return value;
53
+ }
54
+
55
+
56
+ inline static VALUE materialize_proc(VALUE context, VALUE scope, VALUE key, VALUE value)
57
+ {
58
+ if (scope != Qnil && rb_obj_is_proc(value) && rb_respond_to(scope, id_aset)) {
59
+ if (rb_proc_arity(value) == 1) {
60
+ value = rb_funcall(value, id_call, 1, context);
61
+ } else {
62
+ value = rb_funcall(value, id_call, 0);
63
+ }
64
+ rb_funcall(scope, id_aset, 2, key, value);
65
+ }
66
+ return value;
67
+ }
68
+
69
+ #endif
70
+
@@ -0,0 +1,89 @@
1
+ #include <ruby.h>
2
+ #include <stdalign.h>
3
+ #include "liquid.h"
4
+ #include "vm_assembler.h"
5
+ #include "document_body.h"
6
+
7
+ static VALUE cLiquidCDocumentBody;
8
+
9
+ static void document_body_mark(void *ptr)
10
+ {
11
+ document_body_t *body = ptr;
12
+ rb_gc_mark(body->constants);
13
+ }
14
+
15
+ static void document_body_free(void *ptr)
16
+ {
17
+ document_body_t *body = ptr;
18
+ c_buffer_free(&body->buffer);
19
+ xfree(body);
20
+ }
21
+
22
+ static size_t document_body_memsize(const void *ptr)
23
+ {
24
+ const document_body_t *body = ptr;
25
+ return sizeof(document_body_t) + c_buffer_size(&body->buffer);
26
+ }
27
+
28
+ const rb_data_type_t document_body_data_type = {
29
+ "liquid_document_body",
30
+ { document_body_mark, document_body_free, document_body_memsize },
31
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
32
+ };
33
+
34
+ static VALUE document_body_allocate(VALUE klass)
35
+ {
36
+ document_body_t *body;
37
+
38
+ VALUE obj = TypedData_Make_Struct(klass, document_body_t, &document_body_data_type, body);
39
+ body->self = obj;
40
+ body->constants = rb_ary_new();
41
+ body->buffer = c_buffer_init();
42
+
43
+ return obj;
44
+ }
45
+
46
+ #define DocumentBody_Get_Struct(obj, sval) TypedData_Get_Struct(obj, document_body_t, &document_body_data_type, sval)
47
+
48
+ VALUE document_body_new_instance(void)
49
+ {
50
+ return rb_class_new_instance(0, NULL, cLiquidCDocumentBody);
51
+ }
52
+
53
+ document_body_entry_t document_body_write_block_body(VALUE self, bool blank, uint32_t render_score, vm_assembler_t *code)
54
+ {
55
+ assert(!RB_OBJ_FROZEN(self));
56
+
57
+ document_body_t *body;
58
+ DocumentBody_Get_Struct(self, body);
59
+
60
+ c_buffer_zero_pad_for_alignment(&body->buffer, alignof(block_body_header_t));
61
+
62
+ size_t buffer_offset = c_buffer_size(&body->buffer);
63
+
64
+ assert(c_buffer_size(&code->constants) % sizeof(VALUE) == 0);
65
+ uint32_t constants_len = (uint32_t)(c_buffer_size(&code->constants) / sizeof(VALUE));
66
+
67
+ block_body_header_t *buf_block_body = c_buffer_extend_for_write(&body->buffer, sizeof(block_body_header_t));
68
+ buf_block_body->instructions_offset = (uint32_t)sizeof(block_body_header_t);
69
+ buf_block_body->instructions_bytes = (uint32_t)c_buffer_size(&code->instructions);
70
+ buf_block_body->constants_offset = (uint32_t)RARRAY_LEN(body->constants);
71
+ buf_block_body->constants_len = constants_len;
72
+ buf_block_body->flags = 0;
73
+ if (blank) buf_block_body->flags |= BLOCK_BODY_HEADER_FLAG_BLANK;
74
+ buf_block_body->render_score = render_score;
75
+ buf_block_body->max_stack_size = code->max_stack_size;
76
+
77
+ c_buffer_concat(&body->buffer, &code->instructions);
78
+
79
+ rb_ary_cat(body->constants, (VALUE *)code->constants.data, constants_len);
80
+
81
+ return (document_body_entry_t) { .body = body, .buffer_offset = buffer_offset };
82
+ }
83
+
84
+ void liquid_define_document_body(void)
85
+ {
86
+ cLiquidCDocumentBody = rb_define_class_under(mLiquidC, "DocumentBody", rb_cObject);
87
+ rb_global_variable(&cLiquidCDocumentBody);
88
+ rb_define_alloc_func(cLiquidCDocumentBody, document_body_allocate);
89
+ }
@@ -0,0 +1,59 @@
1
+ #ifndef LIQUID_DOCUMENT_BODY_H
2
+ #define LIQUID_DOCUMENT_BODY_H
3
+
4
+ #include "c_buffer.h"
5
+ #include "vm_assembler.h"
6
+
7
+ typedef struct block_body_header {
8
+ uint32_t instructions_offset;
9
+ uint32_t instructions_bytes;
10
+ uint32_t constants_offset;
11
+ uint32_t constants_len;
12
+ uint32_t flags;
13
+ uint32_t render_score;
14
+ uint64_t max_stack_size;
15
+ } block_body_header_t;
16
+
17
+ #define BLOCK_BODY_HEADER_FLAG_BLANK (1 << 0)
18
+ #define BLOCK_BODY_HEADER_BLANK_P(header) (header->flags & BLOCK_BODY_HEADER_FLAG_BLANK)
19
+
20
+ typedef struct document_body {
21
+ VALUE self;
22
+ VALUE constants;
23
+ c_buffer_t buffer;
24
+ } document_body_t;
25
+
26
+ typedef struct document_body_entry {
27
+ document_body_t *body;
28
+ size_t buffer_offset;
29
+ } document_body_entry_t;
30
+
31
+ void liquid_define_document_body(void);
32
+ VALUE document_body_new_instance(void);
33
+ document_body_entry_t document_body_write_block_body(VALUE self, bool blank, uint32_t render_score, vm_assembler_t *code);
34
+
35
+ static inline void document_body_entry_mark(document_body_entry_t *entry)
36
+ {
37
+ rb_gc_mark(entry->body->self);
38
+ rb_gc_mark(entry->body->constants);
39
+ }
40
+
41
+ static inline block_body_header_t *document_body_get_block_body_header_ptr(const document_body_entry_t *entry)
42
+ {
43
+ return (block_body_header_t *)(entry->body->buffer.data + entry->buffer_offset);
44
+ }
45
+
46
+ static inline const VALUE *document_body_get_constants_ptr(const document_body_entry_t *entry)
47
+ {
48
+ block_body_header_t *header = document_body_get_block_body_header_ptr(entry);
49
+ return RARRAY_PTR(entry->body->constants) + header->constants_offset;
50
+ }
51
+
52
+ static inline void document_body_ensure_compile_finished(document_body_t *body)
53
+ {
54
+ if (RB_UNLIKELY(!RB_OBJ_FROZEN(body->self))) {
55
+ rb_raise(rb_eRuntimeError, "Liquid document hasn't finished compilation");
56
+ }
57
+ }
58
+
59
+ #endif
@@ -0,0 +1,116 @@
1
+ #include "liquid.h"
2
+ #include "vm_assembler.h"
3
+ #include "parser.h"
4
+ #include "vm.h"
5
+ #include "expression.h"
6
+
7
+ VALUE cLiquidCExpression;
8
+
9
+ static void expression_mark(void *ptr)
10
+ {
11
+ expression_t *expression = ptr;
12
+ vm_assembler_gc_mark(&expression->code);
13
+ }
14
+
15
+ static void expression_free(void *ptr)
16
+ {
17
+ expression_t *expression = ptr;
18
+ vm_assembler_free(&expression->code);
19
+ xfree(expression);
20
+ }
21
+
22
+ static size_t expression_memsize(const void *ptr)
23
+ {
24
+ const expression_t *expression = ptr;
25
+ return sizeof(expression_t) + vm_assembler_alloc_memsize(&expression->code);
26
+ }
27
+
28
+ const rb_data_type_t expression_data_type = {
29
+ "liquid_expression",
30
+ { expression_mark, expression_free, expression_memsize, },
31
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
32
+ };
33
+
34
+ VALUE expression_new(VALUE klass, expression_t **expression_ptr)
35
+ {
36
+ expression_t *expression;
37
+ VALUE obj = TypedData_Make_Struct(klass, expression_t, &expression_data_type, expression);
38
+ *expression_ptr = expression;
39
+ vm_assembler_init(&expression->code);
40
+ return obj;
41
+ }
42
+
43
+ static VALUE internal_expression_parse(parser_t *p)
44
+ {
45
+ if (p->cur.type == TOKEN_EOS)
46
+ return Qnil;
47
+
48
+ // Avoid allocating an expression object just to wrap a constant
49
+ VALUE const_obj = try_parse_constant_expression(p);
50
+ if (const_obj != Qundef)
51
+ return const_obj;
52
+
53
+ expression_t *expression;
54
+ VALUE expr_obj = expression_new(cLiquidCExpression, &expression);
55
+
56
+ parse_and_compile_expression(p, &expression->code);
57
+ assert(expression->code.stack_size == 1);
58
+ vm_assembler_add_leave(&expression->code);
59
+
60
+ return expr_obj;
61
+ }
62
+
63
+ static VALUE expression_strict_parse(VALUE klass, VALUE markup)
64
+ {
65
+ if (NIL_P(markup))
66
+ return Qnil;
67
+
68
+ StringValue(markup);
69
+ char *start = RSTRING_PTR(markup);
70
+
71
+ parser_t p;
72
+ init_parser(&p, start, start + RSTRING_LEN(markup));
73
+ VALUE expr_obj = internal_expression_parse(&p);
74
+
75
+ if (p.cur.type != TOKEN_EOS)
76
+ rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "[:%s] is not a valid expression", symbol_names[p.cur.type]);
77
+
78
+ return expr_obj;
79
+ }
80
+
81
+ VALUE expression_evaluate(VALUE self, VALUE context)
82
+ {
83
+ expression_t *expression;
84
+ Expression_Get_Struct(self, expression);
85
+ return liquid_vm_evaluate(context, &expression->code);
86
+ }
87
+
88
+ VALUE internal_expression_evaluate(expression_t *expression, VALUE context)
89
+ {
90
+ return liquid_vm_evaluate(context, &expression->code);
91
+ }
92
+
93
+ static VALUE expression_disassemble(VALUE self)
94
+ {
95
+ expression_t *expression;
96
+ Expression_Get_Struct(self, expression);
97
+
98
+ VALUE constants = rb_ary_new();
99
+ uint32_t constants_len = (uint32_t)(c_buffer_size(&expression->code.constants) / sizeof(VALUE));
100
+ rb_ary_cat(constants, (VALUE *)expression->code.constants.data, constants_len);
101
+
102
+ return vm_assembler_disassemble(
103
+ expression->code.instructions.data,
104
+ expression->code.instructions.data_end,
105
+ &constants
106
+ );
107
+ }
108
+
109
+ void liquid_define_expression(void)
110
+ {
111
+ cLiquidCExpression = rb_define_class_under(mLiquidC, "Expression", rb_cObject);
112
+ rb_undef_alloc_func(cLiquidCExpression);
113
+ rb_define_singleton_method(cLiquidCExpression, "strict_parse", expression_strict_parse, 1);
114
+ rb_define_method(cLiquidCExpression, "evaluate", expression_evaluate, 1);
115
+ rb_define_method(cLiquidCExpression, "disassemble", expression_disassemble, 0);
116
+ }