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
@@ -1,5 +1,7 @@
1
+ #include <assert.h>
1
2
  #include "liquid.h"
2
3
  #include "tokenizer.h"
4
+ #include "stringutil.h"
3
5
 
4
6
  VALUE cLiquidTokenizer;
5
7
 
@@ -35,40 +37,89 @@ static VALUE tokenizer_allocate(VALUE klass)
35
37
 
36
38
  obj = TypedData_Make_Struct(klass, tokenizer_t, &tokenizer_data_type, tokenizer);
37
39
  tokenizer->source = Qnil;
40
+ tokenizer->bug_compatible_whitespace_trimming = false;
41
+ tokenizer->raw_tag_body = NULL;
42
+ tokenizer->raw_tag_body_len = 0;
38
43
  return obj;
39
44
  }
40
45
 
41
- static VALUE tokenizer_initialize_method(VALUE self, VALUE source, VALUE line_numbers)
46
+ static VALUE tokenizer_initialize_method(VALUE self, VALUE source, VALUE start_line_number, VALUE for_liquid_tag)
42
47
  {
43
48
  tokenizer_t *tokenizer;
44
49
 
45
50
  Check_Type(source, T_STRING);
51
+ check_utf8_encoding(source, "source");
52
+
53
+ #define MAX_SOURCE_CODE_BYTES ((1 << 24) - 1)
54
+ if (RSTRING_LEN(source) > MAX_SOURCE_CODE_BYTES) {
55
+ rb_enc_raise(utf8_encoding, rb_eArgError, "Source too large, max %d bytes", MAX_SOURCE_CODE_BYTES);
56
+ }
57
+ #undef MAX_SOURCE_CODE_BYTES
58
+
46
59
  Tokenizer_Get_Struct(self, tokenizer);
47
60
  source = rb_str_dup_frozen(source);
48
61
  tokenizer->source = source;
49
62
  tokenizer->cursor = RSTRING_PTR(source);
50
- tokenizer->length = RSTRING_LEN(source);
51
- tokenizer->lstrip_flag = 0;
63
+ tokenizer->cursor_end = tokenizer->cursor + RSTRING_LEN(source);
64
+ tokenizer->lstrip_flag = false;
52
65
  // tokenizer->line_number keeps track of the current line number or it is 0
53
66
  // to indicate that line numbers aren't being calculated
54
- tokenizer->line_number = RTEST(line_numbers) ? 1 : 0;
67
+ tokenizer->line_number = FIX2UINT(start_line_number);
68
+ tokenizer->for_liquid_tag = RTEST(for_liquid_tag);
55
69
  return Qnil;
56
70
  }
57
71
 
58
- void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
72
+ // Internal function to setup an existing tokenizer from C for a liquid tag.
73
+ // This overwrites the passed in tokenizer, so a copy of the struct should
74
+ // be used to reset the tokenizer after parsing the liquid tag.
75
+ void tokenizer_setup_for_liquid_tag(tokenizer_t *tokenizer, const char *cursor, const char *cursor_end, int line_number)
59
76
  {
60
- if (tokenizer->length <= 0) {
61
- memset(token, 0, sizeof(*token));
62
- return;
77
+ tokenizer->cursor = cursor;
78
+ tokenizer->cursor_end = cursor_end;
79
+ tokenizer->lstrip_flag = false;
80
+ tokenizer->line_number = line_number;
81
+ tokenizer->for_liquid_tag = true;
82
+ }
83
+
84
+ // Tokenizes contents of {% liquid ... %}
85
+ static void tokenizer_next_for_liquid_tag(tokenizer_t *tokenizer, token_t *token)
86
+ {
87
+ const char *end = tokenizer->cursor_end;
88
+ const char *start = tokenizer->cursor;
89
+ const char *start_trimmed = read_while(start, end, is_non_newline_space);
90
+
91
+ token->str_full = start;
92
+ token->str_trimmed = start_trimmed;
93
+
94
+ const char *end_full = read_while(start_trimmed, end, not_newline);
95
+ if (end_full < end) {
96
+ tokenizer->cursor = end_full + 1;
97
+ if (tokenizer->line_number)
98
+ tokenizer->line_number++;
99
+ } else {
100
+ tokenizer->cursor = end_full;
63
101
  }
64
102
 
103
+ const char *end_trimmed = read_while_reverse(start_trimmed, end_full, rb_isspace);
104
+
105
+ token->len_trimmed = end_trimmed - start_trimmed;
106
+ token->len_full = end_full - token->str_full;
107
+
108
+ if (token->len_trimmed == 0) {
109
+ token->type = TOKEN_BLANK_LIQUID_TAG_LINE;
110
+ } else {
111
+ token->type = TOKEN_TAG;
112
+ }
113
+ }
114
+
115
+ // Tokenizes contents of a full Liquid template
116
+ static void tokenizer_next_for_template(tokenizer_t *tokenizer, token_t *token)
117
+ {
65
118
  const char *cursor = tokenizer->cursor;
66
- const char *last = cursor + tokenizer->length - 1;
119
+ const char *last = tokenizer->cursor_end - 1;
67
120
 
68
- token->str = cursor;
121
+ token->str_full = cursor;
69
122
  token->type = TOKEN_RAW;
70
- token->lstrip = 0;
71
- token->rstrip = 0;
72
123
 
73
124
  while (cursor < last) {
74
125
  if (*cursor++ != '{')
@@ -78,17 +129,17 @@ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
78
129
  if (c != '%' && c != '{')
79
130
  continue;
80
131
  if (cursor <= last && *cursor == '-') {
81
- cursor++;
82
- token->rstrip = 1;
132
+ cursor++;
133
+ token->rstrip = 1;
83
134
  }
84
135
  if (cursor - tokenizer->cursor > (ptrdiff_t)(2 + token->rstrip)) {
85
136
  token->type = TOKEN_RAW;
86
137
  cursor -= 2 + token->rstrip;
87
138
  token->lstrip = tokenizer->lstrip_flag;
88
- tokenizer->lstrip_flag = 0;
139
+ tokenizer->lstrip_flag = false;
89
140
  goto found;
90
141
  }
91
- tokenizer->lstrip_flag = 0;
142
+ tokenizer->lstrip_flag = false;
92
143
  token->type = TOKEN_INVALID;
93
144
  token->lstrip = token->rstrip;
94
145
  token->rstrip = 0;
@@ -103,12 +154,12 @@ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
103
154
  continue;
104
155
  token->type = TOKEN_TAG;
105
156
  if(cursor[-3] == '-')
106
- token->rstrip = tokenizer->lstrip_flag = 1;
157
+ token->rstrip = tokenizer->lstrip_flag = true;
107
158
  goto found;
108
159
  }
109
160
  // unterminated tag
110
161
  cursor = tokenizer->cursor + 2;
111
- tokenizer->lstrip_flag = 0;
162
+ tokenizer->lstrip_flag = false;
112
163
  goto found;
113
164
  } else {
114
165
  while (cursor < last) {
@@ -121,31 +172,51 @@ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
121
172
  }
122
173
  token->type = TOKEN_VARIABLE;
123
174
  if(cursor[-3] == '-')
124
- token->rstrip = tokenizer->lstrip_flag = 1;
175
+ token->rstrip = tokenizer->lstrip_flag = true;
125
176
  goto found;
126
177
  }
127
178
  // unterminated variable
128
179
  cursor = tokenizer->cursor + 2;
129
- tokenizer->lstrip_flag = 0;
180
+ tokenizer->lstrip_flag = false;
130
181
  goto found;
131
182
  }
132
183
  }
133
184
  cursor = last + 1;
134
185
  token->lstrip = tokenizer->lstrip_flag;
135
- tokenizer->lstrip_flag = 0;
186
+ tokenizer->lstrip_flag = false;
136
187
  found:
137
- token->length = cursor - tokenizer->cursor;
138
- tokenizer->cursor += token->length;
139
- tokenizer->length -= token->length;
188
+ token->len_full = cursor - token->str_full;
189
+
190
+ token->str_trimmed = token->str_full;
191
+ token->len_trimmed = token->len_full;
192
+
193
+ if (token->type == TOKEN_VARIABLE || token->type == TOKEN_TAG) {
194
+ token->str_trimmed += 2 + token->lstrip;
195
+ token->len_trimmed -= 2 + token->lstrip + 2;
196
+ if (token->rstrip && token->len_trimmed)
197
+ token->len_trimmed--;
198
+ }
199
+
200
+ assert(token->len_trimmed >= 0);
201
+
202
+ tokenizer->cursor += token->len_full;
140
203
 
141
204
  if (tokenizer->line_number) {
142
- const char *cursor = token->str;
143
- const char *end = token->str + token->length;
144
- while (cursor < end) {
145
- if (*cursor == '\n')
146
- tokenizer->line_number++;
147
- cursor++;
148
- }
205
+ tokenizer->line_number += count_newlines(token->str_full, token->str_full + token->len_full);
206
+ }
207
+ }
208
+
209
+ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
210
+ {
211
+ memset(token, 0, sizeof(*token));
212
+
213
+ if (tokenizer->cursor >= tokenizer->cursor_end) {
214
+ return;
215
+ }
216
+ if (tokenizer->for_liquid_tag) {
217
+ tokenizer_next_for_liquid_tag(tokenizer, token);
218
+ } else {
219
+ tokenizer_next_for_template(tokenizer, token);
149
220
  }
150
221
  }
151
222
 
@@ -159,7 +230,25 @@ static VALUE tokenizer_shift_method(VALUE self)
159
230
  if (!token.type)
160
231
  return Qnil;
161
232
 
162
- return rb_enc_str_new(token.str, token.length, utf8_encoding);
233
+ // When sent back to Ruby, tokens are the raw string including whitespace
234
+ // and tag delimiters. It should be possible to reconstruct the exact
235
+ // template from the tokens.
236
+ return rb_enc_str_new(token.str_full, token.len_full, utf8_encoding);
237
+ }
238
+
239
+ static VALUE tokenizer_shift_trimmed_method(VALUE self)
240
+ {
241
+ tokenizer_t *tokenizer;
242
+ Tokenizer_Get_Struct(self, tokenizer);
243
+
244
+ token_t token;
245
+ tokenizer_next(tokenizer, &token);
246
+ if (!token.type)
247
+ return Qnil;
248
+
249
+ // This method doesn't include whitespace and tag delimiters. It allows for
250
+ // testing the output of tokenizer_next as used by rb_block_parse.
251
+ return rb_enc_str_new(token.str_trimmed, token.len_trimmed, utf8_encoding);
163
252
  }
164
253
 
165
254
  static VALUE tokenizer_line_number_method(VALUE self)
@@ -173,12 +262,37 @@ static VALUE tokenizer_line_number_method(VALUE self)
173
262
  return UINT2NUM(tokenizer->line_number);
174
263
  }
175
264
 
176
- void init_liquid_tokenizer()
265
+ static VALUE tokenizer_for_liquid_tag_method(VALUE self)
266
+ {
267
+ tokenizer_t *tokenizer;
268
+ Tokenizer_Get_Struct(self, tokenizer);
269
+
270
+ return tokenizer->for_liquid_tag ? Qtrue : Qfalse;
271
+ }
272
+
273
+
274
+ // Temporary to test rollout of the fix for this bug
275
+ static VALUE tokenizer_bug_compatible_whitespace_trimming(VALUE self) {
276
+ tokenizer_t *tokenizer;
277
+ Tokenizer_Get_Struct(self, tokenizer);
278
+
279
+ tokenizer->bug_compatible_whitespace_trimming = true;
280
+ return Qnil;
281
+ }
282
+
283
+ void liquid_define_tokenizer(void)
177
284
  {
178
285
  cLiquidTokenizer = rb_define_class_under(mLiquidC, "Tokenizer", rb_cObject);
286
+ rb_global_variable(&cLiquidTokenizer);
287
+
179
288
  rb_define_alloc_func(cLiquidTokenizer, tokenizer_allocate);
180
- rb_define_method(cLiquidTokenizer, "initialize", tokenizer_initialize_method, 2);
181
- rb_define_method(cLiquidTokenizer, "shift", tokenizer_shift_method, 0);
289
+ rb_define_method(cLiquidTokenizer, "initialize", tokenizer_initialize_method, 3);
182
290
  rb_define_method(cLiquidTokenizer, "line_number", tokenizer_line_number_method, 0);
291
+ rb_define_method(cLiquidTokenizer, "for_liquid_tag", tokenizer_for_liquid_tag_method, 0);
292
+ rb_define_method(cLiquidTokenizer, "bug_compatible_whitespace_trimming!", tokenizer_bug_compatible_whitespace_trimming, 0);
293
+
294
+ // For testing the internal token representation.
295
+ rb_define_private_method(cLiquidTokenizer, "shift", tokenizer_shift_method, 0);
296
+ rb_define_private_method(cLiquidTokenizer, "shift_trimmed", tokenizer_shift_trimmed_method, 0);
183
297
  }
184
298
 
@@ -6,31 +6,42 @@ enum token_type {
6
6
  TOKEN_INVALID,
7
7
  TOKEN_RAW,
8
8
  TOKEN_TAG,
9
- TOKEN_VARIABLE
9
+ TOKEN_VARIABLE,
10
+ TOKEN_BLANK_LIQUID_TAG_LINE
10
11
  };
11
12
 
12
13
  typedef struct token {
13
14
  enum token_type type;
14
- const char *str;
15
- long length;
16
- unsigned int lstrip;
17
- unsigned int rstrip;
15
+
16
+ // str_trimmed contains no tag delimiters
17
+ const char *str_trimmed, *str_full;
18
+ long len_trimmed, len_full;
19
+
20
+ bool lstrip, rstrip;
18
21
  } token_t;
19
22
 
20
23
  typedef struct tokenizer {
21
24
  VALUE source;
22
- const char *cursor;
23
- long length;
25
+ const char *cursor, *cursor_end;
24
26
  unsigned int line_number;
25
- unsigned int lstrip_flag;
27
+ bool lstrip_flag;
28
+ bool for_liquid_tag;
29
+
30
+ // Temporary to test rollout of the fix for this bug
31
+ bool bug_compatible_whitespace_trimming;
32
+
33
+ char *raw_tag_body;
34
+ unsigned int raw_tag_body_len;
26
35
  } tokenizer_t;
27
36
 
28
37
  extern VALUE cLiquidTokenizer;
29
38
  extern const rb_data_type_t tokenizer_data_type;
30
39
  #define Tokenizer_Get_Struct(obj, sval) TypedData_Get_Struct(obj, tokenizer_t, &tokenizer_data_type, sval)
31
40
 
32
- void init_liquid_tokenizer();
41
+ void liquid_define_tokenizer(void);
33
42
  void tokenizer_next(tokenizer_t *tokenizer, token_t *token);
34
43
 
44
+ void tokenizer_setup_for_liquid_tag(tokenizer_t *tokenizer, const char *cursor, const char *cursor_end, int line_number);
45
+
35
46
  #endif
36
47
 
@@ -0,0 +1,18 @@
1
+ #include "usage.h"
2
+
3
+ static VALUE cLiquidUsage;
4
+ static ID id_increment;
5
+
6
+ void usage_increment(const char *name)
7
+ {
8
+ VALUE name_str = rb_str_new_cstr(name);
9
+ rb_funcall(cLiquidUsage, id_increment, 1, name_str);
10
+ }
11
+
12
+ void liquid_define_usage(void)
13
+ {
14
+ cLiquidUsage = rb_const_get(mLiquid, rb_intern("Usage"));
15
+ rb_global_variable(&cLiquidUsage);
16
+
17
+ id_increment = rb_intern("increment");
18
+ }
@@ -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 "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
+ }