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
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
return markup.to_f
end end
ss.string = markup # Check for dot (float)
# the first byte must be a digit or a dash if markup.getbyte(pos) == DOT
byte = ss.scan_byte 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 pos > digit_after_dot && pos == len
# Simple float like "123.456"
if byte == DASH return markup.to_f
peek_byte = ss.peek_byte elsif pos > digit_after_dot
# Float followed by more dots or other chars: "1.2.3.4"
# if it starts with a dash, the next byte must be a digit # Return the float portion up to second dot
return false if peek_byte.nil? || !(peek_byte >= ZERO && peek_byte <= NINE) first_dot_pos = dot_pos + 1
end while pos < len
b = markup.getbyte(pos)
# The markup could be a float with multiple dots if b == DOT
first_dot_pos = nil return markup.byteslice(0, pos).to_f
num_end_pos = nil elsif b < ZERO || b > NINE
return false
while (byte = ss.scan_byte) end
return false if byte != DOT && (byte < ZERO || byte > NINE) pos += 1
# 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
end end
return markup.byteslice(0, pos).to_f
else
# dot at end: "123."
return markup.byteslice(0, dot_pos).to_f
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