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
@@ -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
|
+
}
|