replace INTEGER_REGEX/FLOAT_REGEX with byte-level parse_number

This commit is contained in:
Tobi Lutke
2026-04-04 17:42:32 -07:00
committed by Chris Pak
parent c6617accc5
commit 1f309b19a7
+58 -45
View File
@@ -78,61 +78,74 @@ module Liquid
end
end
def parse_number(markup, ss)
def parse_number(markup, _ss = nil)
len = markup.bytesize
return false if len == 0
# Quick reject: first byte must be digit or dash
first = markup.getbyte(0)
return false if first != DASH && (first < ZERO || first > NINE)
pos = 0
first = markup.getbyte(pos)
if first == DASH
pos += 1
return false if pos >= len
b = markup.getbyte(pos)
return false if b < ZERO || b > NINE
pos += 1
elsif first >= ZERO && first <= NINE
pos += 1
else
return false
end
# check if the markup is simple integer or float
case markup
when INTEGER_REGEX
# Scan digits
while pos < len
b = markup.getbyte(pos)
break unless b >= ZERO && b <= NINE
pos += 1
end
# If we consumed everything, it's a simple integer
if pos == len
return Integer(markup, 10)
when FLOAT_REGEX
return markup.to_f
end
ss.string = markup
# the first byte must be a digit or a dash
byte = ss.scan_byte
# Check for dot (float)
if markup.getbyte(pos) == DOT
dot_pos = pos
pos += 1
# Must have at least one digit after dot
digit_after_dot = pos
while pos < len
b = markup.getbyte(pos)
break unless b >= ZERO && b <= NINE
pos += 1
end
return false if byte != DASH && (byte < ZERO || byte > NINE)
if byte == DASH
peek_byte = ss.peek_byte
# if it starts with a dash, the next byte must be a digit
return false if peek_byte.nil? || !(peek_byte >= ZERO && peek_byte <= NINE)
end
# The markup could be a float with multiple dots
first_dot_pos = nil
num_end_pos = nil
while (byte = ss.scan_byte)
return false if byte != DOT && (byte < ZERO || byte > NINE)
# we found our number and now we are just scanning the rest of the string
next if num_end_pos
if byte == DOT
if first_dot_pos.nil?
first_dot_pos = ss.pos
else
# we found another dot, so we know that the number ends here
num_end_pos = ss.pos - 1
if pos > digit_after_dot && pos == len
# Simple float like "123.456"
return markup.to_f
elsif pos > digit_after_dot
# Float followed by more dots or other chars: "1.2.3.4"
# Return the float portion up to second dot
first_dot_pos = dot_pos + 1
while pos < len
b = markup.getbyte(pos)
if b == DOT
return markup.byteslice(0, pos).to_f
elsif b < ZERO || b > NINE
return false
end
pos += 1
end
return markup.byteslice(0, pos).to_f
else
# dot at end: "123."
return markup.byteslice(0, dot_pos).to_f
end
end
num_end_pos = markup.length if ss.eos?
if num_end_pos
# number ends with a number "123.123"
markup.byteslice(0, num_end_pos).to_f
else
# number ends with a dot "123."
markup.byteslice(0, first_dot_pos).to_f
end
# Not a number (has non-digit, non-dot characters)
false
end
end
end