diff --git a/Tests/LibWeb/Text/expected/input-number-float.txt b/Tests/LibWeb/Text/expected/input-number-float.txt new file mode 100644 index 00000000000..01a29b1a498 --- /dev/null +++ b/Tests/LibWeb/Text/expected/input-number-float.txt @@ -0,0 +1,49 @@ +Getting values (string to number) +42 +-42 +NaN +0 +0.5 +-0.5 +0.5 +NaN +NaN +NaN +NaN +NaN +NaN +NaN +100 +0.01 +1500 +-0.0015 +NaN +NaN +NaN +NaN +NaN +NaN +0 +NaN +1e+308 +-1e+308 +NaN +NaN +Setting values (number to string) +42 +-42 +0.5 +-0.5 +100 +0.01 +1500 +-0.0015 +error +error +1e+308 +-1e+308 +error +error +0 +0 +0 diff --git a/Tests/LibWeb/Text/input/input-number-float.html b/Tests/LibWeb/Text/input/input-number-float.html new file mode 100644 index 00000000000..c2127b3b59b --- /dev/null +++ b/Tests/LibWeb/Text/input/input-number-float.html @@ -0,0 +1,84 @@ + + + diff --git a/Userland/Libraries/LibWeb/HTML/Numbers.cpp b/Userland/Libraries/LibWeb/HTML/Numbers.cpp index 111969da50a..dad6fca6454 100644 --- a/Userland/Libraries/LibWeb/HTML/Numbers.cpp +++ b/Userland/Libraries/LibWeb/HTML/Numbers.cpp @@ -83,13 +83,204 @@ Optional parse_non_negative_integer(StringView string) // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-floating-point-number-values Optional parse_floating_point_number(StringView string) { - // FIXME: Implement spec compliant floating point number parsing - auto maybe_double = string.to_number(TrimWhitespace::Yes); - if (!maybe_double.has_value()) + // 1. Let input be the string being parsed. + // 2. Let position be a pointer into input, initially pointing at the start of the string. + GenericLexer lexer { string }; + + // 3. Let value have the value 1. + double value = 1; + + // 4. Let divisor have the value 1. + double divisor = 1; + + // 5. Let exponent have the value 1. + i16 exponent = 1; + + // 6. Skip ASCII whitespace within input given position. + lexer.ignore_while(Web::Infra::is_ascii_whitespace); + + // 7. If position is past the end of input, return an error. + if (lexer.is_eof()) { return {}; - if (!isfinite(maybe_double.value())) + } + + // 8. If the character indicated by position is a U+002D HYPHEN-MINUS character (-): + if (lexer.next_is('-')) { + // 8.1. Change value and divisor to −1. + value = -1; + divisor = -1; + + // 8.2. Advance position to the next character. + lexer.consume(); + + // 8.3. If position is past the end of input, return an error. + if (lexer.is_eof()) { + return {}; + } + } + // Otherwise, if the character indicated by position (the first character) is a U+002B PLUS SIGN character (+): + else if (lexer.next_is('+')) { + // 8.1. Advance position to the next character. (The "+" is ignored, but it is not conforming.) + lexer.consume(); + + // 8.2. If position is past the end of input, return an error. + if (lexer.is_eof()) { + return {}; + } + } + + // 9. If the character indicated by position is a U+002E FULL STOP (.), + // and that is not the last character in input, + // and the character after the character indicated by position is an ASCII digit, + // then set value to zero and jump to the step labeled fraction. + if (lexer.next_is('.') && (lexer.tell_remaining() > 1) && is_ascii_digit(lexer.peek(1))) { + value = 0; + goto fraction; + } + + // 10. If the character indicated by position is not an ASCII digit, then return an error. + if (!lexer.next_is(is_ascii_digit)) { return {}; - return maybe_double.value(); + } + + // 11. Collect a sequence of code points that are ASCII digits from input given position, and interpret the resulting sequence as a base-ten integer. + // Multiply value by that integer. + { + size_t start_index = lexer.tell(); + lexer.consume_while(is_ascii_digit); + size_t end_index = lexer.tell(); + auto digits = lexer.input().substring_view(start_index, end_index - start_index); + auto optional_value = AK::StringUtils::convert_to_int(digits); + value *= optional_value.value(); + } + + // 12. If position is past the end of input, jump to the step labeled conversion. + if (lexer.is_eof()) { + goto conversion; + } + +fraction: { + // 13. Fraction: If the character indicated by position is a U+002E FULL STOP (.), run these substeps: + if (lexer.next_is('.')) { + // 13.1. Advance position to the next character. + lexer.consume(); + + // 13.2. If position is past the end of input, + // or if the character indicated by position is not an ASCII digit, + // U+0065 LATIN SMALL LETTER E (e), or U+0045 LATIN CAPITAL LETTER E (E), + // then jump to the step labeled conversion. + if (lexer.is_eof() || (!lexer.next_is(is_ascii_digit) && !lexer.next_is('e') && !lexer.next_is('E'))) { + goto conversion; + } + + // 13.3. If the character indicated by position is a U+0065 LATIN SMALL LETTER E character (e) or a U+0045 LATIN CAPITAL LETTER E character (E), + // skip the remainder of these substeps. + if (lexer.next_is('e') || lexer.next_is('E')) { + goto fraction_exit; + } + + // fraction_loop: + while (true) { + // 13.4. Fraction loop: Multiply divisor by ten. + divisor *= 10; + + // 13.5. Add the value of the character indicated by position, interpreted as a base-ten digit (0..9) and divided by divisor, to value. + value += (lexer.peek() - '0') / divisor; + + // 13.6. Advance position to the next character. + lexer.consume(); + + // 13.7. If position is past the end of input, then jump to the step labeled conversion. + if (lexer.is_eof()) { + goto conversion; + } + + // 13.8. If the character indicated by position is an ASCII digit, jump back to the step labeled fraction loop in these substeps. + if (!lexer.next_is(is_ascii_digit)) { + break; + } + } + } + +fraction_exit: +} + + // 14. If the character indicated by position is U+0065 (e) or a U+0045 (E), then: + if (lexer.next_is('e') || lexer.next_is('E')) { + // 14.1. Advance position to the next character. + lexer.consume(); + + // 14.2. If position is past the end of input, then jump to the step labeled conversion. + if (lexer.is_eof()) { + goto conversion; + } + + // 14.3. If the character indicated by position is a U+002D HYPHEN-MINUS character (-): + if (lexer.next_is('-')) { + // 14.3.1. Change exponent to −1. + exponent = -1; + + // 14.3.2. Advance position to the next character. + lexer.consume(); + + // 14.3.3. If position is past the end of input, then jump to the step labeled conversion. + if (lexer.is_eof()) { + goto conversion; + } + } + // Otherwise, if the character indicated by position is a U+002B PLUS SIGN character (+): + else if (lexer.next_is('+')) { + // 14.3.1. Advance position to the next character. + lexer.consume(); + + // 14.3.2. If position is past the end of input, then jump to the step labeled conversion. + if (lexer.is_eof()) { + goto conversion; + } + } + + // 14.4. If the character indicated by position is not an ASCII digit, then jump to the step labeled conversion. + if (!lexer.next_is(is_ascii_digit)) { + goto conversion; + } + + // 14.5. Collect a sequence of code points that are ASCII digits from input given position, and interpret the resulting sequence as a base-ten integer. + // Multiply exponent by that integer. + { + size_t start_index = lexer.tell(); + lexer.consume_while(is_ascii_digit); + size_t end_index = lexer.tell(); + auto digits = lexer.input().substring_view(start_index, end_index - start_index); + auto optional_value = AK::StringUtils::convert_to_int(digits); + exponent *= optional_value.value(); + } + + // 14.6. Multiply value by ten raised to the exponentth power. + value *= std::pow(10, exponent); + } + +conversion: { + // 15. Conversion: Let S be the set of finite IEEE 754 double-precision floating-point values except −0, + // but with two special values added: 2^1024 and −2^1024. + if (!std::isfinite(value)) { + return {}; + } + if ((value == 0) && std::signbit(value)) { + return 0; + } + + // 16. Let rounded-value be the number in S that is closest to value, selecting the number with an even significand if there are two equally close values. + // (The two special values 2^1024 and −2^1024 are considered to have even significands for this purpose.) + double rounded_value = value; + + // 17. If rounded-value is 2^1024 or −2^1024, return an error. + if (std::abs(rounded_value) >= std::pow(2, 1024)) { + return {}; + } + + // 18. Return rounded-value. + return rounded_value; +} } // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-floating-point-number