mirror of
https://github.com/Shopify/liquid.git
synced 2026-10-03 00:55:11 -07:00
add StringScannerPool for thread safety
This commit is contained in:
@@ -49,6 +49,7 @@ end
|
|||||||
require "liquid/version"
|
require "liquid/version"
|
||||||
require "liquid/deprecations"
|
require "liquid/deprecations"
|
||||||
require "liquid/const"
|
require "liquid/const"
|
||||||
|
require "liquid/string_scanner_pool"
|
||||||
require 'liquid/standardfilters'
|
require 'liquid/standardfilters'
|
||||||
require 'liquid/file_system'
|
require 'liquid/file_system'
|
||||||
require 'liquid/parser_switching'
|
require 'liquid/parser_switching'
|
||||||
|
|||||||
@@ -72,10 +72,6 @@ module Liquid
|
|||||||
CACHE = LruRedux::Cache.new(10_000) # most themes would have less than 2,000 unique expression
|
CACHE = LruRedux::Cache.new(10_000) # most themes would have less than 2,000 unique expression
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def string_scanner
|
|
||||||
@ss ||= StringScanner.new("")
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse(markup)
|
def parse(markup)
|
||||||
return unless markup
|
return unless markup
|
||||||
|
|
||||||
@@ -106,8 +102,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parse_number(markup)
|
def parse_number(markup)
|
||||||
ss = string_scanner
|
ss = StringScannerPool.pop(markup)
|
||||||
ss.string = markup
|
|
||||||
|
|
||||||
is_integer = true
|
is_integer = true
|
||||||
last_dot_pos = nil
|
last_dot_pos = nil
|
||||||
@@ -147,6 +142,8 @@ module Liquid
|
|||||||
# we should never reach this point
|
# we should never reach this point
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
ensure
|
||||||
|
StringScannerPool.release(ss)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+27
-31
@@ -157,40 +157,34 @@ module Liquid
|
|||||||
table.freeze
|
table.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
class << self
|
|
||||||
def string_scanner
|
|
||||||
@string_scanner ||= StringScanner.new("")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(input)
|
def initialize(input)
|
||||||
@ss = self.class.string_scanner
|
@input = input
|
||||||
@ss.string = input
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop:disable Metrics/BlockNesting
|
# rubocop:disable Metrics/BlockNesting
|
||||||
def tokenize
|
def tokenize
|
||||||
|
ss = StringScannerPool.pop(@input)
|
||||||
@output = []
|
@output = []
|
||||||
|
|
||||||
until @ss.eos?
|
until ss.eos?
|
||||||
@ss.skip(WHITESPACE_OR_NOTHING)
|
ss.skip(WHITESPACE_OR_NOTHING)
|
||||||
|
|
||||||
break if @ss.eos?
|
break if ss.eos?
|
||||||
|
|
||||||
start_pos = @ss.pos
|
start_pos = ss.pos
|
||||||
peeked = @ss.peek_byte
|
peeked = ss.peek_byte
|
||||||
|
|
||||||
if (special = SPECIAL_TABLE[peeked])
|
if (special = SPECIAL_TABLE[peeked])
|
||||||
@ss.scan_byte
|
ss.scan_byte
|
||||||
# Special case for ".."
|
# Special case for ".."
|
||||||
if special == DOT && @ss.peek_byte == DOT_ORD
|
if special == DOT && ss.peek_byte == DOT_ORD
|
||||||
@ss.scan_byte
|
ss.scan_byte
|
||||||
@output << DOTDOT
|
@output << DOTDOT
|
||||||
elsif special == DASH
|
elsif special == DASH
|
||||||
# Special case for negative numbers
|
# Special case for negative numbers
|
||||||
if (peeked_byte = @ss.peek_byte) && NUMBER_TABLE[peeked_byte]
|
if (peeked_byte = ss.peek_byte) && NUMBER_TABLE[peeked_byte]
|
||||||
@ss.pos -= 1
|
ss.pos -= 1
|
||||||
@output << [:number, @ss.scan(NUMBER_LITERAL)]
|
@output << [:number, ss.scan(NUMBER_LITERAL)]
|
||||||
else
|
else
|
||||||
@output << special
|
@output << special
|
||||||
end
|
end
|
||||||
@@ -198,25 +192,25 @@ module Liquid
|
|||||||
@output << special
|
@output << special
|
||||||
end
|
end
|
||||||
elsif (sub_table = TWO_CHARS_COMPARISON_JUMP_TABLE[peeked])
|
elsif (sub_table = TWO_CHARS_COMPARISON_JUMP_TABLE[peeked])
|
||||||
@ss.scan_byte
|
ss.scan_byte
|
||||||
if (peeked_byte = @ss.peek_byte) && (found = sub_table[peeked_byte])
|
if (peeked_byte = ss.peek_byte) && (found = sub_table[peeked_byte])
|
||||||
@output << found
|
@output << found
|
||||||
@ss.scan_byte
|
ss.scan_byte
|
||||||
else
|
else
|
||||||
raise_syntax_error(start_pos)
|
raise_syntax_error(start_pos, ss)
|
||||||
end
|
end
|
||||||
elsif (sub_table = COMPARISON_JUMP_TABLE[peeked])
|
elsif (sub_table = COMPARISON_JUMP_TABLE[peeked])
|
||||||
@ss.scan_byte
|
ss.scan_byte
|
||||||
if (peeked_byte = @ss.peek_byte) && (found = sub_table[peeked_byte])
|
if (peeked_byte = ss.peek_byte) && (found = sub_table[peeked_byte])
|
||||||
@output << found
|
@output << found
|
||||||
@ss.scan_byte
|
ss.scan_byte
|
||||||
else
|
else
|
||||||
@output << SINGLE_COMPARISON_TOKENS[peeked]
|
@output << SINGLE_COMPARISON_TOKENS[peeked]
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
type, pattern = NEXT_MATCHER_JUMP_TABLE[peeked]
|
type, pattern = NEXT_MATCHER_JUMP_TABLE[peeked]
|
||||||
|
|
||||||
if type && (t = @ss.scan(pattern))
|
if type && (t = ss.scan(pattern))
|
||||||
# Special case for "contains"
|
# Special case for "contains"
|
||||||
@output << if type == :id && t == "contains" && @output.last&.first != :dot
|
@output << if type == :id && t == "contains" && @output.last&.first != :dot
|
||||||
COMPARISON_CONTAINS
|
COMPARISON_CONTAINS
|
||||||
@@ -224,19 +218,21 @@ module Liquid
|
|||||||
[type, t]
|
[type, t]
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise_syntax_error(start_pos)
|
raise_syntax_error(start_pos, ss)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/BlockNesting
|
# rubocop:enable Metrics/BlockNesting
|
||||||
|
|
||||||
@output << EOS
|
@output << EOS
|
||||||
|
ensure
|
||||||
|
StringScannerPool.release(ss)
|
||||||
end
|
end
|
||||||
|
|
||||||
def raise_syntax_error(start_pos)
|
def raise_syntax_error(start_pos, ss)
|
||||||
@ss.pos = start_pos
|
ss.pos = start_pos
|
||||||
# the character could be a UTF-8 character, use getch to get all the bytes
|
# the character could be a UTF-8 character, use getch to get all the bytes
|
||||||
raise SyntaxError, "Unexpected character #{@ss.getch}"
|
raise SyntaxError, "Unexpected character #{ss.getch}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
module Liquid
|
||||||
|
class StringScannerPool
|
||||||
|
class << self
|
||||||
|
def pop(input)
|
||||||
|
@ss_pool ||= [StringScanner.new("")] * 5
|
||||||
|
|
||||||
|
if @ss_pool.empty?
|
||||||
|
StringScanner.new(input)
|
||||||
|
else
|
||||||
|
ss = @ss_pool.pop
|
||||||
|
ss.string = input
|
||||||
|
ss
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def release(ss)
|
||||||
|
binding.irb if ss.nil?
|
||||||
|
@ss_pool ||= []
|
||||||
|
@ss_pool << ss
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -56,12 +56,6 @@ module Liquid
|
|||||||
CLOSE_CURLEY = "}".ord
|
CLOSE_CURLEY = "}".ord
|
||||||
PERCENTAGE = "%".ord
|
PERCENTAGE = "%".ord
|
||||||
|
|
||||||
class << self
|
|
||||||
def string_scanner
|
|
||||||
@string_scanner ||= StringScanner.new("")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
|
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
|
||||||
@line_number = line_number || (line_numbers ? 1 : nil)
|
@line_number = line_number || (line_numbers ? 1 : nil)
|
||||||
@for_liquid_tag = for_liquid_tag
|
@for_liquid_tag = for_liquid_tag
|
||||||
@@ -91,13 +85,13 @@ module Liquid
|
|||||||
if @for_liquid_tag
|
if @for_liquid_tag
|
||||||
@tokens = @source.split("\n")
|
@tokens = @source.split("\n")
|
||||||
else
|
else
|
||||||
@ss = self.class.string_scanner
|
@ss = StringScannerPool.pop(@source)
|
||||||
@ss.string = @source
|
|
||||||
@tokens << shift_normal until @ss.eos?
|
@tokens << shift_normal until @ss.eos?
|
||||||
end
|
end
|
||||||
|
|
||||||
@ss = nil
|
|
||||||
@source = nil
|
@source = nil
|
||||||
|
ensure
|
||||||
|
StringScannerPool.release(@ss) if @ss
|
||||||
end
|
end
|
||||||
|
|
||||||
def shift_normal
|
def shift_normal
|
||||||
|
|||||||
Reference in New Issue
Block a user