liquid-c 4.0.1 → 4.2.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/cla.yml +23 -0
- data/.github/workflows/liquid.yml +36 -11
- data/.gitignore +4 -0
- data/.rubocop.yml +14 -0
- data/Gemfile +15 -5
- data/README.md +32 -8
- data/Rakefile +12 -63
- data/ext/liquid_c/block.c +493 -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 +97 -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 +21 -9
- data/ext/liquid_c/intutil.h +22 -0
- data/ext/liquid_c/lexer.c +39 -3
- 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/liquid_vm.c +618 -0
- data/ext/liquid_c/liquid_vm.h +25 -0
- data/ext/liquid_c/parse_context.c +76 -0
- data/ext/liquid_c/parse_context.h +13 -0
- data/ext/liquid_c/parser.c +153 -65
- data/ext/liquid_c/parser.h +4 -2
- data/ext/liquid_c/raw.c +136 -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_assembler.c +491 -0
- data/ext/liquid_c/vm_assembler.h +240 -0
- data/ext/liquid_c/vm_assembler_pool.c +99 -0
- data/ext/liquid_c/vm_assembler_pool.h +26 -0
- data/lib/liquid/c/compile_ext.rb +44 -0
- data/lib/liquid/c/version.rb +3 -1
- data/lib/liquid/c.rb +226 -48
- 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 +21 -2
- data/test/unit/block_test.rb +137 -0
- data/test/unit/context_test.rb +85 -0
- data/test/unit/expression_test.rb +191 -0
- data/test/unit/gc_stress_test.rb +28 -0
- data/test/unit/raw_test.rb +93 -0
- data/test/unit/resource_limits_test.rb +50 -0
- data/test/unit/tokenizer_test.rb +90 -20
- data/test/unit/variable_test.rb +279 -60
- metadata +60 -11
- data/test/liquid_test.rb +0 -11
data/ext/liquid_c/variable.c
CHANGED
@@ -1,25 +1,43 @@
|
|
1
1
|
#include "liquid.h"
|
2
2
|
#include "variable.h"
|
3
3
|
#include "parser.h"
|
4
|
+
#include "expression.h"
|
5
|
+
#include "liquid_vm.h"
|
6
|
+
|
4
7
|
#include <stdio.h>
|
5
8
|
|
6
|
-
static
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
static ID id_rescue_strict_parse_syntax_error;
|
10
|
+
|
11
|
+
static ID id_ivar_parse_context;
|
12
|
+
static ID id_ivar_name;
|
13
|
+
static ID id_ivar_filters;
|
10
14
|
|
15
|
+
VALUE cLiquidCVariableExpression;
|
16
|
+
|
17
|
+
static VALUE frozen_empty_array;
|
18
|
+
|
19
|
+
static VALUE try_variable_strict_parse(VALUE uncast_args)
|
20
|
+
{
|
21
|
+
variable_parse_args_t *parse_args = (void *)uncast_args;
|
11
22
|
parser_t p;
|
12
|
-
init_parser(&p,
|
23
|
+
init_parser(&p, parse_args->markup, parse_args->markup_end);
|
24
|
+
vm_assembler_t *code = parse_args->code;
|
13
25
|
|
14
|
-
if (p.cur.type == TOKEN_EOS)
|
26
|
+
if (p.cur.type == TOKEN_EOS) {
|
27
|
+
vm_assembler_add_push_nil(code);
|
15
28
|
return Qnil;
|
29
|
+
}
|
16
30
|
|
17
|
-
|
31
|
+
parse_and_compile_expression(&p, code);
|
18
32
|
|
19
33
|
while (parser_consume(&p, TOKEN_PIPE).type) {
|
20
|
-
lexer_token_t
|
34
|
+
lexer_token_t filter_name_token = parser_must_consume(&p, TOKEN_IDENTIFIER);
|
35
|
+
VALUE filter_name = token_to_rsym(filter_name_token);
|
21
36
|
|
22
|
-
|
37
|
+
size_t arg_count = 0;
|
38
|
+
size_t keyword_arg_count = 0;
|
39
|
+
VALUE push_keywords_obj = Qnil;
|
40
|
+
vm_assembler_t *push_keywords_code = NULL;
|
23
41
|
|
24
42
|
if (parser_consume(&p, TOKEN_COLON).type) {
|
25
43
|
do {
|
@@ -27,28 +45,186 @@ static VALUE rb_variable_parse(VALUE self, VALUE markup, VALUE filters)
|
|
27
45
|
VALUE key = token_to_rstr(parser_consume_any(&p));
|
28
46
|
parser_consume_any(&p);
|
29
47
|
|
30
|
-
|
31
|
-
|
48
|
+
keyword_arg_count++;
|
49
|
+
|
50
|
+
if (push_keywords_obj == Qnil) {
|
51
|
+
expression_t *push_keywords_expr;
|
52
|
+
// use an object to automatically free on an exception
|
53
|
+
push_keywords_obj = expression_new(cLiquidCExpression, &push_keywords_expr);
|
54
|
+
rb_obj_hide(push_keywords_obj);
|
55
|
+
push_keywords_code = &push_keywords_expr->code;
|
56
|
+
}
|
57
|
+
|
58
|
+
vm_assembler_add_push_const(push_keywords_code, key);
|
59
|
+
parse_and_compile_expression(&p, push_keywords_code);
|
32
60
|
} else {
|
33
|
-
|
61
|
+
parse_and_compile_expression(&p, code);
|
62
|
+
arg_count++;
|
34
63
|
}
|
35
64
|
} while (parser_consume(&p, TOKEN_COMMA).type);
|
36
65
|
}
|
37
66
|
|
38
|
-
if (
|
39
|
-
|
40
|
-
|
41
|
-
|
67
|
+
if (keyword_arg_count) {
|
68
|
+
arg_count++;
|
69
|
+
if (keyword_arg_count > 255)
|
70
|
+
rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "Too many filter keyword arguments");
|
71
|
+
|
72
|
+
vm_assembler_concat(code, push_keywords_code);
|
73
|
+
vm_assembler_add_hash_new(code, keyword_arg_count);
|
74
|
+
|
75
|
+
RB_GC_GUARD(push_keywords_obj);
|
42
76
|
}
|
43
|
-
|
77
|
+
vm_assembler_add_filter(code, filter_name, arg_count);
|
44
78
|
}
|
45
79
|
|
46
80
|
parser_must_consume(&p, TOKEN_EOS);
|
47
|
-
|
81
|
+
|
82
|
+
return Qnil;
|
83
|
+
}
|
84
|
+
|
85
|
+
typedef struct variable_strict_parse_rescue {
|
86
|
+
variable_parse_args_t *parse_args;
|
87
|
+
size_t instructions_size;
|
88
|
+
size_t constants_size;
|
89
|
+
size_t stack_size;
|
90
|
+
} variable_strict_parse_rescue_t;
|
91
|
+
|
92
|
+
static VALUE variable_strict_parse_rescue(VALUE uncast_args, VALUE exception)
|
93
|
+
{
|
94
|
+
variable_strict_parse_rescue_t *rescue_args = (void *)uncast_args;
|
95
|
+
variable_parse_args_t *parse_args = rescue_args->parse_args;
|
96
|
+
vm_assembler_t *code = parse_args->code;
|
97
|
+
|
98
|
+
// undo partial strict parse
|
99
|
+
uint8_t *last_constants_data_end = (uint8_t *)code->constants.data + rescue_args->constants_size;
|
100
|
+
VALUE *const_ptr = (VALUE *)last_constants_data_end;
|
101
|
+
st_table *constants_table = code->constants_table;
|
102
|
+
|
103
|
+
while((uint8_t *)const_ptr < code->constants.data_end) {
|
104
|
+
st_data_t key = (st_data_t)const_ptr[0];
|
105
|
+
st_delete(constants_table, &key, 0);
|
106
|
+
const_ptr++;
|
107
|
+
}
|
108
|
+
|
109
|
+
code->instructions.data_end = code->instructions.data + rescue_args->instructions_size;
|
110
|
+
code->constants.data_end = last_constants_data_end;
|
111
|
+
code->stack_size = rescue_args->stack_size;
|
112
|
+
|
113
|
+
if (rb_obj_is_kind_of(exception, cLiquidSyntaxError) == Qfalse)
|
114
|
+
rb_exc_raise(exception);
|
115
|
+
|
116
|
+
VALUE markup_obj = rb_enc_str_new(parse_args->markup, parse_args->markup_end - parse_args->markup, utf8_encoding);
|
117
|
+
VALUE variable_obj = rb_funcall(
|
118
|
+
cLiquidVariable, id_rescue_strict_parse_syntax_error, 3,
|
119
|
+
exception, markup_obj, parse_args->parse_context
|
120
|
+
);
|
121
|
+
|
122
|
+
// lax parse
|
123
|
+
code->protected_stack_size = code->stack_size;
|
124
|
+
rb_funcall(variable_obj, id_compile_evaluate, 1, parse_args->code_obj);
|
125
|
+
if (code->stack_size != code->protected_stack_size + 1) {
|
126
|
+
rb_raise(rb_eRuntimeError, "Liquid::Variable#compile_evaluate didn't leave exactly 1 new element on the stack");
|
127
|
+
}
|
128
|
+
|
129
|
+
return Qnil;
|
130
|
+
}
|
131
|
+
|
132
|
+
void internal_variable_compile_evaluate(variable_parse_args_t *parse_args)
|
133
|
+
{
|
134
|
+
vm_assembler_t *code = parse_args->code;
|
135
|
+
variable_strict_parse_rescue_t rescue_args = {
|
136
|
+
.parse_args = parse_args,
|
137
|
+
.instructions_size = c_buffer_size(&code->instructions),
|
138
|
+
.constants_size = c_buffer_size(&code->constants),
|
139
|
+
.stack_size = code->stack_size,
|
140
|
+
};
|
141
|
+
rb_rescue(try_variable_strict_parse, (VALUE)parse_args, variable_strict_parse_rescue, (VALUE)&rescue_args);
|
48
142
|
}
|
49
143
|
|
50
|
-
void
|
144
|
+
void internal_variable_compile(variable_parse_args_t *parse_args, unsigned int line_number)
|
51
145
|
{
|
52
|
-
|
146
|
+
vm_assembler_t *code = parse_args->code;
|
147
|
+
vm_assembler_add_render_variable_rescue(code, line_number);
|
148
|
+
internal_variable_compile_evaluate(parse_args);
|
149
|
+
vm_assembler_add_pop_write(code);
|
150
|
+
}
|
151
|
+
|
152
|
+
static VALUE variable_strict_parse_method(VALUE self, VALUE markup)
|
153
|
+
{
|
154
|
+
StringValue(markup);
|
155
|
+
check_utf8_encoding(markup, "markup");
|
156
|
+
|
157
|
+
VALUE parse_context = rb_ivar_get(self, id_ivar_parse_context);
|
158
|
+
|
159
|
+
expression_t *expression;
|
160
|
+
VALUE expression_obj = expression_new(cLiquidCVariableExpression, &expression);
|
161
|
+
|
162
|
+
variable_parse_args_t parse_args = {
|
163
|
+
.markup = RSTRING_PTR(markup),
|
164
|
+
.markup_end = RSTRING_END(markup),
|
165
|
+
.code = &expression->code,
|
166
|
+
.code_obj = expression_obj,
|
167
|
+
.parse_context = parse_context,
|
168
|
+
};
|
169
|
+
try_variable_strict_parse((VALUE)&parse_args);
|
170
|
+
RB_GC_GUARD(markup);
|
171
|
+
assert(expression->code.stack_size == 1);
|
172
|
+
vm_assembler_add_leave(&expression->code);
|
173
|
+
|
174
|
+
rb_ivar_set(self, id_ivar_name, expression_obj);
|
175
|
+
rb_ivar_set(self, id_ivar_filters, frozen_empty_array);
|
176
|
+
|
177
|
+
return Qnil;
|
178
|
+
}
|
179
|
+
|
180
|
+
typedef struct {
|
181
|
+
expression_t *expression;
|
182
|
+
VALUE context;
|
183
|
+
} variable_expression_evaluate_args_t;
|
184
|
+
|
185
|
+
static VALUE try_variable_expression_evaluate(VALUE uncast_args)
|
186
|
+
{
|
187
|
+
variable_expression_evaluate_args_t *args = (void *)uncast_args;
|
188
|
+
return liquid_vm_evaluate(args->context, &args->expression->code);
|
189
|
+
}
|
190
|
+
|
191
|
+
static VALUE rescue_variable_expression_evaluate(VALUE uncast_args, VALUE exception)
|
192
|
+
{
|
193
|
+
variable_expression_evaluate_args_t *args = (void *)uncast_args;
|
194
|
+
vm_t *vm = vm_from_context(args->context);
|
195
|
+
exception = vm_translate_if_filter_argument_error(vm, exception);
|
196
|
+
rb_exc_raise(exception);
|
197
|
+
}
|
198
|
+
|
199
|
+
VALUE internal_variable_expression_evaluate(expression_t *expression, VALUE context)
|
200
|
+
{
|
201
|
+
variable_expression_evaluate_args_t args = { expression, context };
|
202
|
+
return rb_rescue(try_variable_expression_evaluate, (VALUE)&args, rescue_variable_expression_evaluate, (VALUE)&args);
|
203
|
+
}
|
204
|
+
|
205
|
+
static VALUE variable_expression_evaluate_method(VALUE self, VALUE context)
|
206
|
+
{
|
207
|
+
expression_t *expression;
|
208
|
+
Expression_Get_Struct(self, expression);
|
209
|
+
return internal_variable_expression_evaluate(expression, context);
|
210
|
+
}
|
211
|
+
|
212
|
+
void liquid_define_variable(void)
|
213
|
+
{
|
214
|
+
id_rescue_strict_parse_syntax_error = rb_intern("rescue_strict_parse_syntax_error");
|
215
|
+
|
216
|
+
id_ivar_parse_context = rb_intern("@parse_context");
|
217
|
+
id_ivar_name = rb_intern("@name");
|
218
|
+
id_ivar_filters = rb_intern("@filters");
|
219
|
+
|
220
|
+
frozen_empty_array = rb_ary_new();
|
221
|
+
rb_ary_freeze(frozen_empty_array);
|
222
|
+
rb_global_variable(&frozen_empty_array);
|
223
|
+
|
224
|
+
rb_define_method(cLiquidVariable, "c_strict_parse", variable_strict_parse_method, 1);
|
225
|
+
|
226
|
+
cLiquidCVariableExpression = rb_define_class_under(mLiquidC, "VariableExpression", cLiquidCExpression);
|
227
|
+
rb_global_variable(&cLiquidCVariableExpression);
|
228
|
+
rb_define_method(cLiquidCVariableExpression, "evaluate", variable_expression_evaluate_method, 1);
|
53
229
|
}
|
54
230
|
|
data/ext/liquid_c/variable.h
CHANGED
@@ -1,7 +1,24 @@
|
|
1
1
|
#if !defined(LIQUID_VARIABLE_H)
|
2
2
|
#define LIQUID_VARIABLE_H
|
3
3
|
|
4
|
-
|
4
|
+
#include "vm_assembler.h"
|
5
|
+
#include "block.h"
|
6
|
+
#include "expression.h"
|
7
|
+
|
8
|
+
extern VALUE cLiquidCVariableExpression;
|
9
|
+
|
10
|
+
typedef struct variable_parse_args {
|
11
|
+
const char *markup;
|
12
|
+
const char *markup_end;
|
13
|
+
vm_assembler_t *code;
|
14
|
+
VALUE code_obj;
|
15
|
+
VALUE parse_context;
|
16
|
+
} variable_parse_args_t;
|
17
|
+
|
18
|
+
void liquid_define_variable(void);
|
19
|
+
void internal_variable_compile(variable_parse_args_t *parse_args, unsigned int line_number);
|
20
|
+
void internal_variable_compile_evaluate(variable_parse_args_t *parse_args);
|
21
|
+
VALUE internal_variable_expression_evaluate(expression_t *expression, VALUE context);
|
5
22
|
|
6
23
|
#endif
|
7
24
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#include "liquid.h"
|
2
|
+
#include "context.h"
|
3
|
+
|
4
|
+
static ID id_has_key, id_aref, id_fetch, id_to_liquid_value;
|
5
|
+
|
6
|
+
VALUE variable_lookup_key(VALUE context, VALUE object, VALUE key, bool is_command)
|
7
|
+
{
|
8
|
+
if (rb_obj_class(key) != rb_cString) {
|
9
|
+
VALUE key_value = rb_check_funcall(key, id_to_liquid_value, 0, 0);
|
10
|
+
|
11
|
+
if (key_value != Qundef) {
|
12
|
+
key = key_value;
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
if (rb_respond_to(object, id_aref) && (
|
17
|
+
(rb_respond_to(object, id_has_key) && rb_funcall(object, id_has_key, 1, key)) ||
|
18
|
+
(rb_obj_is_kind_of(key, rb_cInteger) && rb_respond_to(object, id_fetch))
|
19
|
+
)) {
|
20
|
+
VALUE next_object = rb_funcall(object, id_aref, 1, key);
|
21
|
+
next_object = materialize_proc(context, object, key, next_object);
|
22
|
+
return value_to_liquid_and_set_context(next_object, context);
|
23
|
+
}
|
24
|
+
|
25
|
+
if (is_command) {
|
26
|
+
Check_Type(key, T_STRING);
|
27
|
+
ID intern_key = rb_intern(RSTRING_PTR(key));
|
28
|
+
if (rb_respond_to(object, intern_key)) {
|
29
|
+
VALUE next_object = rb_funcall(object, intern_key, 0);
|
30
|
+
return value_to_liquid_and_set_context(next_object, context);
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
context_maybe_raise_undefined_variable(context, key);
|
35
|
+
return Qnil;
|
36
|
+
}
|
37
|
+
|
38
|
+
void liquid_define_variable_lookup(void)
|
39
|
+
{
|
40
|
+
id_has_key = rb_intern("key?");
|
41
|
+
id_aref = rb_intern("[]");
|
42
|
+
id_fetch = rb_intern("fetch");
|
43
|
+
id_to_liquid_value = rb_intern("to_liquid_value");
|
44
|
+
}
|