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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/cla.yml +23 -0
  3. data/.github/workflows/liquid.yml +36 -11
  4. data/.gitignore +4 -0
  5. data/.rubocop.yml +14 -0
  6. data/Gemfile +15 -5
  7. data/README.md +32 -8
  8. data/Rakefile +12 -63
  9. data/ext/liquid_c/block.c +493 -60
  10. data/ext/liquid_c/block.h +28 -2
  11. data/ext/liquid_c/c_buffer.c +42 -0
  12. data/ext/liquid_c/c_buffer.h +76 -0
  13. data/ext/liquid_c/context.c +233 -0
  14. data/ext/liquid_c/context.h +70 -0
  15. data/ext/liquid_c/document_body.c +97 -0
  16. data/ext/liquid_c/document_body.h +59 -0
  17. data/ext/liquid_c/expression.c +116 -0
  18. data/ext/liquid_c/expression.h +24 -0
  19. data/ext/liquid_c/extconf.rb +21 -9
  20. data/ext/liquid_c/intutil.h +22 -0
  21. data/ext/liquid_c/lexer.c +39 -3
  22. data/ext/liquid_c/lexer.h +18 -3
  23. data/ext/liquid_c/liquid.c +76 -6
  24. data/ext/liquid_c/liquid.h +24 -1
  25. data/ext/liquid_c/liquid_vm.c +618 -0
  26. data/ext/liquid_c/liquid_vm.h +25 -0
  27. data/ext/liquid_c/parse_context.c +76 -0
  28. data/ext/liquid_c/parse_context.h +13 -0
  29. data/ext/liquid_c/parser.c +153 -65
  30. data/ext/liquid_c/parser.h +4 -2
  31. data/ext/liquid_c/raw.c +136 -0
  32. data/ext/liquid_c/raw.h +6 -0
  33. data/ext/liquid_c/resource_limits.c +279 -0
  34. data/ext/liquid_c/resource_limits.h +23 -0
  35. data/ext/liquid_c/stringutil.h +44 -0
  36. data/ext/liquid_c/tokenizer.c +149 -35
  37. data/ext/liquid_c/tokenizer.h +20 -9
  38. data/ext/liquid_c/usage.c +18 -0
  39. data/ext/liquid_c/usage.h +9 -0
  40. data/ext/liquid_c/variable.c +196 -20
  41. data/ext/liquid_c/variable.h +18 -1
  42. data/ext/liquid_c/variable_lookup.c +44 -0
  43. data/ext/liquid_c/variable_lookup.h +8 -0
  44. data/ext/liquid_c/vm_assembler.c +491 -0
  45. data/ext/liquid_c/vm_assembler.h +240 -0
  46. data/ext/liquid_c/vm_assembler_pool.c +99 -0
  47. data/ext/liquid_c/vm_assembler_pool.h +26 -0
  48. data/lib/liquid/c/compile_ext.rb +44 -0
  49. data/lib/liquid/c/version.rb +3 -1
  50. data/lib/liquid/c.rb +226 -48
  51. data/liquid-c.gemspec +16 -10
  52. data/performance/c_profile.rb +23 -0
  53. data/performance.rb +6 -4
  54. data/rakelib/compile.rake +15 -0
  55. data/rakelib/integration_test.rake +43 -0
  56. data/rakelib/performance.rake +43 -0
  57. data/rakelib/rubocop.rake +6 -0
  58. data/rakelib/unit_test.rake +14 -0
  59. data/test/integration_test.rb +11 -0
  60. data/test/liquid_test_helper.rb +21 -0
  61. data/test/test_helper.rb +21 -2
  62. data/test/unit/block_test.rb +137 -0
  63. data/test/unit/context_test.rb +85 -0
  64. data/test/unit/expression_test.rb +191 -0
  65. data/test/unit/gc_stress_test.rb +28 -0
  66. data/test/unit/raw_test.rb +93 -0
  67. data/test/unit/resource_limits_test.rb +50 -0
  68. data/test/unit/tokenizer_test.rb +90 -20
  69. data/test/unit/variable_test.rb +279 -60
  70. metadata +60 -11
  71. data/test/liquid_test.rb +0 -11
@@ -0,0 +1,9 @@
1
+ #ifndef LIQUID_USAGE_H
2
+ #define LIQUID_USAGE_H
3
+
4
+ #include "liquid.h"
5
+
6
+ void liquid_define_usage(void);
7
+ void usage_increment(const char *name);
8
+
9
+ #endif
@@ -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 VALUE rb_variable_parse(VALUE self, VALUE markup, VALUE filters)
7
- {
8
- StringValue(markup);
9
- char *start = RSTRING_PTR(markup);
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, start, start + RSTRING_LEN(markup));
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
- VALUE name = parse_expression(&p);
31
+ parse_and_compile_expression(&p, code);
18
32
 
19
33
  while (parser_consume(&p, TOKEN_PIPE).type) {
20
- lexer_token_t filter_name = parser_must_consume(&p, TOKEN_IDENTIFIER);
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
- VALUE filter_args = rb_ary_new(), keyword_args = Qnil, filter;
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
- if (keyword_args == Qnil) keyword_args = rb_hash_new();
31
- rb_hash_aset(keyword_args, key, parse_expression(&p));
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
- rb_ary_push(filter_args, parse_expression(&p));
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 (keyword_args == Qnil) {
39
- filter = rb_ary_new3(2, token_to_rstr(filter_name), filter_args);
40
- } else {
41
- filter = rb_ary_new3(3, token_to_rstr(filter_name), filter_args, keyword_args);
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
- rb_ary_push(filters, filter);
77
+ vm_assembler_add_filter(code, filter_name, arg_count);
44
78
  }
45
79
 
46
80
  parser_must_consume(&p, TOKEN_EOS);
47
- return name;
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 init_liquid_variable(void)
144
+ void internal_variable_compile(variable_parse_args_t *parse_args, unsigned int line_number)
51
145
  {
52
- rb_define_singleton_method(cLiquidVariable, "c_strict_parse", rb_variable_parse, 2);
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
 
@@ -1,7 +1,24 @@
1
1
  #if !defined(LIQUID_VARIABLE_H)
2
2
  #define LIQUID_VARIABLE_H
3
3
 
4
- void init_liquid_variable(void);
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
+ }
@@ -0,0 +1,8 @@
1
+ #if !defined(LIQUID_VARIABLE_LOOKUP_H)
2
+ #define LIQUID_VARIABLE_LOOKUP_H
3
+
4
+ void liquid_define_variable_lookup(void);
5
+ VALUE variable_lookup_key(VALUE context, VALUE object, VALUE key, bool is_command);
6
+
7
+ #endif
8
+