liquid-c 4.0.1 → 4.1.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/liquid.yml +24 -2
- data/.gitignore +4 -0
- data/.rubocop.yml +14 -0
- data/Gemfile +14 -5
- data/README.md +29 -5
- data/Rakefile +13 -62
- data/ext/liquid_c/block.c +488 -60
- data/ext/liquid_c/block.h +28 -2
- data/ext/liquid_c/c_buffer.c +42 -0
- data/ext/liquid_c/c_buffer.h +76 -0
- data/ext/liquid_c/context.c +233 -0
- data/ext/liquid_c/context.h +70 -0
- data/ext/liquid_c/document_body.c +89 -0
- data/ext/liquid_c/document_body.h +59 -0
- data/ext/liquid_c/expression.c +116 -0
- data/ext/liquid_c/expression.h +24 -0
- data/ext/liquid_c/extconf.rb +19 -9
- data/ext/liquid_c/intutil.h +22 -0
- data/ext/liquid_c/lexer.c +6 -2
- data/ext/liquid_c/lexer.h +18 -3
- data/ext/liquid_c/liquid.c +76 -6
- data/ext/liquid_c/liquid.h +24 -1
- data/ext/liquid_c/parse_context.c +76 -0
- data/ext/liquid_c/parse_context.h +13 -0
- data/ext/liquid_c/parser.c +141 -65
- data/ext/liquid_c/parser.h +4 -2
- data/ext/liquid_c/raw.c +110 -0
- data/ext/liquid_c/raw.h +6 -0
- data/ext/liquid_c/resource_limits.c +279 -0
- data/ext/liquid_c/resource_limits.h +23 -0
- data/ext/liquid_c/stringutil.h +44 -0
- data/ext/liquid_c/tokenizer.c +149 -35
- data/ext/liquid_c/tokenizer.h +20 -9
- data/ext/liquid_c/usage.c +18 -0
- data/ext/liquid_c/usage.h +9 -0
- data/ext/liquid_c/variable.c +196 -20
- data/ext/liquid_c/variable.h +18 -1
- data/ext/liquid_c/variable_lookup.c +44 -0
- data/ext/liquid_c/variable_lookup.h +8 -0
- data/ext/liquid_c/vm.c +588 -0
- data/ext/liquid_c/vm.h +25 -0
- data/ext/liquid_c/vm_assembler.c +491 -0
- data/ext/liquid_c/vm_assembler.h +240 -0
- data/ext/liquid_c/vm_assembler_pool.c +97 -0
- data/ext/liquid_c/vm_assembler_pool.h +27 -0
- data/lib/liquid/c/compile_ext.rb +44 -0
- data/lib/liquid/c/version.rb +3 -1
- data/lib/liquid/c.rb +225 -46
- data/liquid-c.gemspec +16 -10
- data/performance/c_profile.rb +23 -0
- data/performance.rb +6 -4
- data/rakelib/compile.rake +15 -0
- data/rakelib/integration_test.rake +43 -0
- data/rakelib/performance.rake +43 -0
- data/rakelib/rubocop.rake +6 -0
- data/rakelib/unit_test.rake +14 -0
- data/test/integration_test.rb +11 -0
- data/test/liquid_test_helper.rb +21 -0
- data/test/test_helper.rb +14 -2
- data/test/unit/block_test.rb +130 -0
- data/test/unit/context_test.rb +83 -0
- data/test/unit/expression_test.rb +186 -0
- data/test/unit/gc_stress_test.rb +28 -0
- data/test/unit/raw_test.rb +19 -0
- data/test/unit/resource_limits_test.rb +50 -0
- data/test/unit/tokenizer_test.rb +90 -20
- data/test/unit/variable_test.rb +212 -60
- metadata +59 -11
- data/test/liquid_test.rb +0 -11
@@ -0,0 +1,24 @@
|
|
1
|
+
#if !defined(LIQUID_EXPRESSION_H)
|
2
|
+
#define LIQUID_EXPRESSION_H
|
3
|
+
|
4
|
+
#include "vm_assembler.h"
|
5
|
+
#include "parser.h"
|
6
|
+
|
7
|
+
extern VALUE cLiquidCExpression;
|
8
|
+
extern const rb_data_type_t expression_data_type;
|
9
|
+
|
10
|
+
typedef struct expression {
|
11
|
+
vm_assembler_t code;
|
12
|
+
} expression_t;
|
13
|
+
|
14
|
+
extern const rb_data_type_t expression_data_type;
|
15
|
+
#define Expression_Get_Struct(obj, sval) TypedData_Get_Struct(obj, expression_t, &expression_data_type, sval)
|
16
|
+
|
17
|
+
void liquid_define_expression(void);
|
18
|
+
|
19
|
+
VALUE expression_new(VALUE klass, expression_t **expression_ptr);
|
20
|
+
VALUE expression_evaluate(VALUE self, VALUE context);
|
21
|
+
VALUE internal_expression_evaluate(expression_t *expression, VALUE context);
|
22
|
+
|
23
|
+
#endif
|
24
|
+
|
data/ext/liquid_c/extconf.rb
CHANGED
@@ -1,14 +1,24 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mkmf"
|
4
|
+
$CFLAGS << " -std=c11 -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers"
|
5
|
+
append_cflags("-fvisibility=hidden")
|
3
6
|
# In Ruby 2.6 and earlier, the Ruby headers did not have struct timespec defined
|
4
|
-
valid_headers = RbConfig::CONFIG[
|
5
|
-
pedantic = !ENV[
|
7
|
+
valid_headers = RbConfig::CONFIG["host_os"] !~ /linux/ || Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7")
|
8
|
+
pedantic = !ENV["LIQUID_C_PEDANTIC"].to_s.empty?
|
6
9
|
if pedantic && valid_headers
|
7
|
-
$CFLAGS <<
|
10
|
+
$CFLAGS << " -Werror"
|
8
11
|
end
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
+
if ENV["DEBUG"] == "true"
|
13
|
+
append_cflags("-fbounds-check")
|
14
|
+
CONFIG["optflags"] = " -O0"
|
15
|
+
else
|
16
|
+
$CFLAGS << " -DNDEBUG"
|
12
17
|
end
|
13
|
-
|
18
|
+
|
19
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0") # added in 2.7
|
20
|
+
$CFLAGS << " -DHAVE_RB_HASH_BULK_INSERT"
|
21
|
+
end
|
22
|
+
|
23
|
+
$warnflags&.gsub!(/-Wdeclaration-after-statement/, "")
|
14
24
|
create_makefile("liquid_c")
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#ifndef LIQUID_INTUTIL_H
|
2
|
+
#define LIQUID_INTUTIL_H
|
3
|
+
|
4
|
+
#include <stdint.h>
|
5
|
+
|
6
|
+
static inline unsigned int bytes_to_uint24(const uint8_t *bytes)
|
7
|
+
{
|
8
|
+
return (bytes[0] << 16) | (bytes[1] << 8) | bytes[2];
|
9
|
+
}
|
10
|
+
|
11
|
+
static inline void uint24_to_bytes(unsigned int num, uint8_t *bytes)
|
12
|
+
{
|
13
|
+
assert(num < (1 << 24));
|
14
|
+
|
15
|
+
bytes[0] = num >> 16;
|
16
|
+
bytes[1] = num >> 8;
|
17
|
+
bytes[2] = num;
|
18
|
+
|
19
|
+
assert(bytes_to_uint24(bytes) == num);
|
20
|
+
}
|
21
|
+
|
22
|
+
#endif
|
data/ext/liquid_c/lexer.c
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#include "liquid.h"
|
2
2
|
#include "lexer.h"
|
3
|
+
#include "usage.h"
|
3
4
|
#include <stdio.h>
|
4
5
|
|
5
6
|
const char *symbol_names[TOKEN_END] = {
|
@@ -59,8 +60,6 @@ inline static const char *scan_past(const char *cur, const char *end, char targe
|
|
59
60
|
const char *tok_end = str + (n); \
|
60
61
|
token->type = (t); \
|
61
62
|
token->val = str; \
|
62
|
-
if (str != start) token->flags |= TOKEN_SPACE_PREFIX; \
|
63
|
-
if (tok_end < end && ISSPACE(*tok_end)) token->flags |= TOKEN_SPACE_SUFFIX; \
|
64
63
|
return (token->val_end = tok_end); \
|
65
64
|
}
|
66
65
|
|
@@ -109,6 +108,11 @@ const char *lex_one(const char *start, const char *end, lexer_token_t *token)
|
|
109
108
|
}
|
110
109
|
}
|
111
110
|
|
111
|
+
// Instrument for bug: https://github.com/Shopify/liquid-c/pull/120
|
112
|
+
if (c == '-' && str + 1 < end && str[1] == '.') {
|
113
|
+
usage_increment("liquid_c_negative_float_without_integer");
|
114
|
+
}
|
115
|
+
|
112
116
|
if (ISDIGIT(c) || c == '-') {
|
113
117
|
int has_dot = 0;
|
114
118
|
cur = str;
|
data/ext/liquid_c/lexer.h
CHANGED
@@ -24,9 +24,6 @@ enum lexer_token_type {
|
|
24
24
|
TOKEN_END = 256
|
25
25
|
};
|
26
26
|
|
27
|
-
#define TOKEN_SPACE_PREFIX 0x1
|
28
|
-
#define TOKEN_SPACE_SUFFIX 0x2
|
29
|
-
#define TOKEN_SPACE_AFFIX (TOKEN_SPACE_PREFIX | TOKEN_SPACE_SUFFIX)
|
30
27
|
#define TOKEN_FLOAT_NUMBER 0x4
|
31
28
|
|
32
29
|
typedef struct lexer_token {
|
@@ -42,5 +39,23 @@ inline static VALUE token_to_rstr(lexer_token_t token) {
|
|
42
39
|
return rb_enc_str_new(token.val, token.val_end - token.val, utf8_encoding);
|
43
40
|
}
|
44
41
|
|
42
|
+
inline static VALUE token_check_for_symbol(lexer_token_t token) {
|
43
|
+
return rb_check_symbol_cstr(token.val, token.val_end - token.val, utf8_encoding);
|
44
|
+
}
|
45
|
+
|
46
|
+
inline static VALUE token_to_rstr_leveraging_existing_symbol(lexer_token_t token) {
|
47
|
+
VALUE sym = token_check_for_symbol(token);
|
48
|
+
if (RB_LIKELY(sym != Qnil))
|
49
|
+
return rb_sym2str(sym);
|
50
|
+
return token_to_rstr(token);
|
51
|
+
}
|
52
|
+
|
53
|
+
inline static VALUE token_to_rsym(lexer_token_t token) {
|
54
|
+
VALUE sym = token_check_for_symbol(token);
|
55
|
+
if (RB_LIKELY(sym != Qnil))
|
56
|
+
return sym;
|
57
|
+
return rb_str_intern(token_to_rstr(token));
|
58
|
+
}
|
59
|
+
|
45
60
|
#endif
|
46
61
|
|
data/ext/liquid_c/liquid.c
CHANGED
@@ -3,23 +3,93 @@
|
|
3
3
|
#include "variable.h"
|
4
4
|
#include "lexer.h"
|
5
5
|
#include "parser.h"
|
6
|
+
#include "raw.h"
|
7
|
+
#include "resource_limits.h"
|
8
|
+
#include "expression.h"
|
9
|
+
#include "document_body.h"
|
6
10
|
#include "block.h"
|
11
|
+
#include "context.h"
|
12
|
+
#include "parse_context.h"
|
13
|
+
#include "variable_lookup.h"
|
14
|
+
#include "vm_assembler_pool.h"
|
15
|
+
#include "vm.h"
|
16
|
+
#include "usage.h"
|
17
|
+
|
18
|
+
ID id_evaluate;
|
19
|
+
ID id_to_liquid;
|
20
|
+
ID id_to_s;
|
21
|
+
ID id_call;
|
22
|
+
ID id_compile_evaluate;
|
23
|
+
ID id_ivar_line_number;
|
24
|
+
|
25
|
+
VALUE mLiquid, mLiquidC, cLiquidVariable, cLiquidTemplate, cLiquidBlockBody;
|
26
|
+
VALUE cLiquidVariableLookup, cLiquidRangeLookup;
|
27
|
+
VALUE cLiquidArgumentError, cLiquidSyntaxError, cMemoryError;
|
7
28
|
|
8
|
-
VALUE mLiquid, mLiquidC, cLiquidSyntaxError, cLiquidVariable, cLiquidTemplate;
|
9
29
|
rb_encoding *utf8_encoding;
|
30
|
+
int utf8_encoding_index;
|
31
|
+
|
32
|
+
__attribute__((noreturn)) void raise_non_utf8_encoding_error(VALUE string, const char *value_name)
|
33
|
+
{
|
34
|
+
rb_raise(rb_eEncCompatError, "non-UTF8 encoded %s (%"PRIsVALUE") not supported", value_name, rb_obj_encoding(string));
|
35
|
+
}
|
10
36
|
|
11
|
-
void Init_liquid_c(void)
|
37
|
+
RUBY_FUNC_EXPORTED void Init_liquid_c(void)
|
12
38
|
{
|
39
|
+
id_evaluate = rb_intern("evaluate");
|
40
|
+
id_to_liquid = rb_intern("to_liquid");
|
41
|
+
id_to_s = rb_intern("to_s");
|
42
|
+
id_call = rb_intern("call");
|
43
|
+
id_compile_evaluate = rb_intern("compile_evaluate");
|
44
|
+
id_ivar_line_number = rb_intern("@line_number");
|
45
|
+
|
13
46
|
utf8_encoding = rb_utf8_encoding();
|
47
|
+
utf8_encoding_index = rb_enc_to_index(utf8_encoding);
|
48
|
+
|
14
49
|
mLiquid = rb_define_module("Liquid");
|
50
|
+
rb_global_variable(&mLiquid);
|
51
|
+
|
15
52
|
mLiquidC = rb_define_module_under(mLiquid, "C");
|
53
|
+
rb_global_variable(&mLiquidC);
|
54
|
+
|
55
|
+
cLiquidArgumentError = rb_const_get(mLiquid, rb_intern("ArgumentError"));
|
56
|
+
rb_global_variable(&cLiquidArgumentError);
|
57
|
+
|
16
58
|
cLiquidSyntaxError = rb_const_get(mLiquid, rb_intern("SyntaxError"));
|
59
|
+
rb_global_variable(&cLiquidSyntaxError);
|
60
|
+
|
61
|
+
cMemoryError = rb_const_get(mLiquid, rb_intern("MemoryError"));
|
62
|
+
rb_global_variable(&cMemoryError);
|
63
|
+
|
17
64
|
cLiquidVariable = rb_const_get(mLiquid, rb_intern("Variable"));
|
65
|
+
rb_global_variable(&cLiquidVariable);
|
66
|
+
|
18
67
|
cLiquidTemplate = rb_const_get(mLiquid, rb_intern("Template"));
|
68
|
+
rb_global_variable(&cLiquidTemplate);
|
69
|
+
|
70
|
+
cLiquidBlockBody = rb_const_get(mLiquid, rb_intern("BlockBody"));
|
71
|
+
rb_global_variable(&cLiquidBlockBody);
|
72
|
+
|
73
|
+
cLiquidVariableLookup = rb_const_get(mLiquid, rb_intern("VariableLookup"));
|
74
|
+
rb_global_variable(&cLiquidVariableLookup);
|
75
|
+
|
76
|
+
cLiquidRangeLookup = rb_const_get(mLiquid, rb_intern("RangeLookup"));
|
77
|
+
rb_global_variable(&cLiquidRangeLookup);
|
19
78
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
79
|
+
liquid_define_tokenizer();
|
80
|
+
liquid_define_parser();
|
81
|
+
liquid_define_raw();
|
82
|
+
liquid_define_resource_limits();
|
83
|
+
liquid_define_expression();
|
84
|
+
liquid_define_variable();
|
85
|
+
liquid_define_document_body();
|
86
|
+
liquid_define_block_body();
|
87
|
+
liquid_define_context();
|
88
|
+
liquid_define_parse_context();
|
89
|
+
liquid_define_variable_lookup();
|
90
|
+
liquid_define_vm_assembler_pool();
|
91
|
+
liquid_define_vm_assembler();
|
92
|
+
liquid_define_vm();
|
93
|
+
liquid_define_usage();
|
24
94
|
}
|
25
95
|
|
data/ext/liquid_c/liquid.h
CHANGED
@@ -5,8 +5,31 @@
|
|
5
5
|
#include <ruby/encoding.h>
|
6
6
|
#include <stdbool.h>
|
7
7
|
|
8
|
-
extern
|
8
|
+
extern ID id_evaluate;
|
9
|
+
extern ID id_to_liquid;
|
10
|
+
extern ID id_to_s;
|
11
|
+
extern ID id_call;
|
12
|
+
extern ID id_compile_evaluate;
|
13
|
+
extern ID id_ivar_line_number;
|
14
|
+
|
15
|
+
extern VALUE mLiquid, mLiquidC, cLiquidVariable, cLiquidTemplate, cLiquidBlockBody;
|
16
|
+
extern VALUE cLiquidVariableLookup, cLiquidRangeLookup;
|
17
|
+
extern VALUE cLiquidArgumentError, cLiquidSyntaxError, cMemoryError;
|
9
18
|
extern rb_encoding *utf8_encoding;
|
19
|
+
extern int utf8_encoding_index;
|
20
|
+
|
21
|
+
__attribute__((noreturn)) void raise_non_utf8_encoding_error(VALUE string, const char *string_name);
|
22
|
+
|
23
|
+
static inline void check_utf8_encoding(VALUE string, const char *string_name)
|
24
|
+
{
|
25
|
+
if (RB_UNLIKELY(RB_ENCODING_GET_INLINED(string) != utf8_encoding_index))
|
26
|
+
raise_non_utf8_encoding_error(string, string_name);
|
27
|
+
}
|
28
|
+
|
29
|
+
#ifndef RB_LIKELY
|
30
|
+
// RB_LIKELY added in Ruby 2.4
|
31
|
+
#define RB_LIKELY(x) (__builtin_expect(!!(x), 1))
|
32
|
+
#endif
|
10
33
|
|
11
34
|
#endif
|
12
35
|
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#include "parse_context.h"
|
2
|
+
#include "document_body.h"
|
3
|
+
|
4
|
+
static ID id_document_body, id_vm_assembler_pool;
|
5
|
+
|
6
|
+
static bool parse_context_document_body_initialized_p(VALUE self)
|
7
|
+
{
|
8
|
+
return RTEST(rb_attr_get(self, id_document_body));
|
9
|
+
}
|
10
|
+
|
11
|
+
static void parse_context_init_document_body(VALUE self)
|
12
|
+
{
|
13
|
+
VALUE document_body = document_body_new_instance();
|
14
|
+
rb_ivar_set(self, id_document_body, document_body);
|
15
|
+
}
|
16
|
+
|
17
|
+
VALUE parse_context_get_document_body(VALUE self)
|
18
|
+
{
|
19
|
+
assert(parse_context_document_body_initialized_p(self));
|
20
|
+
|
21
|
+
return rb_ivar_get(self, id_document_body);
|
22
|
+
}
|
23
|
+
|
24
|
+
vm_assembler_pool_t *parse_context_init_vm_assembler_pool(VALUE self)
|
25
|
+
{
|
26
|
+
assert(!RTEST(rb_attr_get(self, id_vm_assembler_pool)));
|
27
|
+
|
28
|
+
VALUE vm_assembler_pool_obj = vm_assembler_pool_new();
|
29
|
+
rb_ivar_set(self, id_vm_assembler_pool, vm_assembler_pool_obj);
|
30
|
+
|
31
|
+
vm_assembler_pool_t *vm_assembler_pool;
|
32
|
+
VMAssemblerPool_Get_Struct(vm_assembler_pool_obj, vm_assembler_pool);
|
33
|
+
|
34
|
+
return vm_assembler_pool;
|
35
|
+
}
|
36
|
+
|
37
|
+
vm_assembler_pool_t *parse_context_get_vm_assembler_pool(VALUE self)
|
38
|
+
{
|
39
|
+
VALUE obj = rb_ivar_get(self, id_vm_assembler_pool);
|
40
|
+
|
41
|
+
if (obj == Qnil) {
|
42
|
+
rb_raise(rb_eRuntimeError, "Liquid::ParseContext#start_liquid_c_parsing has not yet been called");
|
43
|
+
}
|
44
|
+
|
45
|
+
vm_assembler_pool_t *vm_assembler_pool;
|
46
|
+
VMAssemblerPool_Get_Struct(obj, vm_assembler_pool);
|
47
|
+
return vm_assembler_pool;
|
48
|
+
}
|
49
|
+
|
50
|
+
static VALUE parse_context_start_liquid_c_parsing(VALUE self)
|
51
|
+
{
|
52
|
+
if (RB_UNLIKELY(parse_context_document_body_initialized_p(self))) {
|
53
|
+
rb_raise(rb_eRuntimeError, "liquid-c parsing already started for this parse context");
|
54
|
+
}
|
55
|
+
parse_context_init_document_body(self);
|
56
|
+
parse_context_init_vm_assembler_pool(self);
|
57
|
+
return Qnil;
|
58
|
+
}
|
59
|
+
|
60
|
+
static VALUE parse_context_cleanup_liquid_c_parsing(VALUE self)
|
61
|
+
{
|
62
|
+
rb_obj_freeze(rb_ivar_get(self, id_document_body));
|
63
|
+
rb_ivar_set(self, id_document_body, Qnil);
|
64
|
+
rb_ivar_set(self, id_vm_assembler_pool, Qnil);
|
65
|
+
return Qnil;
|
66
|
+
}
|
67
|
+
|
68
|
+
void liquid_define_parse_context(void)
|
69
|
+
{
|
70
|
+
id_document_body = rb_intern("document_body");
|
71
|
+
id_vm_assembler_pool = rb_intern("vm_assembler_pool");
|
72
|
+
|
73
|
+
VALUE cLiquidParseContext = rb_const_get(mLiquid, rb_intern("ParseContext"));
|
74
|
+
rb_define_method(cLiquidParseContext, "start_liquid_c_parsing", parse_context_start_liquid_c_parsing, 0);
|
75
|
+
rb_define_method(cLiquidParseContext, "cleanup_liquid_c_parsing", parse_context_cleanup_liquid_c_parsing, 0);
|
76
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#ifndef LIQUID_PARSE_CONTEXT_H
|
2
|
+
#define LIQUID_PARSE_CONTEXT_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <stdbool.h>
|
6
|
+
#include "vm_assembler_pool.h"
|
7
|
+
|
8
|
+
void liquid_define_parse_context(void);
|
9
|
+
VALUE parse_context_get_document_body(VALUE self);
|
10
|
+
|
11
|
+
vm_assembler_pool_t *parse_context_get_vm_assembler_pool(VALUE self);
|
12
|
+
|
13
|
+
#endif
|
data/ext/liquid_c/parser.c
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
#include "parser.h"
|
3
3
|
#include "lexer.h"
|
4
4
|
|
5
|
-
static VALUE
|
6
|
-
static ID
|
5
|
+
static VALUE empty_string;
|
6
|
+
static ID id_to_i, idEvaluate;
|
7
7
|
|
8
8
|
void init_parser(parser_t *p, const char *str, const char *end)
|
9
9
|
{
|
@@ -67,79 +67,133 @@ static VALUE parse_number(parser_t *p)
|
|
67
67
|
return out;
|
68
68
|
}
|
69
69
|
|
70
|
-
static VALUE
|
70
|
+
static VALUE try_parse_constant_range(parser_t *p)
|
71
71
|
{
|
72
|
+
parser_t saved_state = *p;
|
73
|
+
|
72
74
|
parser_must_consume(p, TOKEN_OPEN_ROUND);
|
73
75
|
|
74
|
-
VALUE
|
75
|
-
|
76
|
+
VALUE begin = try_parse_constant_expression(p);
|
77
|
+
if (begin == Qundef) {
|
78
|
+
*p = saved_state;
|
79
|
+
return Qundef;
|
80
|
+
}
|
76
81
|
parser_must_consume(p, TOKEN_DOTDOT);
|
77
82
|
|
78
|
-
|
83
|
+
VALUE end = try_parse_constant_expression(p);
|
84
|
+
if (end == Qundef) {
|
85
|
+
*p = saved_state;
|
86
|
+
return Qundef;
|
87
|
+
}
|
79
88
|
parser_must_consume(p, TOKEN_CLOSE_ROUND);
|
80
89
|
|
81
|
-
|
82
|
-
|
90
|
+
begin = rb_funcall(begin, id_to_i, 0);
|
91
|
+
end = rb_funcall(end, id_to_i, 0);
|
83
92
|
|
84
|
-
|
93
|
+
bool exclude_end = false;
|
94
|
+
return rb_range_new(begin, end, exclude_end);
|
85
95
|
}
|
86
96
|
|
87
|
-
static
|
97
|
+
static void parse_and_compile_range(parser_t *p, vm_assembler_t *code)
|
88
98
|
{
|
89
|
-
VALUE
|
90
|
-
|
99
|
+
VALUE const_range = try_parse_constant_range(p);
|
100
|
+
if (const_range != Qundef) {
|
101
|
+
vm_assembler_add_push_const(code, const_range);
|
102
|
+
return;
|
103
|
+
}
|
91
104
|
|
105
|
+
parser_must_consume(p, TOKEN_OPEN_ROUND);
|
106
|
+
parse_and_compile_expression(p, code);
|
107
|
+
parser_must_consume(p, TOKEN_DOTDOT);
|
108
|
+
parse_and_compile_expression(p, code);
|
109
|
+
parser_must_consume(p, TOKEN_CLOSE_ROUND);
|
110
|
+
vm_assembler_add_new_int_range(code);
|
111
|
+
}
|
112
|
+
|
113
|
+
static void parse_and_compile_variable_lookup(parser_t *p, vm_assembler_t *code)
|
114
|
+
{
|
92
115
|
if (parser_consume(p, TOKEN_OPEN_SQUARE).type) {
|
93
|
-
|
116
|
+
parse_and_compile_expression(p, code);
|
94
117
|
parser_must_consume(p, TOKEN_CLOSE_SQUARE);
|
118
|
+
vm_assembler_add_find_variable(code);
|
95
119
|
} else {
|
96
|
-
name =
|
120
|
+
VALUE name = token_to_rstr_leveraging_existing_symbol(parser_must_consume(p, TOKEN_IDENTIFIER));
|
121
|
+
vm_assembler_add_find_static_variable(code, name);
|
97
122
|
}
|
98
123
|
|
99
124
|
while (true) {
|
100
125
|
if (p->cur.type == TOKEN_OPEN_SQUARE) {
|
101
126
|
parser_consume_any(p);
|
102
|
-
|
127
|
+
parse_and_compile_expression(p, code);
|
103
128
|
parser_must_consume(p, TOKEN_CLOSE_SQUARE);
|
104
|
-
|
105
|
-
rb_ary_push(lookups, lookup);
|
129
|
+
vm_assembler_add_lookup_key(code);
|
106
130
|
} else if (p->cur.type == TOKEN_DOT) {
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
if (has_space_affix)
|
111
|
-
rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "Unexpected dot");
|
112
|
-
|
113
|
-
if (rstring_eq(lookup, "size") || rstring_eq(lookup, "first") || rstring_eq(lookup, "last"))
|
114
|
-
command_flags |= 1 << RARRAY_LEN(lookups);
|
131
|
+
parser_consume_any(p);
|
132
|
+
VALUE key = token_to_rstr_leveraging_existing_symbol(parser_must_consume(p, TOKEN_IDENTIFIER));
|
115
133
|
|
116
|
-
|
134
|
+
if (rstring_eq(key, "size") || rstring_eq(key, "first") || rstring_eq(key, "last"))
|
135
|
+
vm_assembler_add_lookup_command(code, key);
|
136
|
+
else
|
137
|
+
vm_assembler_add_lookup_const_key(code, key);
|
117
138
|
} else {
|
118
139
|
break;
|
119
140
|
}
|
120
141
|
}
|
142
|
+
}
|
121
143
|
|
122
|
-
|
123
|
-
|
124
|
-
|
144
|
+
static VALUE try_parse_literal(parser_t *p)
|
145
|
+
{
|
146
|
+
if (p->next.type == TOKEN_DOT || p->next.type == TOKEN_OPEN_SQUARE)
|
147
|
+
return Qundef;
|
148
|
+
|
149
|
+
const char *str = p->cur.val;
|
150
|
+
long size = p->cur.val_end - str;
|
151
|
+
VALUE result = Qundef;
|
152
|
+
switch (size) {
|
153
|
+
case 3:
|
154
|
+
if (memcmp(str, "nil", size) == 0)
|
155
|
+
result = Qnil;
|
156
|
+
break;
|
157
|
+
case 4:
|
158
|
+
if (memcmp(str, "null", size) == 0) {
|
159
|
+
result = Qnil;
|
160
|
+
} else if (memcmp(str, "true", size) == 0) {
|
161
|
+
result = Qtrue;
|
162
|
+
}
|
163
|
+
break;
|
164
|
+
case 5:
|
165
|
+
switch (*str) {
|
166
|
+
case 'f':
|
167
|
+
if (memcmp(str, "false", size) == 0)
|
168
|
+
result = Qfalse;
|
169
|
+
break;
|
170
|
+
case 'b':
|
171
|
+
if (memcmp(str, "blank", size) == 0)
|
172
|
+
result = empty_string;
|
173
|
+
break;
|
174
|
+
case 'e':
|
175
|
+
if (memcmp(str, "empty", size) == 0)
|
176
|
+
result = empty_string;
|
177
|
+
break;
|
178
|
+
}
|
179
|
+
break;
|
125
180
|
}
|
126
|
-
|
127
|
-
|
128
|
-
return
|
181
|
+
if (result != Qundef)
|
182
|
+
parser_consume_any(p);
|
183
|
+
return result;
|
129
184
|
}
|
130
185
|
|
131
|
-
VALUE
|
186
|
+
VALUE try_parse_constant_expression(parser_t *p)
|
132
187
|
{
|
133
188
|
switch (p->cur.type) {
|
134
189
|
case TOKEN_IDENTIFIER:
|
135
|
-
|
136
|
-
return parse_variable(p);
|
190
|
+
return try_parse_literal(p);
|
137
191
|
|
138
192
|
case TOKEN_NUMBER:
|
139
193
|
return parse_number(p);
|
140
194
|
|
141
195
|
case TOKEN_OPEN_ROUND:
|
142
|
-
return
|
196
|
+
return try_parse_constant_range(p);
|
143
197
|
|
144
198
|
case TOKEN_STRING:
|
145
199
|
{
|
@@ -149,47 +203,69 @@ VALUE parse_expression(parser_t *p)
|
|
149
203
|
return token_to_rstr(token);
|
150
204
|
}
|
151
205
|
}
|
152
|
-
|
153
|
-
if (p->cur.type == TOKEN_EOS) {
|
154
|
-
rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "[:%s] is not a valid expression", symbol_names[p->cur.type]);
|
155
|
-
} else {
|
156
|
-
rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "[:%s, \"%.*s\"] is not a valid expression",
|
157
|
-
symbol_names[p->cur.type], (int)(p->cur.val_end - p->cur.val), p->cur.val);
|
158
|
-
}
|
159
|
-
return Qnil;
|
206
|
+
return Qundef;
|
160
207
|
}
|
161
208
|
|
162
|
-
static
|
209
|
+
static void parse_and_compile_number(parser_t *p, vm_assembler_t *code)
|
163
210
|
{
|
164
|
-
|
165
|
-
|
211
|
+
VALUE num = parse_number(p);
|
212
|
+
if (RB_FIXNUM_P(num))
|
213
|
+
vm_assembler_add_push_fixnum(code, num);
|
214
|
+
else
|
215
|
+
vm_assembler_add_push_const(code, num);
|
216
|
+
return;
|
217
|
+
}
|
166
218
|
|
167
|
-
|
168
|
-
|
219
|
+
void parse_and_compile_expression(parser_t *p, vm_assembler_t *code)
|
220
|
+
{
|
221
|
+
switch (p->cur.type) {
|
222
|
+
case TOKEN_IDENTIFIER:
|
223
|
+
{
|
224
|
+
VALUE literal = try_parse_literal(p);
|
225
|
+
if (literal != Qundef) {
|
226
|
+
vm_assembler_add_push_literal(code, literal);
|
227
|
+
return;
|
228
|
+
}
|
229
|
+
|
230
|
+
__attribute__ ((fallthrough));
|
231
|
+
}
|
232
|
+
case TOKEN_OPEN_SQUARE:
|
233
|
+
parse_and_compile_variable_lookup(p, code);
|
234
|
+
return;
|
169
235
|
|
170
|
-
|
171
|
-
|
236
|
+
case TOKEN_NUMBER:
|
237
|
+
parse_and_compile_number(p, code);
|
238
|
+
return;
|
172
239
|
|
173
|
-
|
240
|
+
case TOKEN_OPEN_ROUND:
|
241
|
+
parse_and_compile_range(p, code);
|
242
|
+
return;
|
174
243
|
|
175
|
-
|
176
|
-
|
244
|
+
case TOKEN_STRING:
|
245
|
+
{
|
246
|
+
lexer_token_t token = parser_consume_any(p);
|
247
|
+
token.val++;
|
248
|
+
token.val_end--;
|
249
|
+
VALUE str = token_to_rstr(token);
|
250
|
+
vm_assembler_add_push_const(code, str);
|
251
|
+
return;
|
252
|
+
}
|
253
|
+
}
|
177
254
|
|
178
|
-
|
255
|
+
if (p->cur.type == TOKEN_EOS) {
|
256
|
+
rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "[:%s] is not a valid expression", symbol_names[p->cur.type]);
|
257
|
+
} else {
|
258
|
+
rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "[:%s, \"%.*s\"] is not a valid expression",
|
259
|
+
symbol_names[p->cur.type], (int)(p->cur.val_end - p->cur.val), p->cur.val);
|
260
|
+
}
|
179
261
|
}
|
180
262
|
|
181
|
-
void
|
263
|
+
void liquid_define_parser(void)
|
182
264
|
{
|
183
|
-
|
265
|
+
id_to_i = rb_intern("to_i");
|
184
266
|
idEvaluate = rb_intern("evaluate");
|
185
267
|
|
186
|
-
|
187
|
-
|
188
|
-
cLiquidVariableLookup = rb_const_get(mLiquid, rb_intern("VariableLookup"));
|
189
|
-
|
190
|
-
VALUE cLiquidExpression = rb_const_get(mLiquid, rb_intern("Expression"));
|
191
|
-
rb_define_singleton_method(cLiquidExpression, "c_parse", rb_parse_expression, 1);
|
192
|
-
|
193
|
-
vLiquidExpressionLiterals = rb_const_get(cLiquidExpression, rb_intern("LITERALS"));
|
268
|
+
empty_string = rb_utf8_str_new_literal("");
|
269
|
+
rb_global_variable(&empty_string);
|
194
270
|
}
|
195
271
|
|
data/ext/liquid_c/parser.h
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
#define LIQUID_PARSER_H
|
3
3
|
|
4
4
|
#include "lexer.h"
|
5
|
+
#include "vm_assembler.h"
|
5
6
|
|
6
7
|
typedef struct parser {
|
7
8
|
lexer_token_t cur, next;
|
@@ -14,9 +15,10 @@ lexer_token_t parser_must_consume(parser_t *parser, unsigned char type);
|
|
14
15
|
lexer_token_t parser_consume(parser_t *parser, unsigned char type);
|
15
16
|
lexer_token_t parser_consume_any(parser_t *parser);
|
16
17
|
|
17
|
-
|
18
|
+
void parse_and_compile_expression(parser_t *p, vm_assembler_t *code);
|
19
|
+
VALUE try_parse_constant_expression(parser_t *p);
|
18
20
|
|
19
|
-
void
|
21
|
+
void liquid_define_parser(void);
|
20
22
|
|
21
23
|
#endif
|
22
24
|
|