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 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
|
# frozen_string_literal: true
require "liquid/c/version"
require "liquid"
require "liquid_c"
require "liquid/c/compile_ext"
Liquid::C::BlockBody.class_eval do
def render(context)
render_to_output_buffer(context, +"")
end
end
module Liquid
BlockBody.class_eval do
def self.c_rescue_render_node(context, output, line_number, exc, blank_tag)
# There seems to be a MRI ruby bug with how the rb_rescue C function,
# where $! gets set for its rescue callback, but it doesn't stay set
# after a nested exception is raised and handled as is the case in
# Liquid::Context#internal_error. This isn't a problem for plain ruby code,
# so use a ruby rescue block to have setup $! properly.
raise(exc)
rescue => exc
rescue_render_node(context, output, line_number, exc, blank_tag)
end
end
end
module Liquid
module C
# Placeholder for variables in the Liquid::C::BlockBody#nodelist.
class VariablePlaceholder
class << self
private :new
end
end
class Tokenizer
MAX_SOURCE_BYTE_SIZE = (1 << 24) - 1
end
end
end
Liquid::Raw.class_eval do
alias_method :ruby_parse, :parse
def parse(tokenizer)
if parse_context.liquid_c_nodes_disabled?
ruby_parse(tokenizer)
else
c_parse(tokenizer)
end
end
end
Liquid::ParseContext.class_eval do
class << self
attr_accessor :liquid_c_nodes_disabled
end
self.liquid_c_nodes_disabled = false
alias_method :ruby_new_block_body, :new_block_body
def new_block_body
if liquid_c_nodes_disabled?
ruby_new_block_body
else
Liquid::C::BlockBody.new(self)
end
end
alias_method :ruby_new_tokenizer, :new_tokenizer
def new_tokenizer(source, start_line_number: nil, for_liquid_tag: false)
unless liquid_c_nodes_disabled?
source = source.to_s.to_str
if source.bytesize <= Liquid::C::Tokenizer::MAX_SOURCE_BYTE_SIZE
source = source.encode(Encoding::UTF_8)
return Liquid::C::Tokenizer.new(source, start_line_number || 0, for_liquid_tag)
else
@liquid_c_nodes_disabled = true
end
end
ruby_new_tokenizer(source, start_line_number: start_line_number, for_liquid_tag: for_liquid_tag)
end
def parse_expression(markup)
if liquid_c_nodes_disabled?
Liquid::Expression.parse(markup)
else
Liquid::C::Expression.lax_parse(markup)
end
end
# @api private
def liquid_c_nodes_disabled?
# 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.
# Also, some templates are parsed before the profiler is running, on which case we
# provide the `disable_liquid_c_nodes` option to enable the Ruby AST to be produced
# so the profiler can use it on future runs.
return @liquid_c_nodes_disabled if defined?(@liquid_c_nodes_disabled)
@liquid_c_nodes_disabled = !Liquid::C.enabled || @template_options[:profile] ||
@template_options[:disable_liquid_c_nodes] || self.class.liquid_c_nodes_disabled
end
end
module Liquid
module C
module DocumentClassPatch
def parse(tokenizer, parse_context)
if tokenizer.is_a?(Liquid::C::Tokenizer)
if parse_context[:bug_compatible_whitespace_trimming]
# Temporary to test rollout of the fix for this bug
tokenizer.bug_compatible_whitespace_trimming!
end
begin
parse_context.start_liquid_c_parsing
super
ensure
parse_context.cleanup_liquid_c_parsing
end
else
super
end
end
end
Liquid::Document.singleton_class.prepend(DocumentClassPatch)
end
end
Liquid::Variable.class_eval do
class << self
# @api private
def call_variable_fallback_stats_callback(parse_context)
callbacks = parse_context[:stats_callbacks]
callbacks[:variable_fallback]&.call if callbacks
end
private
# helper method for C code
def rescue_strict_parse_syntax_error(error, markup, parse_context)
error.line_number = parse_context.line_number
error.markup_context = "in \"{{#{markup}}}\""
case parse_context.error_mode
when :strict
raise
when :warn
parse_context.warnings << error
end
call_variable_fallback_stats_callback(parse_context)
lax_parse(markup, parse_context) # Avoid redundant strict parse
end
def lax_parse(markup, parse_context)
old_error_mode = parse_context.error_mode
begin
set_error_mode(parse_context, :lax)
new(markup, parse_context)
ensure
set_error_mode(parse_context, old_error_mode)
end
end
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.liquid_c_nodes_disabled?
ruby_strict_parse(markup)
else
c_strict_parse(markup)
end
end
end
Liquid::StrainerTemplate.class_eval do
class << self
private
def filter_methods_hash
@filter_methods_hash ||= {}.tap do |hash|
filter_methods.each do |method_name|
hash[method_name.to_sym] = true
end
end
end
# Convert wrong number of argument error into a liquid exception to
# treat it as an error in the template, not an internal error.
def arg_exc_to_liquid_exc(argument_error)
raise Liquid::ArgumentError, argument_error.message, argument_error.backtrace
rescue Liquid::ArgumentError => liquid_exc
liquid_exc
end
end
end
Liquid::C::Expression.class_eval do
class << self
def lax_parse(markup)
strict_parse(markup)
rescue Liquid::SyntaxError
Liquid::Expression.parse(markup)
end
# Default to strict parsing, since Liquid::C::Expression.parse should only really
# be used with constant expressions. Otherwise, prefer parse_context.parse_expression.
alias_method :parse, :strict_parse
end
end
Liquid::Context.class_eval do
alias_method :ruby_parse_evaluate, :[]
alias_method :ruby_evaluate, :evaluate
alias_method :ruby_find_variable, :find_variable
alias_method :ruby_strict_variables=, :strict_variables=
# This isn't entered often by Ruby (most calls stay in C via VariableLookup#evaluate)
# so the wrapper method isn't costly.
def c_find_variable_kwarg(key, raise_on_not_found: true)
c_find_variable(key, raise_on_not_found)
end
def c_parse_evaluate(expression)
c_evaluate(Liquid::C::Expression.lax_parse(expression))
end
end
Liquid::ResourceLimits.class_eval do
def self.new(limits)
if Liquid::C.enabled
Liquid::C::ResourceLimits.new(
limits[:render_length_limit],
limits[:render_score_limit],
limits[:assign_score_limit]
)
else
super
end
end
end
module Liquid
module C
class << self
attr_reader :enabled
def enabled=(value)
@enabled = value
if value
Liquid::Context.send(:alias_method, :[], :c_parse_evaluate)
Liquid::Context.send(:alias_method, :evaluate, :c_evaluate)
Liquid::Context.send(:alias_method, :find_variable, :c_find_variable_kwarg)
Liquid::Context.send(:alias_method, :strict_variables=, :c_strict_variables=)
else
Liquid::Context.send(:alias_method, :[], :ruby_parse_evaluate)
Liquid::Context.send(:alias_method, :evaluate, :ruby_evaluate)
Liquid::Context.send(:alias_method, :find_variable, :ruby_find_variable)
Liquid::Context.send(:alias_method, :strict_variables=, :ruby_strict_variables=)
end
end
end
self.enabled = true
end
end
|