File: context.c

package info (click to toggle)
ruby-liquid-c 4.2.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 504 kB
  • sloc: ansic: 3,866; ruby: 1,151; makefile: 7
file content (233 lines) | stat: -rw-r--r-- 9,207 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#include "liquid.h"
#include "context.h"
#include "variable_lookup.h"
#include "variable.h"
#include "liquid_vm.h"
#include "expression.h"
#include "document_body.h"

static VALUE cLiquidUndefinedVariable;
ID id_aset, id_set_context;
static ID id_has_key, id_aref, id_strainer, id_filter_methods_hash, id_strict_filters, id_global_filter;
static ID id_ivar_scopes, id_ivar_environments, id_ivar_static_environments, id_ivar_strict_variables, id_ivar_interrupts, id_ivar_resource_limits, id_ivar_document_body;

void context_internal_init(VALUE context_obj, context_t *context)
{
    context->self = context_obj;

    context->environments = rb_ivar_get(context_obj, id_ivar_environments);
    Check_Type(context->environments, T_ARRAY);

    context->static_environments = rb_ivar_get(context_obj, id_ivar_static_environments);
    Check_Type(context->static_environments, T_ARRAY);

    context->scopes = rb_ivar_get(context_obj, id_ivar_scopes);
    Check_Type(context->scopes, T_ARRAY);

    context->strainer = rb_funcall(context->self, id_strainer, 0);
    Check_Type(context->strainer, T_OBJECT);

    context->filter_methods = rb_funcall(RBASIC_CLASS(context->strainer), id_filter_methods_hash, 0);
    Check_Type(context->filter_methods, T_HASH);

    context->interrupts = rb_ivar_get(context->self, id_ivar_interrupts);
    Check_Type(context->interrupts, T_ARRAY);

    context->resource_limits_obj = rb_ivar_get(context->self, id_ivar_resource_limits);;
    ResourceLimits_Get_Struct(context->resource_limits_obj, context->resource_limits);

    context->strict_variables = false;
    context->strict_filters = RTEST(rb_funcall(context->self, id_strict_filters, 0));
    context->global_filter = rb_funcall(context->self, id_global_filter, 0);
}

void context_mark(context_t *context)
{
    rb_gc_mark(context->self);
    rb_gc_mark(context->environments);
    rb_gc_mark(context->static_environments);
    rb_gc_mark(context->scopes);
    rb_gc_mark(context->strainer);
    rb_gc_mark(context->filter_methods);
    rb_gc_mark(context->interrupts);
    rb_gc_mark(context->resource_limits_obj);
    rb_gc_mark(context->global_filter);
}

static context_t *context_from_obj(VALUE self)
{
    return &vm_from_context(self)->context;
}

static VALUE context_evaluate(VALUE self, VALUE expression)
{
    // Scalar type stored directly in the VALUE, this needs to be checked anyways to use RB_BUILTIN_TYPE
    if (RB_SPECIAL_CONST_P(expression))
        return expression;

    switch (RB_BUILTIN_TYPE(expression)) {
        case T_DATA:
        {
            if (RTYPEDDATA_P(expression) && RTYPEDDATA_TYPE(expression) == &expression_data_type) {
                if (RBASIC_CLASS(expression) == cLiquidCExpression) {
                    return internal_expression_evaluate(DATA_PTR(expression), self);
                } else {
                    assert(RBASIC_CLASS(expression) == cLiquidCVariableExpression);
                    return internal_variable_expression_evaluate(DATA_PTR(expression), self);
                }
            }
            break; // e.g. BigDecimal
        }
        case T_OBJECT: // may be Liquid::VariableLookup or Liquid::RangeLookup
        {
            VALUE result = rb_check_funcall(expression, id_evaluate, 1, &self);
            return RB_LIKELY(result != Qundef) ? result : expression;
        }
        default:
            break;
    }
    return expression;
}

void context_maybe_raise_undefined_variable(VALUE self, VALUE key)
{
    context_t *context = context_from_obj(self);
    if (context->strict_variables) {
        Check_Type(key, T_STRING);
        rb_enc_raise(utf8_encoding, cLiquidUndefinedVariable, "undefined variable %s", RSTRING_PTR(key));
    }
}

static bool environments_find_variable(VALUE environments, VALUE key, bool strict_variables, VALUE raise_on_not_found, VALUE *scope_out, VALUE *variable_out) {
    VALUE variable = Qnil;
    Check_Type(environments, T_ARRAY);

    for (long i = 0; i < RARRAY_LEN(environments); i++) {
        VALUE this_environ = RARRAY_AREF(environments, i);
        if (RB_LIKELY(TYPE(this_environ) == T_HASH)) {
            // Does not invoke any default value proc, this is equivalent in
            // cost and semantics to #key? but loads the value as well
            variable = rb_hash_lookup2(this_environ, key, Qundef);
            if (variable != Qundef) {
                *variable_out = variable;
                *scope_out = this_environ;
                return true;
            }

            if (!(RTEST(raise_on_not_found) && strict_variables)) {
                // If we aren't running strictly, we need to invoke the default
                // value proc, rb_hash_aref does this
                variable = rb_hash_aref(this_environ, key);
                if (variable != Qnil) {
                    *variable_out = variable;
                    *scope_out = this_environ;
                    return true;
                }
            }
        } else if (RTEST(rb_funcall(this_environ, id_has_key, 1, key))) {
            // Slow path: It is valid to pass a non-hash value to Liquid as an
            // environment if it supports #key? and #[]
            *variable_out = rb_funcall(this_environ, id_aref, 1, key);
            *scope_out = this_environ;
            return true;
        }
    }
    return false;
}

VALUE context_find_variable(context_t *context, VALUE key, VALUE raise_on_not_found)
{
    VALUE self = context->self;
    VALUE scope = Qnil, variable = Qnil;

    VALUE scopes = context->scopes;
    for (long i = 0; i < RARRAY_LEN(scopes); i++) {
        VALUE this_scope = RARRAY_AREF(scopes, i);
        if (RB_LIKELY(TYPE(this_scope) == T_HASH)) {
            // Does not invoke any default value proc, this is equivalent in
            // cost and semantics to #key? but loads the value as well
            variable = rb_hash_lookup2(this_scope, key, Qundef);
            if (variable != Qundef) {
                scope = this_scope;
                goto variable_found;
            }
        } else if (RTEST(rb_funcall(this_scope, id_has_key, 1, key))) {
            // Slow path: It is valid to pass a non-hash value to Liquid as a
            // scope if it supports #key? and #[]
            variable = rb_funcall(this_scope, id_aref, 1, key);
            goto variable_found;
        }
    }

    if (environments_find_variable(context->environments, key, context->strict_variables, raise_on_not_found,
                                   &scope, &variable))
        goto variable_found;

    if (environments_find_variable(context->static_environments, key, context->strict_variables, raise_on_not_found,
                                   &scope, &variable))
        goto variable_found;

    if (RTEST(raise_on_not_found)) {
        context_maybe_raise_undefined_variable(self, key);
    }
    variable = Qnil;

variable_found:
    variable = materialize_proc(self, scope, key, variable);
    variable = value_to_liquid_and_set_context(variable, self);

    return variable;
}

static VALUE context_find_variable_method(VALUE self, VALUE key, VALUE raise_on_not_found)
{
    return context_find_variable(context_from_obj(self), key, raise_on_not_found);
}

static VALUE context_set_strict_variables(VALUE self, VALUE strict_variables)
{
    context_t *context = context_from_obj(self);
    context->strict_variables = RTEST(strict_variables);
    rb_ivar_set(self, id_ivar_strict_variables, strict_variables);
    return Qnil;
}

// Shopify requires checking if we are filtering, so provide a
// way to do that in liquid-c until we figure out how we want to
// support that longer term.
VALUE context_filtering_p(VALUE self)
{
    return liquid_vm_filtering(self) ? Qtrue : Qfalse;
}

void liquid_define_context(void)
{
    id_has_key = rb_intern("key?");
    id_aset = rb_intern("[]=");
    id_aref = rb_intern("[]");
    id_set_context = rb_intern("context=");
    id_strainer = rb_intern("strainer");
    id_filter_methods_hash = rb_intern("filter_methods_hash");
    id_strict_filters = rb_intern("strict_filters");
    id_global_filter = rb_intern("global_filter");

    id_ivar_scopes = rb_intern("@scopes");
    id_ivar_environments = rb_intern("@environments");
    id_ivar_static_environments = rb_intern("@static_environments");
    id_ivar_strict_variables = rb_intern("@strict_variables");
    id_ivar_interrupts = rb_intern("@interrupts");
    id_ivar_resource_limits = rb_intern("@resource_limits");
    id_ivar_document_body = rb_intern("@document_body");

    cLiquidVariableLookup = rb_const_get(mLiquid, rb_intern("VariableLookup"));
    rb_global_variable(&cLiquidVariableLookup);

    cLiquidUndefinedVariable = rb_const_get(mLiquid, rb_intern("UndefinedVariable"));
    rb_global_variable(&cLiquidUndefinedVariable);

    VALUE cLiquidContext = rb_const_get(mLiquid, rb_intern("Context"));
    rb_define_method(cLiquidContext, "c_evaluate", context_evaluate, 1);
    rb_define_method(cLiquidContext, "c_find_variable", context_find_variable_method, 2);
    rb_define_method(cLiquidContext, "c_strict_variables=", context_set_strict_variables, 1);
    rb_define_private_method(cLiquidContext, "c_filtering?", context_filtering_p, 0);
}