replace INTEGER_REGEX/FLOAT_REGEX with byte-level parse_number

This commit is contained in:
Tobi Lutke
2026-03-11 08:54:31 -04:00
parent 79840b1eaa
commit 69430e9a88
+58 -45
View File
@@ -78,61 +78,74 @@ module Liquid
end end
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 # Quick reject: first byte must be digit or dash
first = markup.getbyte(0) pos = 0
return false if first != DASH && (first < ZERO || first > NINE) 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 # Scan digits
case markup while pos < len
when INTEGER_REGEX 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) return Integer(markup, 10)
when FLOAT_REGEX end
# 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
if pos > digit_after_dot && pos == len
# Simple float like "123.456"
return markup.to_f 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 end
pos += 1
ss.string = markup
# the first byte must be a digit or a dash
byte = ss.scan_byte
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 end
return markup.byteslice(0, pos).to_f
# 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 else
# we found another dot, so we know that the number ends here # dot at end: "123."
num_end_pos = ss.pos - 1 return markup.byteslice(0, dot_pos).to_f
end
end end
end end
num_end_pos = markup.length if ss.eos? # Not a number (has non-digit, non-dot characters)
false
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
end end
end end
end end