diff --git a/lib/liquid/expression.rb b/lib/liquid/expression.rb index 9a48952a..981946ef 100644 --- a/lib/liquid/expression.rb +++ b/lib/liquid/expression.rb @@ -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