For tag: migrate lax_parse to Cursor with zero-alloc skip_id/expect_id

This commit is contained in:
Tobi Lutke
2026-03-11 09:19:59 -04:00
parent bf1f5cb62d
commit cdc34388e3
+29 -55
View File
@@ -76,73 +76,47 @@ module Liquid
REVERSED_BYTES = "reversed".bytes.freeze REVERSED_BYTES = "reversed".bytes.freeze
def lax_parse(markup) def lax_parse(markup)
# Try fast manual parse first c = @parse_context.cursor
len = markup.bytesize c.reset(markup)
pos = 0 c.skip_ws
# Skip whitespace # Parse variable name
pos += 1 while pos < len && (b = markup.getbyte(pos)) && (b == 32 || b == 9) var_start = c.pos
var_len = c.skip_id
raise SyntaxError, options[:locale].t("errors.syntax.for") if var_len == 0
@variable_name = c.slice(var_start, var_len)
# Parse variable name: [\w-]+ # Expect "in"
var_start = pos c.skip_ws
while pos < len raise SyntaxError, options[:locale].t("errors.syntax.for") unless c.expect_id("in")
b = markup.getbyte(pos) c.skip_ws
break unless (b >= 97 && b <= 122) || (b >= 65 && b <= 90) || (b >= 48 && b <= 57) || b == 95 || b == 45
pos += 1
end
if pos == var_start # Parse collection name
raise SyntaxError, options[:locale].t("errors.syntax.for") col_start = c.pos
end if c.peek_byte == Cursor::LPAREN
# Parenthesized range: (1..10)
@variable_name = markup.byteslice(var_start, pos - var_start)
# Expect whitespace + "in" + whitespace
pos += 1 while pos < len && markup.getbyte(pos) == 32
unless pos + 1 < len && markup.getbyte(pos) == 105 && markup.getbyte(pos + 1) == 110 # 'i', 'n'
raise SyntaxError, options[:locale].t("errors.syntax.for")
end
pos += 2
pos += 1 while pos < len && markup.getbyte(pos) == 32
# Parse collection name (QuotedFragment - take everything until whitespace)
col_start = pos
# Handle parenthesized ranges: (1..10)
if pos < len && markup.getbyte(pos) == 40 # '('
depth = 1 depth = 1
pos += 1 c.scan_byte
while pos < len && depth > 0 while !c.eos? && depth > 0
b = markup.getbyte(pos) b = c.scan_byte
depth += 1 if b == 40 depth += 1 if b == Cursor::LPAREN
depth -= 1 if b == 41 depth -= 1 if b == Cursor::RPAREN
pos += 1
end end
else else
while pos < len c.skip_fragment
b = markup.getbyte(pos)
break if b == 32 || b == 9
pos += 1
end
end end
collection_name = markup.byteslice(col_start, pos - col_start) collection_name = c.slice(col_start, c.pos - col_start)
@name = "#{@variable_name}-#{collection_name}" @name = "#{@variable_name}-#{collection_name}"
@collection_name = parse_expression(collection_name) @collection_name = parse_expression(collection_name)
# Skip whitespace c.skip_ws
pos += 1 while pos < len && markup.getbyte(pos) == 32 @reversed = c.expect_id("reversed")
c.skip_ws
# Check for 'reversed'
@reversed = false
if pos + 7 < len && markup.byteslice(pos, 8) == "reversed"
@reversed = true
pos += 8
pos += 1 while pos < len && markup.getbyte(pos) == 32
end
# Parse limit:/offset: if present # Parse limit:/offset: if present
if pos < len && markup.include?(':') if !c.eos? && markup.include?(':')
rest = markup.byteslice(pos, len - pos) rest = c.slice(c.pos, markup.bytesize - c.pos)
rest.scan(TagAttributes) do |key, value| rest.scan(TagAttributes) do |key, value|
set_attribute(key, value) set_attribute(key, value)
end end