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