diff --git a/ext/liquid_c/block.c b/ext/liquid_c/block.c index c3d51da5..eeed694b 100644 --- a/ext/liquid_c/block.c +++ b/ext/liquid_c/block.c @@ -142,11 +142,11 @@ static tag_markup_t internal_block_body_parse(block_body_t *body, parse_context_ variable_parse_args_t parse_args = { .markup = token.str_trimmed, .markup_end = token.str_trimmed + token.len_trimmed, - .body = body, + .code = &body->code, + .code_obj = body->obj, .parse_context = parse_context->ruby_obj, - .line_number = token_start_line_number, }; - internal_variable_parse(&parse_args); + internal_variable_compile(&parse_args, token_start_line_number); render_score_increment += 1; body->blank = false; break; diff --git a/ext/liquid_c/variable.c b/ext/liquid_c/variable.c index 71bd5e8f..6c93b3e0 100644 --- a/ext/liquid_c/variable.c +++ b/ext/liquid_c/variable.c @@ -6,17 +6,23 @@ static ID id_rescue_strict_parse_syntax_error; +static ID id_ivar_parse_context; +static ID id_ivar_name; +static ID id_ivar_filters; + +static VALUE frozen_empty_array; + static VALUE try_variable_strict_parse(VALUE uncast_args) { variable_parse_args_t *parse_args = (void *)uncast_args; parser_t p; init_parser(&p, parse_args->markup, parse_args->markup_end); - vm_assembler_t *code = &parse_args->body->code; + vm_assembler_t *code = parse_args->code; - if (p.cur.type == TOKEN_EOS) + if (p.cur.type == TOKEN_EOS) { + vm_assembler_add_push_nil(code); return Qnil; - - vm_assembler_add_render_variable_rescue(code, parse_args->line_number); + } parse_and_compile_expression(&p, code); @@ -70,8 +76,6 @@ static VALUE try_variable_strict_parse(VALUE uncast_args) vm_assembler_add_filter(code, filter_name, arg_count); } - vm_assembler_add_pop_write(code); - parser_must_consume(&p, TOKEN_EOS); return Qnil; @@ -88,7 +92,7 @@ static VALUE variable_strict_parse_rescue(VALUE uncast_args, VALUE exception) { variable_strict_parse_rescue_t *rescue_args = (void *)uncast_args; variable_parse_args_t *parse_args = rescue_args->parse_args; - vm_assembler_t *code = &parse_args->body->code; + vm_assembler_t *code = parse_args->code; // undo partial strict parse code->instructions.data_end = code->instructions.data + rescue_args->instructions_size; @@ -106,19 +110,17 @@ static VALUE variable_strict_parse_rescue(VALUE uncast_args, VALUE exception) // lax parse code->protected_stack_size = code->stack_size; - vm_assembler_add_render_variable_rescue(code, parse_args->line_number); - rb_funcall(variable_obj, id_compile_evaluate, 1, parse_args->body->obj); + rb_funcall(variable_obj, id_compile_evaluate, 1, parse_args->code_obj); if (code->stack_size != code->protected_stack_size + 1) { rb_raise(rb_eRuntimeError, "Liquid::Variable#compile_evaluate didn't leave exactly 1 new element on the stack"); } - vm_assembler_add_pop_write(code); return Qnil; } -void internal_variable_parse(variable_parse_args_t *parse_args) +void internal_variable_compile_evaluate(variable_parse_args_t *parse_args) { - vm_assembler_t *code = &parse_args->body->code; + vm_assembler_t *code = parse_args->code; variable_strict_parse_rescue_t rescue_args = { .parse_args = parse_args, .instructions_size = c_buffer_size(&code->instructions), @@ -128,8 +130,54 @@ void internal_variable_parse(variable_parse_args_t *parse_args) rb_rescue(try_variable_strict_parse, (VALUE)parse_args, variable_strict_parse_rescue, (VALUE)&rescue_args); } +void internal_variable_compile(variable_parse_args_t *parse_args, unsigned int line_number) +{ + vm_assembler_t *code = parse_args->code; + vm_assembler_add_render_variable_rescue(code, line_number); + internal_variable_compile_evaluate(parse_args); + vm_assembler_add_pop_write(code); +} + +static VALUE variable_strict_parse_method(VALUE self, VALUE markup) +{ + StringValue(markup); + check_utf8_encoding(markup, "markup"); + + VALUE parse_context = rb_ivar_get(self, id_ivar_parse_context); + + expression_t *expression; + VALUE expression_obj = expression_new(&expression); + + variable_parse_args_t parse_args = { + .markup = RSTRING_PTR(markup), + .markup_end = RSTRING_END(markup), + .code = &expression->code, + .code_obj = expression_obj, + .parse_context = parse_context, + }; + try_variable_strict_parse((VALUE)&parse_args); + RB_GC_GUARD(markup); + assert(expression->code.stack_size == 1); + vm_assembler_add_leave(&expression->code); + + rb_ivar_set(self, id_ivar_name, expression_obj); + rb_ivar_set(self, id_ivar_filters, frozen_empty_array); + + return Qnil; +} + void init_liquid_variable(void) { id_rescue_strict_parse_syntax_error = rb_intern("rescue_strict_parse_syntax_error"); + + id_ivar_parse_context = rb_intern("@parse_context"); + id_ivar_name = rb_intern("@name"); + id_ivar_filters = rb_intern("@filters"); + + frozen_empty_array = rb_ary_new(); + rb_ary_freeze(frozen_empty_array); + rb_global_variable(&frozen_empty_array); + + rb_define_method(cLiquidVariable, "c_strict_parse", variable_strict_parse_method, 1); } diff --git a/ext/liquid_c/variable.h b/ext/liquid_c/variable.h index 62c548f4..b39e2563 100644 --- a/ext/liquid_c/variable.h +++ b/ext/liquid_c/variable.h @@ -7,13 +7,14 @@ typedef struct variable_parse_args { const char *markup; const char *markup_end; - block_body_t *body; + vm_assembler_t *code; + VALUE code_obj; VALUE parse_context; - unsigned int line_number; } variable_parse_args_t; void init_liquid_variable(void); -void internal_variable_parse(variable_parse_args_t *parse_args); +void internal_variable_compile(variable_parse_args_t *parse_args, unsigned int line_number); +void internal_variable_compile_evaluate(variable_parse_args_t *parse_args); #endif diff --git a/lib/liquid/c.rb b/lib/liquid/c.rb index 38f4e5f0..b23fcb93 100644 --- a/lib/liquid/c.rb +++ b/lib/liquid/c.rb @@ -68,8 +68,7 @@ def new_block_body end end - private - + # @api private def disable_liquid_c_nodes # Liquid::Profiler exposes the internal parse tree that we don't want to build when # parsing with liquid-c, so consider liquid-c to be disabled when using it. @@ -140,6 +139,16 @@ def set_error_mode(parse_context, mode) parse_context.instance_variable_set(:@error_mode, mode) end end + + alias_method :ruby_strict_parse, :strict_parse + + def strict_parse(markup) + if parse_context.disable_liquid_c_nodes + ruby_strict_parse(markup) + else + c_strict_parse(markup) + end + end end Liquid::StrainerTemplate.class_eval do