liquid-c 4.1.0 → 4.2.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/cla.yml +23 -0
- data/.github/workflows/liquid.yml +21 -18
- data/Gemfile +1 -0
- data/README.md +3 -3
- data/Rakefile +0 -2
- data/ext/liquid_c/block.c +10 -5
- data/ext/liquid_c/context.c +1 -1
- data/ext/liquid_c/context.h +1 -1
- data/ext/liquid_c/document_body.c +8 -0
- data/ext/liquid_c/document_body.h +1 -1
- data/ext/liquid_c/expression.c +1 -1
- data/ext/liquid_c/extconf.rb +5 -3
- data/ext/liquid_c/lexer.c +33 -1
- data/ext/liquid_c/liquid.c +1 -1
- data/ext/liquid_c/{vm.c → liquid_vm.c} +48 -18
- data/ext/liquid_c/parser.c +14 -2
- data/ext/liquid_c/raw.c +36 -10
- data/ext/liquid_c/tokenizer.c +1 -1
- data/ext/liquid_c/variable.c +1 -1
- data/ext/liquid_c/vm_assembler.c +1 -1
- data/ext/liquid_c/vm_assembler_pool.c +4 -2
- data/ext/liquid_c/vm_assembler_pool.h +0 -1
- data/lib/liquid/c/version.rb +1 -1
- data/lib/liquid/c.rb +12 -13
- data/test/test_helper.rb +10 -3
- data/test/unit/block_test.rb +8 -1
- data/test/unit/context_test.rb +2 -0
- data/test/unit/expression_test.rb +7 -2
- data/test/unit/raw_test.rb +76 -2
- data/test/unit/tokenizer_test.rb +4 -4
- data/test/unit/variable_test.rb +67 -0
- metadata +6 -5
- /data/ext/liquid_c/{vm.h → liquid_vm.h} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b5a8a5b1b5f7409b378626e889898eeac6996ef2127894d25d2f342183c633c
|
4
|
+
data.tar.gz: 165117bbbcf583f3c9c0223c3232de5e5c42b67a8ed90b3d268864e4732c685d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a7c7ed2cd71c5470c4b53671611279e71279cba16f0a6fcd5623935b80373ed941f6b3360264399ec7f8ab1efe39728f0bba1a6fecdd3e704675e7464ab660aa
|
7
|
+
data.tar.gz: 006af34fd3a151cae0387a0d70f18e5f64f98dba95f1e2bf6ea36fe239db3153f301405c349ff25a852977ad38b8873a6d8b2554753f29f37665ef20f7a30fda
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# .github/workflows/cla.yml
|
2
|
+
name: Contributor License Agreement (CLA)
|
3
|
+
|
4
|
+
on:
|
5
|
+
pull_request_target:
|
6
|
+
types: [opened, synchronize]
|
7
|
+
issue_comment:
|
8
|
+
types: [created]
|
9
|
+
|
10
|
+
jobs:
|
11
|
+
cla:
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
if: |
|
14
|
+
(github.event.issue.pull_request
|
15
|
+
&& !github.event.issue.pull_request.merged_at
|
16
|
+
&& contains(github.event.comment.body, 'signed')
|
17
|
+
)
|
18
|
+
|| (github.event.pull_request && !github.event.pull_request.merged)
|
19
|
+
steps:
|
20
|
+
- uses: Shopify/shopify-cla-action@v1
|
21
|
+
with:
|
22
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
23
|
+
cla-token: ${{ secrets.CLA_TOKEN }}
|
@@ -5,28 +5,36 @@ jobs:
|
|
5
5
|
runs-on: ubuntu-latest
|
6
6
|
strategy:
|
7
7
|
matrix:
|
8
|
-
|
9
|
-
- { ruby: '2.5', allowed-failure: false }
|
8
|
+
include:
|
10
9
|
- { ruby: '2.7', allowed-failure: false }
|
11
10
|
- { ruby: '3.0', allowed-failure: false }
|
11
|
+
- { ruby: '3.1', allowed-failure: false }
|
12
|
+
- { ruby: '3.2', allowed-failure: false }
|
13
|
+
- { ruby: '3.3', allowed-failure: false }
|
12
14
|
- { ruby: ruby-head, allowed-failure: true }
|
13
|
-
|
15
|
+
- { ruby: truffleruby-head, allowed-failure: true }
|
16
|
+
name: test (${{ matrix.ruby }})
|
14
17
|
steps:
|
15
18
|
- uses: actions/checkout@v2
|
16
19
|
- uses: ruby/setup-ruby@v1
|
17
20
|
with:
|
18
|
-
ruby-version: ${{ matrix.
|
19
|
-
|
20
|
-
|
21
|
-
path: vendor/bundle
|
22
|
-
key: ${{ runner.os }}-gems-${{ hashFiles('Gemfile') }}
|
23
|
-
restore-keys: ${{ runner.os }}-gems-
|
24
|
-
- run: bundle install --jobs=3 --retry=3 --path=vendor/bundle
|
21
|
+
ruby-version: ${{ matrix.ruby }}
|
22
|
+
bundler-cache: true
|
23
|
+
|
25
24
|
- run: bundle exec rake
|
26
|
-
continue-on-error: ${{ matrix.
|
25
|
+
continue-on-error: ${{ matrix.allowed-failure }}
|
27
26
|
env:
|
28
27
|
LIQUID_C_PEDANTIC: 'true'
|
28
|
+
if: matrix.ruby != 'truffleruby-head'
|
29
|
+
|
30
|
+
- run: bundle exec rake test:unit
|
31
|
+
continue-on-error: ${{ matrix.allowed-failure }}
|
32
|
+
env:
|
33
|
+
LIQUID_C_PEDANTIC: 'true'
|
34
|
+
if: matrix.ruby == 'truffleruby-head'
|
35
|
+
|
29
36
|
- run: bundle exec rubocop
|
37
|
+
if: matrix.ruby != 'truffleruby-head'
|
30
38
|
|
31
39
|
valgrind:
|
32
40
|
runs-on: ubuntu-latest
|
@@ -34,12 +42,7 @@ jobs:
|
|
34
42
|
- uses: actions/checkout@v2
|
35
43
|
- uses: ruby/setup-ruby@v1
|
36
44
|
with:
|
37
|
-
ruby-version:
|
45
|
+
ruby-version: 3.3
|
46
|
+
bundler-cache: true
|
38
47
|
- run: sudo apt-get install -y valgrind
|
39
|
-
- uses: actions/cache@v1
|
40
|
-
with:
|
41
|
-
path: vendor/bundle
|
42
|
-
key: ${{ runner.os }}-gems-${{ hashFiles('Gemfile') }}
|
43
|
-
restore-keys: ${{ runner.os }}-gems-
|
44
|
-
- run: bundle install --jobs=3 --retry=3 --path=vendor/bundle
|
45
48
|
- run: bundle exec rake test:valgrind
|
data/Gemfile
CHANGED
@@ -10,6 +10,7 @@ gemspec
|
|
10
10
|
gem "liquid", github: "Shopify/liquid", ref: "master"
|
11
11
|
|
12
12
|
group :test do
|
13
|
+
gem "base64", require: false # for older rubocop on Ruby 3.4
|
13
14
|
gem "rubocop", "~> 1.24.1", require: false
|
14
15
|
gem "rubocop-performance", "~> 1.13.2", require: false
|
15
16
|
gem "rubocop-shopify", "~> 2.4.0", require: false
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Liquid::C
|
2
|
-
[](https://travis-ci.org/Shopify/liquid-c)
|
3
3
|
|
4
4
|
Partial native implementation of the liquid ruby gem in C.
|
5
5
|
|
@@ -7,8 +7,8 @@ Partial native implementation of the liquid ruby gem in C.
|
|
7
7
|
|
8
8
|
Add these lines to your application's Gemfile:
|
9
9
|
|
10
|
-
gem 'liquid', github: 'Shopify/liquid', branch: '
|
11
|
-
gem 'liquid-c', github: 'Shopify/liquid-c', branch: '
|
10
|
+
gem 'liquid', github: 'Shopify/liquid', branch: 'main'
|
11
|
+
gem 'liquid-c', github: 'Shopify/liquid-c', branch: 'main'
|
12
12
|
|
13
13
|
And then execute:
|
14
14
|
|
data/Rakefile
CHANGED
data/ext/liquid_c/block.c
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
#include "intutil.h"
|
4
4
|
#include "tokenizer.h"
|
5
5
|
#include "stringutil.h"
|
6
|
-
#include "
|
6
|
+
#include "liquid_vm.h"
|
7
7
|
#include "variable.h"
|
8
8
|
#include "context.h"
|
9
9
|
#include "parse_context.h"
|
@@ -49,7 +49,7 @@ static void block_body_mark(void *ptr)
|
|
49
49
|
} else {
|
50
50
|
rb_gc_mark(body->as.intermediate.parse_context);
|
51
51
|
if (body->as.intermediate.vm_assembler_pool)
|
52
|
-
|
52
|
+
rb_gc_mark(body->as.intermediate.vm_assembler_pool->self);
|
53
53
|
if (body->as.intermediate.code)
|
54
54
|
vm_assembler_gc_mark(body->as.intermediate.code);
|
55
55
|
}
|
@@ -199,9 +199,14 @@ static tag_markup_t internal_block_body_parse(block_body_t *body, parse_context_
|
|
199
199
|
long name_len = name_end - name_start;
|
200
200
|
|
201
201
|
if (name_len == 0) {
|
202
|
-
|
203
|
-
|
204
|
-
|
202
|
+
if (name_start < end && *name_start == '#') { // inline comment
|
203
|
+
name_end++;
|
204
|
+
name_len++;
|
205
|
+
} else {
|
206
|
+
VALUE str = rb_enc_str_new(token.str_trimmed, token.len_trimmed, utf8_encoding);
|
207
|
+
unknown_tag = (tag_markup_t) { str, str };
|
208
|
+
goto loop_break;
|
209
|
+
}
|
205
210
|
}
|
206
211
|
|
207
212
|
if (name_len == 6 && strncmp(name_start, "liquid", 6) == 0) {
|
data/ext/liquid_c/context.c
CHANGED
data/ext/liquid_c/context.h
CHANGED
@@ -38,7 +38,7 @@ inline static VALUE value_to_liquid_and_set_context(VALUE value, VALUE context_t
|
|
38
38
|
if (RB_SPECIAL_CONST_P(value))
|
39
39
|
return value;
|
40
40
|
|
41
|
-
VALUE klass =
|
41
|
+
VALUE klass = RBASIC_CLASS(value);
|
42
42
|
|
43
43
|
// More basic types having #to_liquid of self and no #context=
|
44
44
|
if (klass == rb_cString || klass == rb_cArray || klass == rb_cHash)
|
@@ -9,6 +9,14 @@ static VALUE cLiquidCDocumentBody;
|
|
9
9
|
static void document_body_mark(void *ptr)
|
10
10
|
{
|
11
11
|
document_body_t *body = ptr;
|
12
|
+
/* When Liquid::C::BlockBody#freeze is called, it calls
|
13
|
+
* document_body_write_block_body which sets the document_body_entry but
|
14
|
+
* does not yet set the compiled flag to true. During this time, the only
|
15
|
+
* reference to this Liquid::C::DocumentBody object is in the instance
|
16
|
+
* variables of the parse_context which is marked movable by Ruby. This
|
17
|
+
* causes the self reference here to be moved by compaction causing it to
|
18
|
+
* point to an incorrect object. */
|
19
|
+
rb_gc_mark(body->self);
|
12
20
|
rb_gc_mark(body->constants);
|
13
21
|
}
|
14
22
|
|
@@ -46,7 +46,7 @@ static inline block_body_header_t *document_body_get_block_body_header_ptr(const
|
|
46
46
|
static inline const VALUE *document_body_get_constants_ptr(const document_body_entry_t *entry)
|
47
47
|
{
|
48
48
|
block_body_header_t *header = document_body_get_block_body_header_ptr(entry);
|
49
|
-
return
|
49
|
+
return RARRAY_CONST_PTR(entry->body->constants) + header->constants_offset;
|
50
50
|
}
|
51
51
|
|
52
52
|
static inline void document_body_ensure_compile_finished(document_body_t *body)
|
data/ext/liquid_c/expression.c
CHANGED
data/ext/liquid_c/extconf.rb
CHANGED
@@ -12,13 +12,15 @@ end
|
|
12
12
|
if ENV["DEBUG"] == "true"
|
13
13
|
append_cflags("-fbounds-check")
|
14
14
|
CONFIG["optflags"] = " -O0"
|
15
|
+
# Hack to enable assertions since ruby/assert.h disables assertions unless
|
16
|
+
# Ruby was compiled with -DRUBY_DEBUG.
|
17
|
+
# https://github.com/ruby/ruby/blob/9e678cdbd054f78576a8f21b3f97cccc395ade22/include/ruby/assert.h#L36-L41
|
18
|
+
$CFLAGS << " -DRUBY_DEBUG"
|
15
19
|
else
|
16
20
|
$CFLAGS << " -DNDEBUG"
|
17
21
|
end
|
18
22
|
|
19
|
-
|
20
|
-
$CFLAGS << " -DHAVE_RB_HASH_BULK_INSERT"
|
21
|
-
end
|
23
|
+
have_func "rb_hash_bulk_insert"
|
22
24
|
|
23
25
|
$warnflags&.gsub!(/-Wdeclaration-after-statement/, "")
|
24
26
|
create_makefile("liquid_c")
|
data/ext/liquid_c/lexer.c
CHANGED
@@ -144,7 +144,39 @@ const char *lex_one(const char *start, const char *end, lexer_token_t *token)
|
|
144
144
|
|
145
145
|
if (is_special(c)) RETURN_TOKEN(c, 1);
|
146
146
|
|
147
|
-
|
147
|
+
long remaining_str_len = end - str;
|
148
|
+
int char_len = 0;
|
149
|
+
|
150
|
+
// read multibyte UTF-8 character
|
151
|
+
if ((c & 0x80) == 0) {
|
152
|
+
// 1-byte character
|
153
|
+
char_len = 1;
|
154
|
+
} else if ((c & 0xE0) == 0xC0) {
|
155
|
+
// 2-byte character
|
156
|
+
if (remaining_str_len >= 2) {
|
157
|
+
char_len = 2;
|
158
|
+
}
|
159
|
+
} else if ((c & 0xF0) == 0xE0) {
|
160
|
+
// 3-byte character
|
161
|
+
if (remaining_str_len >= 3) {
|
162
|
+
char_len = 3;
|
163
|
+
}
|
164
|
+
} else if ((c & 0xF8) == 0xF0) {
|
165
|
+
// 4-byte character
|
166
|
+
if (remaining_str_len >= 4) {
|
167
|
+
char_len = 4;
|
168
|
+
}
|
169
|
+
} else {
|
170
|
+
// this should never happen
|
171
|
+
rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "Unexpected character %c", c);
|
172
|
+
}
|
173
|
+
|
174
|
+
if (char_len > 0) {
|
175
|
+
rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "Unexpected character %.*s", char_len, str);
|
176
|
+
} else {
|
177
|
+
rb_raise(rb_eArgError, "invalid byte sequence in UTF-8");
|
178
|
+
}
|
179
|
+
|
148
180
|
return NULL;
|
149
181
|
}
|
150
182
|
|
data/ext/liquid_c/liquid.c
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
#include <assert.h>
|
3
3
|
|
4
4
|
#include "liquid.h"
|
5
|
-
#include "
|
5
|
+
#include "liquid_vm.h"
|
6
6
|
#include "variable_lookup.h"
|
7
7
|
#include "intutil.h"
|
8
8
|
#include "document_body.h"
|
@@ -141,31 +141,40 @@ static inline void vm_stack_push(vm_t *vm, VALUE value)
|
|
141
141
|
vm->stack.data_end = (uint8_t *)stack_ptr;
|
142
142
|
}
|
143
143
|
|
144
|
-
static inline VALUE
|
144
|
+
static inline VALUE *vm_stack_peek_n(vm_t *vm, size_t n)
|
145
145
|
{
|
146
146
|
VALUE *stack_ptr = (VALUE *)vm->stack.data_end;
|
147
|
-
stack_ptr
|
147
|
+
stack_ptr -= n;
|
148
148
|
assert((VALUE *)vm->stack.data <= stack_ptr);
|
149
|
-
|
150
|
-
return *stack_ptr;
|
149
|
+
return stack_ptr;
|
151
150
|
}
|
152
151
|
|
153
|
-
static inline VALUE *
|
152
|
+
static inline VALUE *vm_stack_pop_n(vm_t *vm, size_t n)
|
154
153
|
{
|
155
|
-
VALUE *stack_ptr = (
|
156
|
-
stack_ptr -= n;
|
157
|
-
assert((VALUE *)vm->stack.data <= stack_ptr);
|
154
|
+
VALUE *stack_ptr = vm_stack_peek_n(vm, n);
|
158
155
|
vm->stack.data_end = (uint8_t *)stack_ptr;
|
159
156
|
return stack_ptr;
|
160
157
|
}
|
161
158
|
|
159
|
+
static inline VALUE vm_stack_pop(vm_t *vm)
|
160
|
+
{
|
161
|
+
return *vm_stack_pop_n(vm, 1);
|
162
|
+
}
|
163
|
+
|
162
164
|
static inline void vm_stack_reserve_for_write(vm_t *vm, size_t num_values)
|
163
165
|
{
|
164
166
|
c_buffer_reserve_for_write(&vm->stack, num_values * sizeof(VALUE));
|
165
167
|
}
|
166
168
|
|
167
|
-
static VALUE vm_invoke_filter(vm_t *vm, VALUE filter_name, int num_args
|
169
|
+
static VALUE vm_invoke_filter(vm_t *vm, VALUE filter_name, int num_args)
|
168
170
|
{
|
171
|
+
VALUE *popped_args = vm_stack_pop_n(vm, num_args);
|
172
|
+
/* We have to copy popped_args_ptr to the stack because the VM
|
173
|
+
* no longer holds onto these objects, so they have to exist on
|
174
|
+
* the stack to ensure they don't get garbage collected. */
|
175
|
+
VALUE *args = alloca(sizeof(VALUE *) * num_args);
|
176
|
+
memcpy(args, popped_args, sizeof(VALUE *) * num_args);
|
177
|
+
|
169
178
|
bool not_invokable = rb_hash_lookup(vm->context.filter_methods, filter_name) != Qtrue;
|
170
179
|
if (RB_UNLIKELY(not_invokable)) {
|
171
180
|
if (vm->context.strict_filters) {
|
@@ -313,8 +322,11 @@ static VALUE vm_render_until_error(VALUE uncast_args)
|
|
313
322
|
size_t hash_size = *ip++;
|
314
323
|
size_t num_keys_and_values = hash_size * 2;
|
315
324
|
VALUE hash = rb_hash_new();
|
316
|
-
|
325
|
+
|
326
|
+
VALUE *args_ptr = vm_stack_peek_n(vm, num_keys_and_values);
|
317
327
|
hash_bulk_insert(num_keys_and_values, args_ptr, hash);
|
328
|
+
vm_stack_pop_n(vm, num_keys_and_values);
|
329
|
+
|
318
330
|
vm_stack_push(vm, hash);
|
319
331
|
break;
|
320
332
|
}
|
@@ -336,8 +348,7 @@ static VALUE vm_render_until_error(VALUE uncast_args)
|
|
336
348
|
num_args = *ip++; // includes input argument
|
337
349
|
}
|
338
350
|
|
339
|
-
VALUE
|
340
|
-
VALUE result = vm_invoke_filter(vm, filter_name, num_args, args_ptr);
|
351
|
+
VALUE result = vm_invoke_filter(vm, filter_name, num_args);
|
341
352
|
vm_stack_push(vm, result);
|
342
353
|
break;
|
343
354
|
}
|
@@ -424,24 +435,43 @@ static VALUE vm_render_until_error(VALUE uncast_args)
|
|
424
435
|
}
|
425
436
|
}
|
426
437
|
|
438
|
+
typedef struct vm_evaluate_rescue_args {
|
439
|
+
vm_render_until_error_args_t *render_args;
|
440
|
+
size_t old_stack_byte_size;
|
441
|
+
} vm_evaluate_rescue_args_t;
|
442
|
+
|
443
|
+
static VALUE vm_evaluate_rescue(VALUE uncast_args, VALUE exception)
|
444
|
+
{
|
445
|
+
vm_evaluate_rescue_args_t *args = (void *)uncast_args;
|
446
|
+
vm_render_until_error_args_t *render_args = args->render_args;
|
447
|
+
vm_t *vm = render_args->vm;
|
448
|
+
|
449
|
+
vm->stack.data_end = vm->stack.data + args->old_stack_byte_size;
|
450
|
+
|
451
|
+
rb_exc_raise(exception);
|
452
|
+
return Qnil;
|
453
|
+
}
|
454
|
+
|
427
455
|
// Evaluate instructions that avoid using rendering instructions and leave with the result on
|
428
456
|
// the top of the stack
|
429
457
|
VALUE liquid_vm_evaluate(VALUE context, vm_assembler_t *code)
|
430
458
|
{
|
431
459
|
vm_t *vm = vm_from_context(context);
|
432
460
|
vm_stack_reserve_for_write(vm, code->max_stack_size);
|
433
|
-
#ifndef NDEBUG
|
434
|
-
size_t old_stack_byte_size = c_buffer_size(&vm->stack);
|
435
|
-
#endif
|
436
461
|
|
437
462
|
vm_render_until_error_args_t args = {
|
438
463
|
.vm = vm,
|
439
464
|
.const_ptr = (const size_t *)code->constants.data,
|
440
465
|
.ip = code->instructions.data
|
441
466
|
};
|
442
|
-
|
467
|
+
vm_evaluate_rescue_args_t rescue_args = {
|
468
|
+
.render_args = &args,
|
469
|
+
.old_stack_byte_size = c_buffer_size(&vm->stack),
|
470
|
+
};
|
471
|
+
rb_rescue(vm_render_until_error, (VALUE)&args, vm_evaluate_rescue, (VALUE)&rescue_args);
|
472
|
+
|
443
473
|
VALUE ret = vm_stack_pop(vm);
|
444
|
-
assert(old_stack_byte_size == c_buffer_size(&vm->stack));
|
474
|
+
assert(rescue_args.old_stack_byte_size == c_buffer_size(&vm->stack));
|
445
475
|
return ret;
|
446
476
|
}
|
447
477
|
|
data/ext/liquid_c/parser.c
CHANGED
@@ -67,28 +67,40 @@ static VALUE parse_number(parser_t *p)
|
|
67
67
|
return out;
|
68
68
|
}
|
69
69
|
|
70
|
+
__attribute__((noreturn)) static void raise_invalid_expression_type(const char *expr, int expr_len)
|
71
|
+
{
|
72
|
+
rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "Invalid expression type '%.*s' in range expression", expr_len, expr);
|
73
|
+
}
|
74
|
+
|
70
75
|
static VALUE try_parse_constant_range(parser_t *p)
|
71
76
|
{
|
72
77
|
parser_t saved_state = *p;
|
73
78
|
|
74
79
|
parser_must_consume(p, TOKEN_OPEN_ROUND);
|
75
80
|
|
81
|
+
const char *begin_str = p->cur.val;
|
76
82
|
VALUE begin = try_parse_constant_expression(p);
|
83
|
+
const char *begin_str_end = p->cur.val;
|
77
84
|
if (begin == Qundef) {
|
78
85
|
*p = saved_state;
|
79
86
|
return Qundef;
|
80
87
|
}
|
81
88
|
parser_must_consume(p, TOKEN_DOTDOT);
|
82
89
|
|
90
|
+
const char *end_str = p->cur.val;
|
83
91
|
VALUE end = try_parse_constant_expression(p);
|
92
|
+
const char *end_str_end = p->cur.val;
|
84
93
|
if (end == Qundef) {
|
85
94
|
*p = saved_state;
|
86
95
|
return Qundef;
|
87
96
|
}
|
88
97
|
parser_must_consume(p, TOKEN_CLOSE_ROUND);
|
89
98
|
|
90
|
-
begin =
|
91
|
-
|
99
|
+
begin = rb_check_funcall(begin, id_to_i, 0, NULL);
|
100
|
+
if (begin == Qundef) raise_invalid_expression_type(begin_str, (int)(begin_str_end - begin_str));
|
101
|
+
|
102
|
+
end = rb_check_funcall(end, id_to_i, 0, NULL);
|
103
|
+
if (end == Qundef) raise_invalid_expression_type(end_str, (int)(end_str_end - end_str));
|
92
104
|
|
93
105
|
bool exclude_end = false;
|
94
106
|
return rb_range_new(begin, end, exclude_end);
|
data/ext/liquid_c/raw.c
CHANGED
@@ -27,25 +27,51 @@ static bool match_full_token_possibly_invalid(token_t *token, struct full_token_
|
|
27
27
|
const char *curr_delimiter_start;
|
28
28
|
long curr_delimiter_len = 0;
|
29
29
|
|
30
|
-
|
30
|
+
bool is_last_char_whitespace = true;
|
31
|
+
|
32
|
+
// Search from the end of the string.
|
33
|
+
// The token could have a part of the body like this:
|
34
|
+
// {% endraw {% endraw %}
|
35
|
+
// In this case, we need to return body_len to 10 to preserve the body content.
|
36
|
+
for (long i = len - 3; i > 1; i--) {
|
31
37
|
char c = str[i];
|
32
38
|
|
39
|
+
// match \s
|
40
|
+
bool is_whitespace = rb_isspace(c);
|
41
|
+
|
33
42
|
if (is_word_char(c)) {
|
34
43
|
curr_delimiter_start = str + i;
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
44
|
+
|
45
|
+
if (is_last_char_whitespace) {
|
46
|
+
// start a new delimiter match
|
47
|
+
curr_delimiter_len = 1;
|
48
|
+
} else {
|
49
|
+
curr_delimiter_len++;
|
40
50
|
}
|
51
|
+
} else if (!is_word_char(c) && !is_whitespace) {
|
41
52
|
curr_delimiter_start = NULL;
|
42
53
|
curr_delimiter_len = 0;
|
43
54
|
}
|
44
55
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
56
|
+
is_last_char_whitespace = is_whitespace;
|
57
|
+
|
58
|
+
if (curr_delimiter_len > 0) {
|
59
|
+
// match start of a tag which is {% or {%-
|
60
|
+
if (
|
61
|
+
(str[i - 1] == '%' && str[i - 2] == '{') ||
|
62
|
+
(i - 3 >= 0 && str[i - 1] == '-' && str[i - 2] == '%' && str[i - 3] == '{')
|
63
|
+
) {
|
64
|
+
match->delimiter_start = curr_delimiter_start;
|
65
|
+
match->delimiter_len = curr_delimiter_len;
|
66
|
+
|
67
|
+
if (str[i - 1] == '-') {
|
68
|
+
match->body_len = i - 3;
|
69
|
+
} else {
|
70
|
+
match->body_len = i - 2;
|
71
|
+
}
|
72
|
+
|
73
|
+
return true;
|
74
|
+
}
|
49
75
|
}
|
50
76
|
}
|
51
77
|
|
data/ext/liquid_c/tokenizer.c
CHANGED
@@ -290,9 +290,9 @@ void liquid_define_tokenizer(void)
|
|
290
290
|
rb_define_method(cLiquidTokenizer, "line_number", tokenizer_line_number_method, 0);
|
291
291
|
rb_define_method(cLiquidTokenizer, "for_liquid_tag", tokenizer_for_liquid_tag_method, 0);
|
292
292
|
rb_define_method(cLiquidTokenizer, "bug_compatible_whitespace_trimming!", tokenizer_bug_compatible_whitespace_trimming, 0);
|
293
|
+
rb_define_method(cLiquidTokenizer, "shift", tokenizer_shift_method, 0);
|
293
294
|
|
294
295
|
// For testing the internal token representation.
|
295
|
-
rb_define_private_method(cLiquidTokenizer, "shift", tokenizer_shift_method, 0);
|
296
296
|
rb_define_private_method(cLiquidTokenizer, "shift_trimmed", tokenizer_shift_trimmed_method, 0);
|
297
297
|
}
|
298
298
|
|
data/ext/liquid_c/variable.c
CHANGED
data/ext/liquid_c/vm_assembler.c
CHANGED
@@ -3,8 +3,10 @@
|
|
3
3
|
|
4
4
|
static VALUE cLiquidCVMAssemblerPool;
|
5
5
|
|
6
|
-
void
|
6
|
+
static void vm_assembler_pool_mark(void *ptr)
|
7
7
|
{
|
8
|
+
vm_assembler_pool_t *pool = ptr;
|
9
|
+
|
8
10
|
rb_gc_mark(pool->self);
|
9
11
|
}
|
10
12
|
|
@@ -39,7 +41,7 @@ static size_t vm_assembler_pool_memsize(const void *ptr)
|
|
39
41
|
|
40
42
|
const rb_data_type_t vm_assembler_pool_data_type = {
|
41
43
|
"liquid_vm_assembler_pool",
|
42
|
-
{
|
44
|
+
{ vm_assembler_pool_mark, vm_assembler_pool_free, vm_assembler_pool_memsize, },
|
43
45
|
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
|
44
46
|
};
|
45
47
|
|
@@ -18,7 +18,6 @@ extern const rb_data_type_t vm_assembler_pool_data_type;
|
|
18
18
|
#define VMAssemblerPool_Get_Struct(obj, sval) TypedData_Get_Struct(obj, vm_assembler_pool_t, &vm_assembler_pool_data_type, sval)
|
19
19
|
|
20
20
|
void liquid_define_vm_assembler_pool(void);
|
21
|
-
void vm_assembler_pool_gc_mark(vm_assembler_pool_t *pool);
|
22
21
|
VALUE vm_assembler_pool_new(void);
|
23
22
|
vm_assembler_t *vm_assembler_pool_alloc_assembler(vm_assembler_pool_t *pool);
|
24
23
|
void vm_assembler_pool_free_assembler(vm_assembler_t *assembler);
|
data/lib/liquid/c/version.rb
CHANGED
data/lib/liquid/c.rb
CHANGED
@@ -86,7 +86,7 @@ Liquid::ParseContext.class_eval do
|
|
86
86
|
|
87
87
|
def parse_expression(markup)
|
88
88
|
if liquid_c_nodes_disabled?
|
89
|
-
Liquid::Expression.
|
89
|
+
Liquid::Expression.parse(markup)
|
90
90
|
else
|
91
91
|
Liquid::C::Expression.lax_parse(markup)
|
92
92
|
end
|
@@ -207,22 +207,17 @@ Liquid::C::Expression.class_eval do
|
|
207
207
|
def lax_parse(markup)
|
208
208
|
strict_parse(markup)
|
209
209
|
rescue Liquid::SyntaxError
|
210
|
-
Liquid::Expression.
|
210
|
+
Liquid::Expression.parse(markup)
|
211
211
|
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
Liquid::Expression.class_eval do
|
216
|
-
class << self
|
217
|
-
alias_method :ruby_parse, :parse
|
218
212
|
|
219
|
-
|
220
|
-
|
221
|
-
|
213
|
+
# Default to strict parsing, since Liquid::C::Expression.parse should only really
|
214
|
+
# be used with constant expressions. Otherwise, prefer parse_context.parse_expression.
|
215
|
+
alias_method :parse, :strict_parse
|
222
216
|
end
|
223
217
|
end
|
224
218
|
|
225
219
|
Liquid::Context.class_eval do
|
220
|
+
alias_method :ruby_parse_evaluate, :[]
|
226
221
|
alias_method :ruby_evaluate, :evaluate
|
227
222
|
alias_method :ruby_find_variable, :find_variable
|
228
223
|
alias_method :ruby_strict_variables=, :strict_variables=
|
@@ -232,6 +227,10 @@ Liquid::Context.class_eval do
|
|
232
227
|
def c_find_variable_kwarg(key, raise_on_not_found: true)
|
233
228
|
c_find_variable(key, raise_on_not_found)
|
234
229
|
end
|
230
|
+
|
231
|
+
def c_parse_evaluate(expression)
|
232
|
+
c_evaluate(Liquid::C::Expression.lax_parse(expression))
|
233
|
+
end
|
235
234
|
end
|
236
235
|
|
237
236
|
Liquid::ResourceLimits.class_eval do
|
@@ -256,15 +255,15 @@ module Liquid
|
|
256
255
|
def enabled=(value)
|
257
256
|
@enabled = value
|
258
257
|
if value
|
258
|
+
Liquid::Context.send(:alias_method, :[], :c_parse_evaluate)
|
259
259
|
Liquid::Context.send(:alias_method, :evaluate, :c_evaluate)
|
260
260
|
Liquid::Context.send(:alias_method, :find_variable, :c_find_variable_kwarg)
|
261
261
|
Liquid::Context.send(:alias_method, :strict_variables=, :c_strict_variables=)
|
262
|
-
Liquid::Expression.singleton_class.send(:alias_method, :parse, :c_parse)
|
263
262
|
else
|
263
|
+
Liquid::Context.send(:alias_method, :[], :ruby_parse_evaluate)
|
264
264
|
Liquid::Context.send(:alias_method, :evaluate, :ruby_evaluate)
|
265
265
|
Liquid::Context.send(:alias_method, :find_variable, :ruby_find_variable)
|
266
266
|
Liquid::Context.send(:alias_method, :strict_variables=, :ruby_strict_variables=)
|
267
|
-
Liquid::Expression.singleton_class.send(:alias_method, :parse, :ruby_parse)
|
268
267
|
end
|
269
268
|
end
|
270
269
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,14 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
at_exit { GC.start }
|
4
|
-
|
5
3
|
require "minitest/autorun"
|
6
4
|
require "liquid/c"
|
7
5
|
|
8
6
|
if GC.respond_to?(:verify_compaction_references)
|
9
7
|
# This method was added in Ruby 3.0.0. Calling it this way asks the GC to
|
10
8
|
# move objects around, helping to find object movement bugs.
|
11
|
-
|
9
|
+
begin
|
10
|
+
GC.verify_compaction_references(double_heap: true, toward: :empty)
|
11
|
+
rescue NotImplementedError
|
12
|
+
puts "W: GC compaction not suppported by platform"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Enable auto-compaction in the GC if supported.
|
17
|
+
if GC.respond_to?(:auto_compact=)
|
18
|
+
GC.auto_compact = true
|
12
19
|
end
|
13
20
|
|
14
21
|
GC.stress = true if ENV["GC_STRESS"]
|
data/test/unit/block_test.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "test_helper"
|
4
4
|
|
5
|
-
class BlockTest <
|
5
|
+
class BlockTest < Minitest::Test
|
6
6
|
def test_no_allocation_of_trimmed_strings
|
7
7
|
template = Liquid::Template.parse("{{ a -}} {{- b }}")
|
8
8
|
assert_equal(2, template.root.nodelist.size)
|
@@ -127,4 +127,11 @@ class BlockTest < MiniTest::Test
|
|
127
127
|
Liquid::Template.file_system = old_file_system
|
128
128
|
end
|
129
129
|
end
|
130
|
+
|
131
|
+
def test_assign_filter_argument_exception
|
132
|
+
source = "{% assign v = 'IN' | truncate: 123, liquid_error %}{{ v | default: 'err swallowed' }}"
|
133
|
+
template = Liquid::Template.parse(source)
|
134
|
+
output = template.render({ "liquid_error" => -> { raise Liquid::Error, "var lookup error" } })
|
135
|
+
assert_equal("err swallowed", output)
|
136
|
+
end
|
130
137
|
end
|
data/test/unit/context_test.rb
CHANGED
@@ -29,6 +29,8 @@ class ContextTest < Minitest::Test
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def test_evaluating_a_variable_entirely_within_c
|
32
|
+
skip("TracePoint :call not yet supported") if RUBY_ENGINE == "truffleruby"
|
33
|
+
|
32
34
|
context = Liquid::Context.new({ "var" => 42 })
|
33
35
|
lookup = Liquid::C::Expression.strict_parse("var")
|
34
36
|
context.evaluate(lookup) # memoize vm_internal_new calls
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "test_helper"
|
4
4
|
|
5
|
-
class ExpressionTest <
|
5
|
+
class ExpressionTest < Minitest::Test
|
6
6
|
def test_constant_literals
|
7
7
|
assert_equal(true, Liquid::C::Expression.strict_parse("true"))
|
8
8
|
assert_equal(false, Liquid::C::Expression.strict_parse("false"))
|
@@ -159,7 +159,7 @@ class ExpressionTest < MiniTest::Test
|
|
159
159
|
end
|
160
160
|
|
161
161
|
def test_disable_c_nodes
|
162
|
-
context = Liquid::Context.new({ "x" => 123 })
|
162
|
+
context = Liquid::Context.new({ "x" => 123, "y" => { 123 => 42 } })
|
163
163
|
|
164
164
|
expr = Liquid::ParseContext.new.parse_expression("x")
|
165
165
|
assert_instance_of(Liquid::C::Expression, expr)
|
@@ -168,6 +168,11 @@ class ExpressionTest < MiniTest::Test
|
|
168
168
|
expr = Liquid::ParseContext.new(disable_liquid_c_nodes: true).parse_expression("x")
|
169
169
|
assert_instance_of(Liquid::VariableLookup, expr)
|
170
170
|
assert_equal(123, context.evaluate(expr))
|
171
|
+
|
172
|
+
expr = Liquid::ParseContext.new(disable_liquid_c_nodes: true).parse_expression("y[x]")
|
173
|
+
assert_instance_of(Liquid::VariableLookup, expr)
|
174
|
+
assert_instance_of(Liquid::VariableLookup, expr.lookups.first)
|
175
|
+
assert_equal(42, context.evaluate(expr))
|
171
176
|
end
|
172
177
|
|
173
178
|
private
|
data/test/unit/raw_test.rb
CHANGED
@@ -13,7 +13,81 @@ class RawTest < Minitest::Test
|
|
13
13
|
Liquid::Template.register_tag("raw_wrapper", RawWrapper)
|
14
14
|
|
15
15
|
def test_derived_class
|
16
|
-
|
17
|
-
|
16
|
+
[
|
17
|
+
"{% raw_wrapper %}body{% endraw_wrapper %}",
|
18
|
+
"{% raw_wrapper %}body{%endraw_wrapper%}",
|
19
|
+
"{% raw_wrapper %}body{%- endraw_wrapper -%}",
|
20
|
+
"{% raw_wrapper %}body{%- endraw_wrapper %}",
|
21
|
+
"{% raw_wrapper %}body{% endraw_wrapper -%}",
|
22
|
+
].each do |template|
|
23
|
+
output = Liquid::Template.parse(template).render!
|
24
|
+
|
25
|
+
assert_equal(
|
26
|
+
"<body>",
|
27
|
+
output,
|
28
|
+
"Template: #{template}"
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_allows_extra_string_after_tag_delimiter
|
34
|
+
output = Liquid::Template.parse("{% raw %}message{% endraw this_is_allowed %}").render
|
35
|
+
assert_equal("message", output)
|
36
|
+
|
37
|
+
output = Liquid::Template.parse("{% raw %}message{% endraw r%}").render
|
38
|
+
assert_equal("message", output)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_ignores_incomplete_tag_delimter
|
42
|
+
output = Liquid::Template.parse("{% raw %}{% endraw {% endraw %}").render
|
43
|
+
assert_equal("{% endraw ", output)
|
44
|
+
|
45
|
+
output = Liquid::Template.parse("{% raw %}{%endraw{% endraw %}").render
|
46
|
+
assert_equal("{%endraw", output)
|
47
|
+
|
48
|
+
output = Liquid::Template.parse("{% raw %}{%- endraw {% endraw %}").render
|
49
|
+
assert_equal("{%- endraw ", output)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_does_not_allow_nbsp_in_tag_delimiter
|
53
|
+
# these are valid
|
54
|
+
Liquid::Template.parse("{% raw %}body{%endraw%}")
|
55
|
+
Liquid::Template.parse("{% raw %}body{% endraw-%}")
|
56
|
+
Liquid::Template.parse("{% raw %}body{% endraw -%}")
|
57
|
+
Liquid::Template.parse("{% raw %}body{%-endraw %}")
|
58
|
+
Liquid::Template.parse("{% raw %}body{%- endraw %}")
|
59
|
+
Liquid::Template.parse("{% raw %}body{%-endraw-%}")
|
60
|
+
Liquid::Template.parse("{% raw %}body{%- endraw -%}")
|
61
|
+
Liquid::Template.parse("{% raw %}body{% endraw\u00A0%}")
|
62
|
+
Liquid::Template.parse("{% raw %}body{% endraw \u00A0%}")
|
63
|
+
Liquid::Template.parse("{% raw %}body{% endraw\u00A0 %}")
|
64
|
+
Liquid::Template.parse("{% raw %}body{% endraw \u00A0 %}")
|
65
|
+
Liquid::Template.parse("{% raw %}body{% endraw \u00A0 endraw %}")
|
66
|
+
Liquid::Template.parse("{% raw %}body{% endraw\u00A0endraw %}")
|
67
|
+
|
68
|
+
[
|
69
|
+
"{%\u00A0endraw%}",
|
70
|
+
"{%\u00A0 endraw%}",
|
71
|
+
"{% \u00A0endraw%}",
|
72
|
+
"{% \u00A0 endraw%}",
|
73
|
+
"{%\u00A0endraw\u00A0%}",
|
74
|
+
"{% - endraw %}",
|
75
|
+
"{% endnot endraw %}",
|
76
|
+
].each do |bad_delimiter|
|
77
|
+
exception = assert_raises(
|
78
|
+
Liquid::SyntaxError,
|
79
|
+
"#{bad_delimiter.inspect} did not raise Liquid::SyntaxError"
|
80
|
+
) do
|
81
|
+
Liquid::Template.parse(
|
82
|
+
"{% raw %}body#{bad_delimiter}"
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
assert_equal(
|
87
|
+
exception.message,
|
88
|
+
"Liquid syntax error: 'raw' tag was never closed",
|
89
|
+
"#{bad_delimiter.inspect} raised the wrong exception message",
|
90
|
+
)
|
91
|
+
end
|
18
92
|
end
|
19
93
|
end
|
data/test/unit/tokenizer_test.rb
CHANGED
@@ -6,7 +6,7 @@ require "test_helper"
|
|
6
6
|
class TokenizerTest < Minitest::Test
|
7
7
|
def test_tokenizer_nil
|
8
8
|
tokenizer = new_tokenizer(nil)
|
9
|
-
assert_nil(tokenizer.
|
9
|
+
assert_nil(tokenizer.shift)
|
10
10
|
end
|
11
11
|
|
12
12
|
def test_tokenize_strings
|
@@ -60,10 +60,10 @@ class TokenizerTest < Minitest::Test
|
|
60
60
|
def test_utf8_compatible_source
|
61
61
|
source = String.new("ascii", encoding: Encoding::ASCII)
|
62
62
|
tokenizer = new_tokenizer(source)
|
63
|
-
output = tokenizer.
|
63
|
+
output = tokenizer.shift
|
64
64
|
assert_equal(Encoding::UTF_8, output.encoding)
|
65
65
|
assert_equal(source, output)
|
66
|
-
assert_nil(tokenizer.
|
66
|
+
assert_nil(tokenizer.shift)
|
67
67
|
end
|
68
68
|
|
69
69
|
def test_non_utf8_compatible_source
|
@@ -105,7 +105,7 @@ class TokenizerTest < Minitest::Test
|
|
105
105
|
def tokenize(source, for_liquid_tag: false, trimmed: false)
|
106
106
|
tokenizer = Liquid::C::Tokenizer.new(source, 1, for_liquid_tag)
|
107
107
|
tokens = []
|
108
|
-
while (t = trimmed ? tokenizer.send(:shift_trimmed) : tokenizer.
|
108
|
+
while (t = trimmed ? tokenizer.send(:shift_trimmed) : tokenizer.shift)
|
109
109
|
tokens << t
|
110
110
|
end
|
111
111
|
tokens
|
data/test/unit/variable_test.rb
CHANGED
@@ -254,6 +254,73 @@ class VariableTest < Minitest::Test
|
|
254
254
|
assert_equal("2", output)
|
255
255
|
end
|
256
256
|
|
257
|
+
def test_encoding_error_message_with_multi_byte_characters
|
258
|
+
# 2 byte character
|
259
|
+
exc = assert_raises(Liquid::SyntaxError) do
|
260
|
+
variable_strict_parse("\u00A0")
|
261
|
+
end
|
262
|
+
assert_equal(
|
263
|
+
"Liquid syntax error: Unexpected character \u00A0 in \"{{\u00a0}}\"",
|
264
|
+
exc.message
|
265
|
+
)
|
266
|
+
|
267
|
+
# 3 byte character
|
268
|
+
exc = assert_raises(Liquid::SyntaxError) do
|
269
|
+
variable_strict_parse("\u3042")
|
270
|
+
end
|
271
|
+
assert_equal(
|
272
|
+
"Liquid syntax error: Unexpected character \u3042 in \"{{\u3042}}\"",
|
273
|
+
exc.message
|
274
|
+
)
|
275
|
+
|
276
|
+
# 4 byte character
|
277
|
+
exc = assert_raises(Liquid::SyntaxError) do
|
278
|
+
variable_strict_parse("\u{1F600}")
|
279
|
+
end
|
280
|
+
assert_equal(
|
281
|
+
"Liquid syntax error: Unexpected character \u{1F600} in \"{{\u{1F600}}}\"",
|
282
|
+
exc.message
|
283
|
+
)
|
284
|
+
end
|
285
|
+
|
286
|
+
def test_invalid_utf8_sequence
|
287
|
+
# 2 byte character with 1 byte missing
|
288
|
+
exc = assert_raises(ArgumentError) do
|
289
|
+
variable_strict_parse("\xC0")
|
290
|
+
end
|
291
|
+
assert_equal("invalid byte sequence in UTF-8", exc.message)
|
292
|
+
|
293
|
+
# 3 byte character with 1 byte missing
|
294
|
+
exc = assert_raises(ArgumentError) do
|
295
|
+
variable_strict_parse("\xE0\x01")
|
296
|
+
end
|
297
|
+
assert_equal("invalid byte sequence in UTF-8", exc.message)
|
298
|
+
|
299
|
+
# 3 byte character with 2 byte missing
|
300
|
+
exc = assert_raises(ArgumentError) do
|
301
|
+
variable_strict_parse("\xE0")
|
302
|
+
end
|
303
|
+
assert_equal("invalid byte sequence in UTF-8", exc.message)
|
304
|
+
|
305
|
+
# 4 byte character with 1 byte missing
|
306
|
+
exc = assert_raises(ArgumentError) do
|
307
|
+
variable_strict_parse("\xF0\x01\x01")
|
308
|
+
end
|
309
|
+
assert_equal("invalid byte sequence in UTF-8", exc.message)
|
310
|
+
|
311
|
+
# 4 byte character with 2 byte missing
|
312
|
+
exc = assert_raises(ArgumentError) do
|
313
|
+
variable_strict_parse("\xF0\x01")
|
314
|
+
end
|
315
|
+
assert_equal("invalid byte sequence in UTF-8", exc.message)
|
316
|
+
|
317
|
+
# 4 byte character with 3 byte missing
|
318
|
+
exc = assert_raises(ArgumentError) do
|
319
|
+
variable_strict_parse("\xF0")
|
320
|
+
end
|
321
|
+
assert_equal("invalid byte sequence in UTF-8", exc.message)
|
322
|
+
end
|
323
|
+
|
257
324
|
private
|
258
325
|
|
259
326
|
def variable_strict_parse(markup)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: liquid-c
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Li
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-01-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: liquid
|
@@ -103,6 +103,7 @@ extensions:
|
|
103
103
|
- ext/liquid_c/extconf.rb
|
104
104
|
extra_rdoc_files: []
|
105
105
|
files:
|
106
|
+
- ".github/workflows/cla.yml"
|
106
107
|
- ".github/workflows/liquid.yml"
|
107
108
|
- ".gitignore"
|
108
109
|
- ".rubocop.yml"
|
@@ -126,6 +127,8 @@ files:
|
|
126
127
|
- ext/liquid_c/lexer.h
|
127
128
|
- ext/liquid_c/liquid.c
|
128
129
|
- ext/liquid_c/liquid.h
|
130
|
+
- ext/liquid_c/liquid_vm.c
|
131
|
+
- ext/liquid_c/liquid_vm.h
|
129
132
|
- ext/liquid_c/parse_context.c
|
130
133
|
- ext/liquid_c/parse_context.h
|
131
134
|
- ext/liquid_c/parser.c
|
@@ -143,8 +146,6 @@ files:
|
|
143
146
|
- ext/liquid_c/variable.h
|
144
147
|
- ext/liquid_c/variable_lookup.c
|
145
148
|
- ext/liquid_c/variable_lookup.h
|
146
|
-
- ext/liquid_c/vm.c
|
147
|
-
- ext/liquid_c/vm.h
|
148
149
|
- ext/liquid_c/vm_assembler.c
|
149
150
|
- ext/liquid_c/vm_assembler.h
|
150
151
|
- ext/liquid_c/vm_assembler_pool.c
|
@@ -191,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
191
192
|
- !ruby/object:Gem::Version
|
192
193
|
version: '0'
|
193
194
|
requirements: []
|
194
|
-
rubygems_version: 3.
|
195
|
+
rubygems_version: 3.5.4
|
195
196
|
signing_key:
|
196
197
|
specification_version: 4
|
197
198
|
summary: Liquid performance extension in C
|
File without changes
|