AK+Everywhere: Replace custom number parsers with fast_float
Some checks failed
CI / macOS, arm64, Sanitizer_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
Build Dev Container Image / build (push) Has been cancelled

Our floating point number parser was based on the fast_float library:
https://github.com/fastfloat/fast_float

However, our implementation only supports 8-bit characters. To support
UTF-16, we will need to be able to convert char16_t-based strings to
numbers as well. This works out-of-the-box with fast_float.

We can also use fast_float for integer parsing.
This commit is contained in:
Timothy Flynn 2025-06-26 19:06:46 -04:00 committed by Tim Flynn
commit 62d9a84b8d
Notes: github-actions[bot] 2025-07-03 13:53:10 +00:00
30 changed files with 413 additions and 3034 deletions

View file

@ -7,7 +7,6 @@ set(SOURCES
ConstrainedStream.cpp
CountingStream.cpp
Error.cpp
FloatingPointStringConversions.cpp
FlyString.cpp
Format.cpp
GenericLexer.cpp
@ -25,6 +24,7 @@ set(SOURCES
String.cpp
StringBase.cpp
StringBuilder.cpp
StringConversions.cpp
StringFloatingPointConversions.cpp
StringUtils.cpp
StringView.cpp

File diff suppressed because it is too large Load diff

View file

@ -1,82 +0,0 @@
/*
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/StringView.h>
namespace AK {
static constexpr char floating_point_decimal_separator = '.';
enum class FloatingPointError {
None,
NoOrInvalidInput,
OutOfRange,
RoundedDownToZero
};
template<FloatingPoint T>
struct FloatingPointParseResults {
char const* end_ptr { nullptr };
FloatingPointError error = FloatingPointError::None;
T value {};
[[nodiscard]] bool parsed_value() const
{
// All other errors do indicate out of range but did produce a valid value.
return error != FloatingPointError::NoOrInvalidInput;
}
};
/// This function finds the first floating point within [start, end). The accepted format is
/// intentionally as lenient as possible. If your format is stricter you must validate it
/// first. The format accepts:
/// - An optional sign, both + and - are supported
/// - 0 or more decimal digits, with leading zeros allowed [1]
/// - A decimal point '.', which can have no digits after it
/// - 0 or more decimal digits, unless the first digits [1] doesn't have any digits,
/// then this must have at least one
/// - An exponent 'e' or 'E' followed by an optional sign '+' or '-' and at least one digit
/// This function additionally detects out of range values which have been rounded to
/// [-]infinity or 0 and gives the next character to read after the floating point.
template<FloatingPoint T = double>
FloatingPointParseResults<T> parse_first_floating_point(char const* start, char const* end);
/// This function finds the first floating point starting at start up to the first '\0'.
/// The format is identical to parse_first_floating_point above.
template<FloatingPoint T = double>
FloatingPointParseResults<T> parse_first_floating_point_until_zero_character(char const* start);
/// This function will return either a floating point, or an empty optional if the given StringView
/// does not a floating point or contains more characters beyond the floating point. For the format
/// check the comment on parse_first_floating_point.
template<FloatingPoint T = double>
Optional<T> parse_floating_point_completely(char const* start, char const* end);
/// This function finds the first floating point as a hex float within [start, end).
/// The accepted format is intentionally as lenient as possible. If your format is
/// stricter you must validate it first. The format accepts:
/// - An optional sign, both + and - are supported
/// - Optionally either 0x or OX
/// - 0 or more hexadecimal digits, with leading zeros allowed [1]
/// - A decimal point '.', which can have no digits after it
/// - 0 or more hexadecimal digits, unless the first digits [1] doesn't have any digits,
/// then this must have at least one
/// - An exponent 'p' or 'P' followed by an optional sign '+' or '-' and at least one decimal digit
/// NOTE: The exponent is _not_ hexadecimal and gives powers of 2 not 16.
/// This function additionally detects out of range values which have been rounded to
/// [-]infinity or 0 and gives the next character to read after the floating point.
template<FloatingPoint T = double>
FloatingPointParseResults<T> parse_first_hexfloat_until_zero_character(char const* start);
}
#if USING_AK_GLOBALLY
using AK::parse_first_floating_point;
using AK::parse_first_hexfloat_until_zero_character;
using AK::parse_floating_point_completely;
#endif

View file

@ -132,7 +132,7 @@ ErrorOr<T> GenericLexer::consume_decimal_integer()
if (number_view.is_empty())
return Error::from_errno(EINVAL);
auto maybe_number = StringUtils::convert_to_uint<UnsignedT>(number_view, TrimWhitespace::No);
auto maybe_number = number_view.to_number<UnsignedT>(TrimWhitespace::No);
if (!maybe_number.has_value())
return Error::from_errno(ERANGE);
auto number = maybe_number.value();

View file

@ -12,6 +12,7 @@
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <AK/StringConversions.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
@ -140,7 +141,7 @@ public:
auto separator_part = parts[parts.size() - 2].trim_whitespace();
if (separator_part.is_empty())
return false;
auto separator_value = StringUtils::convert_to_uint_from_hex(separator_part);
auto separator_value = AK::parse_hexadecimal_number<u32>(separator_part);
if (!separator_value.has_value() || separator_value.value() != 0xffff)
return false;
// TODO: this allows multiple compression tags "::" in the prefix, which is technically not legal
@ -148,7 +149,7 @@ public:
auto part = parts[i].trim_whitespace();
if (part.is_empty())
continue;
auto value = StringUtils::convert_to_uint_from_hex(part);
auto value = AK::parse_hexadecimal_number<u32>(part);
if (!value.has_value() || value.value() != 0)
return false;
}
@ -203,7 +204,7 @@ public:
} else {
i++;
}
auto part = StringUtils::convert_to_uint_from_hex(trimmed_part);
auto part = AK::parse_hexadecimal_number<u32>(trimmed_part);
if (!part.has_value() || part.value() > 0xffff)
return {};

View file

@ -5,10 +5,10 @@
*/
#include <AK/CharacterTypes.h>
#include <AK/FloatingPointStringConversions.h>
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonParser.h>
#include <AK/StringConversions.h>
#include <math.h>
namespace AK {
@ -219,18 +219,14 @@ ErrorOr<JsonValue> JsonParser::parse_number()
// use that in the floating point parser.
// The first part should be just ascii digits
StringView view = m_input.substring_view(start_index);
auto view = m_input.substring_view(start_index);
char const* start = view.characters_without_null_termination();
auto parse_result = parse_first_floating_point(start, start + view.length());
if (parse_result.parsed_value()) {
auto characters_parsed = parse_result.end_ptr - start;
m_index = start_index + characters_parsed;
return JsonValue(parse_result.value);
}
auto parse_result = parse_first_number<double>(view, TrimWhitespace::No);
if (!parse_result.has_value())
return Error::from_string_literal("JsonParser: Invalid floating point");
m_index = start_index + parse_result->characters_parsed;
return JsonValue { parse_result->value };
};
if (peek() == '0') {

100
AK/StringConversions.cpp Normal file
View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/StringConversions.h>
#include <AK/StringView.h>
#include <fast_float/fast_float.h>
namespace AK {
#define ENUMERATE_INTEGRAL_TYPES \
__ENUMERATE_TYPE(i8) \
__ENUMERATE_TYPE(i16) \
__ENUMERATE_TYPE(i32) \
__ENUMERATE_TYPE(long) \
__ENUMERATE_TYPE(long long) \
__ENUMERATE_TYPE(u8) \
__ENUMERATE_TYPE(u16) \
__ENUMERATE_TYPE(u32) \
__ENUMERATE_TYPE(unsigned long) \
__ENUMERATE_TYPE(unsigned long long)
#define ENUMERATE_ARITHMETIC_TYPES \
ENUMERATE_INTEGRAL_TYPES \
__ENUMERATE_TYPE(float) \
__ENUMERATE_TYPE(double)
template<Arithmetic T>
Optional<ParseFirstNumberResult<T>> parse_first_number(StringView string, TrimWhitespace trim_whitespace, int base)
{
if (trim_whitespace == TrimWhitespace::Yes)
string = StringUtils::trim_whitespace(string, TrimMode::Both);
auto const* begin = string.characters_without_null_termination();
auto const* end = begin + string.length();
T value { 0 };
fast_float::parse_options_t<char> options;
options.base = base;
options.format |= fast_float::chars_format::no_infnan;
if constexpr (IsSigned<T> || IsFloatingPoint<T>) {
options.format |= fast_float::chars_format::allow_leading_plus;
}
auto result = fast_float::from_chars_advanced(begin, end, value, options);
if constexpr (IsFloatingPoint<T>) {
if (result.ec == std::errc::result_out_of_range && (__builtin_isinf(value) || value == 0))
result.ec = {};
}
if (result.ec != std::errc {})
return {};
return ParseFirstNumberResult { value, static_cast<size_t>(result.ptr - begin) };
}
#define __ENUMERATE_TYPE(type) \
template Optional<ParseFirstNumberResult<type>> parse_first_number(StringView, TrimWhitespace, int);
ENUMERATE_ARITHMETIC_TYPES
#undef __ENUMERATE_TYPE
template<Arithmetic T>
Optional<T> parse_number(StringView string, TrimWhitespace trim_whitespace, int base)
{
if (trim_whitespace == TrimWhitespace::Yes)
string = StringUtils::trim_whitespace(string, TrimMode::Both);
auto result = parse_first_number<T>(string, TrimWhitespace::No, base);
if (!result.has_value())
return {};
if (result->characters_parsed != string.length())
return {};
return result->value;
}
#define __ENUMERATE_TYPE(type) \
template Optional<type> parse_number(StringView, TrimWhitespace, int);
ENUMERATE_ARITHMETIC_TYPES
#undef __ENUMERATE_TYPE
template<Integral T>
Optional<T> parse_hexadecimal_number(StringView string, TrimWhitespace trim_whitespace)
{
return parse_number<T>(string, trim_whitespace, 16);
}
#define __ENUMERATE_TYPE(type) \
template Optional<type> parse_hexadecimal_number(StringView, TrimWhitespace);
ENUMERATE_INTEGRAL_TYPES
#undef __ENUMERATE_TYPE
}

31
AK/StringConversions.h Normal file
View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Concepts.h>
#include <AK/Optional.h>
#include <AK/StringUtils.h>
namespace AK {
template<typename T>
struct ParseFirstNumberResult {
T value { 0 };
size_t characters_parsed { 0 };
};
template<Arithmetic T>
Optional<ParseFirstNumberResult<T>> parse_first_number(StringView, TrimWhitespace = TrimWhitespace::Yes, int base = 10);
template<Arithmetic T>
Optional<T> parse_number(StringView, TrimWhitespace = TrimWhitespace::Yes, int base = 10);
template<Integral T>
Optional<T> parse_hexadecimal_number(StringView, TrimWhitespace = TrimWhitespace::Yes);
}

View file

@ -7,7 +7,6 @@
#include <AK/ByteString.h>
#include <AK/CharacterTypes.h>
#include <AK/FloatingPointStringConversions.h>
#include <AK/MemMem.h>
#include <AK/Optional.h>
#include <AK/String.h>
@ -86,167 +85,6 @@ bool matches(StringView str, StringView mask, CaseSensitivity case_sensitivity,
return string_ptr == string_end && mask_ptr == mask_end;
}
template<typename T>
Optional<T> convert_to_int(StringView str, TrimWhitespace trim_whitespace)
{
auto string = trim_whitespace == TrimWhitespace::Yes
? str.trim_whitespace()
: str;
if (string.is_empty())
return {};
T sign = 1;
size_t i = 0;
auto const characters = string.characters_without_null_termination();
if (characters[0] == '-' || characters[0] == '+') {
if (string.length() == 1)
return {};
i++;
if (characters[0] == '-')
sign = -1;
}
T value = 0;
for (; i < string.length(); i++) {
if (characters[i] < '0' || characters[i] > '9')
return {};
if (__builtin_mul_overflow(value, 10, &value))
return {};
if (__builtin_add_overflow(value, sign * (characters[i] - '0'), &value))
return {};
}
return value;
}
template Optional<i8> convert_to_int(StringView str, TrimWhitespace);
template Optional<i16> convert_to_int(StringView str, TrimWhitespace);
template Optional<i32> convert_to_int(StringView str, TrimWhitespace);
template Optional<long> convert_to_int(StringView str, TrimWhitespace);
template Optional<long long> convert_to_int(StringView str, TrimWhitespace);
template<typename T>
Optional<T> convert_to_uint(StringView str, TrimWhitespace trim_whitespace)
{
auto string = trim_whitespace == TrimWhitespace::Yes
? str.trim_whitespace()
: str;
if (string.is_empty())
return {};
T value = 0;
auto const characters = string.characters_without_null_termination();
for (size_t i = 0; i < string.length(); i++) {
if (characters[i] < '0' || characters[i] > '9')
return {};
if (__builtin_mul_overflow(value, 10, &value))
return {};
if (__builtin_add_overflow(value, characters[i] - '0', &value))
return {};
}
return value;
}
template Optional<u8> convert_to_uint(StringView str, TrimWhitespace);
template Optional<u16> convert_to_uint(StringView str, TrimWhitespace);
template Optional<u32> convert_to_uint(StringView str, TrimWhitespace);
template Optional<unsigned long> convert_to_uint(StringView str, TrimWhitespace);
template Optional<unsigned long long> convert_to_uint(StringView str, TrimWhitespace);
template<typename T>
Optional<T> convert_to_uint_from_hex(StringView str, TrimWhitespace trim_whitespace)
{
auto string = trim_whitespace == TrimWhitespace::Yes
? str.trim_whitespace()
: str;
if (string.is_empty())
return {};
T value = 0;
auto const count = string.length();
T const upper_bound = NumericLimits<T>::max();
for (size_t i = 0; i < count; i++) {
char digit = string[i];
u8 digit_val;
if (value > (upper_bound >> 4))
return {};
if (digit >= '0' && digit <= '9') {
digit_val = digit - '0';
} else if (digit >= 'a' && digit <= 'f') {
digit_val = 10 + (digit - 'a');
} else if (digit >= 'A' && digit <= 'F') {
digit_val = 10 + (digit - 'A');
} else {
return {};
}
value = (value << 4) + digit_val;
}
return value;
}
template Optional<u8> convert_to_uint_from_hex(StringView str, TrimWhitespace);
template Optional<u16> convert_to_uint_from_hex(StringView str, TrimWhitespace);
template Optional<u32> convert_to_uint_from_hex(StringView str, TrimWhitespace);
template Optional<u64> convert_to_uint_from_hex(StringView str, TrimWhitespace);
template<typename T>
Optional<T> convert_to_uint_from_octal(StringView str, TrimWhitespace trim_whitespace)
{
auto string = trim_whitespace == TrimWhitespace::Yes
? str.trim_whitespace()
: str;
if (string.is_empty())
return {};
T value = 0;
auto const count = string.length();
T const upper_bound = NumericLimits<T>::max();
for (size_t i = 0; i < count; i++) {
char digit = string[i];
u8 digit_val;
if (value > (upper_bound >> 3))
return {};
if (digit >= '0' && digit <= '7') {
digit_val = digit - '0';
} else {
return {};
}
value = (value << 3) + digit_val;
}
return value;
}
template Optional<u8> convert_to_uint_from_octal(StringView str, TrimWhitespace);
template Optional<u16> convert_to_uint_from_octal(StringView str, TrimWhitespace);
template Optional<u32> convert_to_uint_from_octal(StringView str, TrimWhitespace);
template Optional<u64> convert_to_uint_from_octal(StringView str, TrimWhitespace);
template<typename T>
Optional<T> convert_to_floating_point(StringView str, TrimWhitespace trim_whitespace)
{
static_assert(IsSame<T, double> || IsSame<T, float>);
auto string = trim_whitespace == TrimWhitespace::Yes
? str.trim_whitespace()
: str;
char const* start = string.characters_without_null_termination();
return parse_floating_point_completely<T>(start, start + string.length());
}
template Optional<double> convert_to_floating_point(StringView str, TrimWhitespace);
template Optional<float> convert_to_floating_point(StringView str, TrimWhitespace);
bool equals_ignoring_ascii_case(StringView a, StringView b)
{
if (a.length() != b.length())

View file

@ -76,16 +76,6 @@ struct MaskSpan {
namespace StringUtils {
bool matches(StringView str, StringView mask, CaseSensitivity = CaseSensitivity::CaseInsensitive, Vector<MaskSpan>* match_spans = nullptr);
template<typename T = int>
Optional<T> convert_to_int(StringView, TrimWhitespace = TrimWhitespace::Yes);
template<typename T = unsigned>
Optional<T> convert_to_uint(StringView, TrimWhitespace = TrimWhitespace::Yes);
template<typename T = unsigned>
Optional<T> convert_to_uint_from_hex(StringView, TrimWhitespace = TrimWhitespace::Yes);
template<typename T = unsigned>
Optional<T> convert_to_uint_from_octal(StringView, TrimWhitespace = TrimWhitespace::Yes);
template<typename T>
Optional<T> convert_to_floating_point(StringView, TrimWhitespace = TrimWhitespace::Yes);
bool equals_ignoring_ascii_case(StringView, StringView);
bool ends_with(StringView a, StringView b, CaseSensitivity);
bool starts_with(StringView, StringView, CaseSensitivity);

View file

@ -14,6 +14,7 @@
#include <AK/Optional.h>
#include <AK/Span.h>
#include <AK/StdLibExtras.h>
#include <AK/StringConversions.h>
#include <AK/StringHash.h>
#include <AK/StringUtils.h>
@ -349,12 +350,7 @@ public:
template<Arithmetic T>
Optional<T> to_number(TrimWhitespace trim_whitespace = TrimWhitespace::Yes) const
{
if constexpr (IsFloatingPoint<T>)
return StringUtils::convert_to_floating_point<T>(*this, trim_whitespace);
if constexpr (IsSigned<T>)
return StringUtils::convert_to_int<T>(*this, trim_whitespace);
else
return StringUtils::convert_to_uint<T>(*this, trim_whitespace);
return parse_number<T>(*this, trim_whitespace);
}
private:

View file

@ -8,8 +8,8 @@
#include <AK/Assertions.h>
#include <AK/ByteString.h>
#include <AK/FloatingPointStringConversions.h>
#include <AK/Optional.h>
#include <AK/StringConversions.h>
#include <AK/Swift.h>
#include <AK/Vector.h>
#include <LibGfx/Color.h>
@ -99,12 +99,8 @@ static Optional<Color> parse_rgba_color(StringView string)
auto g = parts[1].to_number<double>().map(AK::clamp_to<u8, double>);
auto b = parts[2].to_number<double>().map(AK::clamp_to<u8, double>);
double alpha = 0;
auto alpha_str = parts[3].trim_whitespace();
char const* start = alpha_str.characters_without_null_termination();
auto alpha_result = parse_first_floating_point(start, start + alpha_str.length());
if (alpha_result.parsed_value())
alpha = alpha_result.value;
auto parse_result = AK::parse_first_number<double>(parts[3]);
auto alpha = parse_result.has_value() ? parse_result->value : 0.0;
unsigned a = alpha * 255;

View file

@ -6,7 +6,6 @@
*/
#include <AK/CharacterTypes.h>
#include <AK/FloatingPointStringConversions.h>
#include <AK/Function.h>
#include <AK/Optional.h>
#include <AK/Utf16View.h>

View file

@ -7,8 +7,8 @@
#include <AK/BuiltinWrappers.h>
#include <AK/CharacterTypes.h>
#include <AK/FloatingPointStringConversions.h>
#include <AK/Hex.h>
#include <AK/StringConversions.h>
#include <AK/UnicodeUtils.h>
#include <AK/Utf16View.h>
#include <AK/Utf8View.h>
@ -269,12 +269,10 @@ JS_DEFINE_NATIVE_FUNCTION(GlobalObject::parse_float)
// 6. Assert: parsedNumber is a Parse Node.
// 7. Return StringNumericValue of parsedNumber.
auto trimmed_string_view = trimmed_string.bytes_as_string_view();
auto const* begin = trimmed_string_view.characters_without_null_termination();
auto const* end = begin + trimmed_string_view.length();
auto parsed_number = parse_first_floating_point<double>(begin, end);
if (parsed_number.parsed_value())
return parsed_number.value;
auto parsed_number = AK::parse_first_number<double>(trimmed_string_view, TrimWhitespace::No);
if (parsed_number.has_value())
return parsed_number->value;
auto first_code_point = *trimmed_string.code_points().begin();
if (first_code_point == '-' || first_code_point == '+')

View file

@ -5,6 +5,7 @@
*/
#include <AK/StringBuilder.h>
#include <AK/StringConversions.h>
#include <AK/StringUtils.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/TypedArray.h>
@ -502,7 +503,7 @@ DecodeResult from_hex(VM& vm, StringView string, Optional<size_t> max_length)
// d. Let byte be the integer value represented by hexits in base-16 notation, using the letters A-F and a-f
// for digits with values 10 through 15.
// NOTE: We do this early so that we don't have to effectively parse hexits twice.
auto byte = AK::StringUtils::convert_to_uint_from_hex<u8>(hexits, AK::TrimWhitespace::No);
auto byte = AK::parse_hexadecimal_number<u8>(hexits, TrimWhitespace::No);
// b. If hexits contains any code units which are not in "0123456789abcdefABCDEF", then
if (!byte.has_value()) {

View file

@ -10,7 +10,6 @@
#include <AK/Assertions.h>
#include <AK/ByteString.h>
#include <AK/CharacterTypes.h>
#include <AK/FloatingPointStringConversions.h>
#include <AK/StringBuilder.h>
#include <AK/StringFloatingPointConversions.h>
#include <AK/Utf8View.h>

View file

@ -8,7 +8,6 @@
#include "Token.h"
#include <AK/Assertions.h>
#include <AK/CharacterTypes.h>
#include <AK/FloatingPointStringConversions.h>
#include <AK/GenericLexer.h>
#include <AK/StringBuilder.h>

View file

@ -14,7 +14,7 @@
#include <AK/GenericLexer.h>
#include <AK/ScopeGuard.h>
#include <AK/StringBuilder.h>
#include <AK/StringUtils.h>
#include <AK/StringConversions.h>
#include <AK/TemporaryChange.h>
#include <LibUnicode/CharacterTypes.h>
@ -1220,7 +1220,7 @@ StringView ECMA262Parser::read_digits_as_string(ReadDigitsInitialZeroState initi
if (max_count > 0 && count >= max_count)
break;
if (hex && !AK::StringUtils::convert_to_uint_from_hex(c).has_value())
if (hex && !AK::parse_hexadecimal_number<u32>(c).has_value())
break;
if (!hex && !c.to_number<unsigned>().has_value())
break;
@ -1241,7 +1241,7 @@ Optional<unsigned> ECMA262Parser::read_digits(ECMA262Parser::ReadDigitsInitialZe
if (str.is_empty())
return {};
if (hex)
return AK::StringUtils::convert_to_uint_from_hex(str);
return AK::parse_hexadecimal_number<u32>(str);
return str.to_number<unsigned>();
}

View file

@ -12,6 +12,7 @@
#include <AK/Optional.h>
#include <AK/SourceLocation.h>
#include <AK/StringBuilder.h>
#include <AK/StringConversions.h>
#include <AK/StringUtils.h>
#include <AK/Utf8View.h>
#include <LibTextCodec/Decoder.h>
@ -138,15 +139,7 @@ static Optional<ParsedIPv4Number> parse_ipv4_number(StringView input)
}
// 8. Let output be the mathematical integer value that is represented by input in radix-R notation, using ASCII hex digits for digits with values 0 through 15.
Optional<u32> maybe_output;
if (radix == 8)
maybe_output = AK::StringUtils::convert_to_uint_from_octal(input, TrimWhitespace::No);
else if (radix == 10)
maybe_output = input.to_number<u32>(TrimWhitespace::No);
else if (radix == 16)
maybe_output = AK::StringUtils::convert_to_uint_from_hex(input, TrimWhitespace::No);
else
VERIFY_NOT_REACHED();
auto maybe_output = AK::parse_number<u32>(input, TrimWhitespace::No, radix);
// NOTE: Parsing may have failed due to overflow.
if (!maybe_output.has_value())

View file

@ -6,8 +6,8 @@
*/
#include <AK/Debug.h>
#include <AK/FloatingPointStringConversions.h>
#include <AK/SourceLocation.h>
#include <AK/StringConversions.h>
#include <AK/Vector.h>
#include <LibTextCodec/Decoder.h>
#include <LibWeb/CSS/CharacterTypes.h>
@ -377,7 +377,7 @@ u32 Tokenizer::consume_escaped_code_point()
}
// Interpret the hex digits as a hexadecimal number.
auto unhexed = AK::StringUtils::convert_to_uint_from_hex<u32>(builder.string_view()).value_or(0);
auto unhexed = AK::parse_hexadecimal_number<u32>(builder.string_view()).value_or(0);
// If this number is zero, or is for a surrogate, or is greater than the maximum allowed
// code point, return U+FFFD REPLACEMENT CHARACTER (<28>).
if (unhexed == 0 || is_unicode_surrogate(unhexed) || is_greater_than_maximum_allowed_code_point(unhexed)) {

View file

@ -16,6 +16,7 @@
#include <AK/Debug.h>
#include <AK/GenericLexer.h>
#include <AK/QuickSort.h>
#include <AK/StringConversions.h>
#include <AK/TemporaryChange.h>
#include <LibWeb/CSS/FontFace.h>
#include <LibWeb/CSS/Parser/Parser.h>
@ -564,7 +565,7 @@ Optional<Gfx::UnicodeRange> Parser::parse_unicode_range(StringView text)
// with the U+003F QUESTION MARK (?) code points replaced by U+0030 DIGIT ZERO (0) code points.
// This is the start value.
auto start_value_string = start_value_code_points.replace("?"sv, "0"sv, ReplaceMode::All);
auto maybe_start_value = AK::StringUtils::convert_to_uint_from_hex<u32>(start_value_string);
auto maybe_start_value = AK::parse_hexadecimal_number<u32>(start_value_string);
if (!maybe_start_value.has_value()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> ?-converted start value did not parse as hex number.");
return {};
@ -575,7 +576,7 @@ Optional<Gfx::UnicodeRange> Parser::parse_unicode_range(StringView text)
// with the U+003F QUESTION MARK (?) code points replaced by U+0046 LATIN CAPITAL LETTER F (F) code points.
// This is the end value.
auto end_value_string = start_value_code_points.replace("?"sv, "F"sv, ReplaceMode::All);
auto maybe_end_value = AK::StringUtils::convert_to_uint_from_hex<u32>(end_value_string);
auto maybe_end_value = AK::parse_hexadecimal_number<u32>(end_value_string);
if (!maybe_end_value.has_value()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> ?-converted end value did not parse as hex number.");
return {};
@ -586,7 +587,7 @@ Optional<Gfx::UnicodeRange> Parser::parse_unicode_range(StringView text)
return make_valid_unicode_range(start_value, end_value);
}
// Otherwise, interpret the consumed code points as a hexadecimal number. This is the start value.
auto maybe_start_value = AK::StringUtils::convert_to_uint_from_hex<u32>(start_value_code_points);
auto maybe_start_value = AK::parse_hexadecimal_number<u32>(start_value_code_points);
if (!maybe_start_value.has_value()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> start value did not parse as hex number.");
return {};
@ -625,7 +626,7 @@ Optional<Gfx::UnicodeRange> Parser::parse_unicode_range(StringView text)
}
// 7. Interpret the consumed code points as a hexadecimal number. This is the end value.
auto maybe_end_value = AK::StringUtils::convert_to_uint_from_hex<u32>(end_hex_digits);
auto maybe_end_value = AK::parse_hexadecimal_number<u32>(end_hex_digits);
if (!maybe_end_value.has_value()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> end value did not parse as hex number.");
return {};

View file

@ -54,7 +54,7 @@ Optional<CSS::Keyword> HTMLFontElement::parse_legacy_font_size(StringView string
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 value_or_empty = AK::StringUtils::convert_to_int<i32>(digits);
auto value_or_empty = digits.to_number<i32>();
// 7. If digits is the empty string, there is no presentational hint. Return.
if (!value_or_empty.has_value())

View file

@ -34,7 +34,7 @@ WebIDL::UnsignedLong HTMLTableColElement::span() const
// The span IDL attribute must reflect the content attribute of the same name. It is clamped to the range [1, 1000], and its default value is 1.
if (auto span_string = get_attribute(HTML::AttributeNames::span); span_string.has_value()) {
if (auto span_digits = parse_non_negative_integer_digits(*span_string); span_digits.has_value()) {
auto span = AK::StringUtils::convert_to_int<i64>(*span_digits);
auto span = span_digits->to_number<i64>();
// NOTE: If span has no value at this point, the value must be larger than NumericLimits<i64>::max(), so return the maximum value of 1000.
if (!span.has_value())
return 1000;

View file

@ -5,6 +5,7 @@
*/
#include <AK/GenericLexer.h>
#include <AK/StringConversions.h>
#include <LibWeb/HTML/Numbers.h>
#include <LibWeb/Infra/CharacterTypes.h>
#include <math.h>
@ -173,7 +174,7 @@ Optional<double> parse_floating_point_number(StringView string)
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_floating_point<double>(digits, TrimWhitespace::No);
auto optional_value = digits.to_number<double>(TrimWhitespace::No);
value *= optional_value.value();
}
@ -274,7 +275,7 @@ fraction_exit:
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<i32>(digits);
auto optional_value = digits.to_number<i32>();
exponent *= optional_value.value();
}

View file

@ -7,10 +7,10 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "AttributeParser.h"
#include <AK/FloatingPointStringConversions.h>
#include <AK/GenericShorthands.h>
#include <AK/StringBuilder.h>
#include <AK/StringConversions.h>
#include <LibWeb/SVG/AttributeParser.h>
#include <ctype.h>
namespace Web::SVG {
@ -439,14 +439,11 @@ ErrorOr<float> AttributeParser::parse_nonnegative_number()
if (match('+') || match('-') || !match_number())
return Error::from_string_literal("Expected number");
auto remaining_source_text = m_lexer.remaining();
char const* start = remaining_source_text.characters_without_null_termination();
auto parse_result = AK::parse_first_number<float>(m_lexer.remaining(), TrimWhitespace::No);
VERIFY(parse_result.has_value());
auto maybe_float = parse_first_floating_point<float>(start, start + remaining_source_text.length());
VERIFY(maybe_float.parsed_value());
m_lexer.ignore(maybe_float.end_ptr - start);
return maybe_float.value;
m_lexer.ignore(parse_result->characters_parsed);
return parse_result->value;
}
ErrorOr<float> AttributeParser::parse_flag()

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/StringConversions.h>
#include <LibXML/DOM/Document.h>
#include <LibXML/Parser/Parser.h>
@ -791,7 +792,7 @@ ErrorOr<Variant<Parser::EntityReference, ByteString>, ParseError> Parser::parse_
auto hex = TRY(expect_many(
ranges_for_search<Range('0', '9'), Range('a', 'f'), Range('A', 'F')>(),
"any of [0-9a-fA-F]"sv));
code_point = AK::StringUtils::convert_to_uint_from_hex<u32>(hex);
code_point = AK::parse_hexadecimal_number<u32>(hex);
} else {
auto decimal = TRY(expect_many(
ranges_for_search<Range('0', '9')>(),

View file

@ -27,7 +27,6 @@ set(AK_TEST_SOURCES
TestFind.cpp
TestFixedArray.cpp
TestFixedPoint.cpp
TestFloatingPointParsing.cpp
TestFlyString.cpp
TestFormat.cpp
TestGenericLexer.cpp
@ -67,6 +66,7 @@ set(AK_TEST_SOURCES
TestStack.cpp
TestStdLibExtras.cpp
TestString.cpp
TestStringConversions.cpp
TestStringFloatingPointConversions.cpp
TestStringUtils.cpp
TestStringView.cpp

View file

@ -6,18 +6,16 @@
#include <LibTest/TestCase.h>
#include <AK/FloatingPointStringConversions.h>
#include <AK/StringConversions.h>
static double parse_complete_double(StringView view)
{
char const* start = view.characters_without_null_termination();
return parse_floating_point_completely<double>(start, start + view.length()).release_value();
return AK::parse_number<double>(view, TrimWhitespace::No).release_value();
}
static float parse_complete_float(StringView view)
{
char const* start = view.characters_without_null_termination();
return parse_floating_point_completely<float>(start, start + view.length()).release_value();
return AK::parse_number<float>(view, TrimWhitespace::No).release_value();
}
TEST_CASE(simple_cases)
@ -266,12 +264,10 @@ TEST_CASE(partial_parse_stops_at_right_spot)
{
#define EXPECT_PARSE_TO_VALUE_AND_CONSUME_CHARS(string_value, double_value, chars_parsed) \
do { \
StringView view = string_value##sv; \
char const* start = view.characters_without_null_termination(); \
auto result = parse_first_floating_point<double>(start, start + view.length()); \
EXPECT(result.error == AK::FloatingPointError::None); \
EXPECT_EQ(bit_cast<u64>(result.value), bit_cast<u64>(static_cast<double>(double_value))); \
EXPECT_EQ(result.end_ptr - start, chars_parsed); \
auto result = AK::parse_first_number<double>(string_value##sv); \
VERIFY(result.has_value()); \
EXPECT_EQ(bit_cast<u64>(result->value), bit_cast<u64>(static_cast<double>(double_value))); \
EXPECT_EQ(result->characters_parsed, chars_parsed##uz); \
} while (false)
EXPECT_PARSE_TO_VALUE_AND_CONSUME_CHARS("0x", 0., 1);
@ -291,10 +287,8 @@ TEST_CASE(invalid_parse)
{
#define EXPECT_PARSE_TO_FAIL(string_value) \
do { \
StringView view = string_value##sv; \
char const* start = view.characters_without_null_termination(); \
auto result = parse_first_floating_point<double>(start, start + view.length()); \
EXPECT(result.error == AK::FloatingPointError::NoOrInvalidInput); \
auto result = AK::parse_first_number<double>(string_value##sv); \
EXPECT(!result.has_value()); \
} while (false)
EXPECT_PARSE_TO_FAIL("");
@ -333,25 +327,22 @@ TEST_CASE(invalid_parse)
TEST_CASE(detect_out_of_range_values)
{
#define EXPECT_PARSE_TO_HAVE_ERROR(string_value, error_value) \
#define EXPECT_PARSE_TO_HAVE_ERROR(string_value, double_value) \
do { \
StringView view = string_value##sv; \
char const* start = view.characters_without_null_termination(); \
auto result = parse_first_floating_point<double>(start, start + view.length()); \
EXPECT(result.error == error_value); \
EXPECT(result.end_ptr == start + view.length()); \
auto result = AK::parse_first_number<double>(string_value##sv); \
VERIFY(result.has_value()); \
EXPECT_EQ(bit_cast<u64>(result->value), bit_cast<u64>(static_cast<double>(double_value))); \
} while (false)
EXPECT_PARSE_TO_HAVE_ERROR("10e-10000", AK::FloatingPointError::RoundedDownToZero);
EXPECT_PARSE_TO_HAVE_ERROR("-10e-10000", AK::FloatingPointError::RoundedDownToZero);
EXPECT_PARSE_TO_HAVE_ERROR("10e10000", AK::FloatingPointError::OutOfRange);
EXPECT_PARSE_TO_HAVE_ERROR("-10e10000", AK::FloatingPointError::OutOfRange);
EXPECT_PARSE_TO_HAVE_ERROR("10e-10000", 0.0);
EXPECT_PARSE_TO_HAVE_ERROR("-10e-10000", -0.0);
EXPECT_PARSE_TO_HAVE_ERROR("10e10000", INFINITY);
EXPECT_PARSE_TO_HAVE_ERROR("-10e10000", -INFINITY);
}
static bool parse_completely_passes(StringView view)
{
char const* start = view.characters_without_null_termination();
return parse_floating_point_completely<double>(start, start + view.length()).has_value();
return AK::parse_number<double>(view, TrimWhitespace::No).has_value();
}
TEST_CASE(parse_completely_must_be_just_floating_point)
@ -406,173 +397,6 @@ TEST_CASE(parse_completely_must_be_just_floating_point)
EXPECT_PARSE_COMPLETELY_TO_FAIL("1234567=890");
}
static double newhex(char const* view)
{
auto value = parse_first_hexfloat_until_zero_character<double>(view);
VERIFY(value.error == AK::FloatingPointError::None);
return value.value;
}
static float newhexf(char const* view)
{
auto value = parse_first_hexfloat_until_zero_character<float>(view);
VERIFY(value.error == AK::FloatingPointError::None);
return value.value;
}
TEST_CASE(hexfloat)
{
#define DOES_PARSE_HEX_DOUBLE_LIKE_CPP(value) \
do { \
EXPECT_EQ(static_cast<double>(value), newhex(#value)); \
EXPECT_EQ(-static_cast<double>(value), newhex("-" #value)); \
} while (false)
#define DOES_PARSE_HEX_FLOAT_LIKE_CPP(value) \
do { \
EXPECT_EQ(static_cast<float>(value##f), newhexf(#value)); \
EXPECT_EQ(-static_cast<float>(value##f), newhexf("-" #value)); \
} while (false)
#define DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(value) \
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(value); \
DOES_PARSE_HEX_FLOAT_LIKE_CPP(value)
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x123456789ABCDEFp0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x123456789ABCDEFp+0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x123456789ABCDEFp-0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x123456789ABCDEF.p-0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x123456789ABCDEF.123456789ABCDEFp-0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x123456789ABCDEF.123456789ABCDEFp-1);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x123456789ABCDEF.123456789ABCDEFp+1);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c0p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c00p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c000p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c001p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c10001p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c8p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c8001p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c80000000000000000000000000000000000000000000000000000000001p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c80000000000000000000000000000000000000000000000000000000000p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffp+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c9p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c9001p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x180eafb89ba47c9.001p+52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x180eafb89ba47c9.001p-4);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp-1075);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p-1075);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp-1040);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p-1040);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp-999);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p-999);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp-788);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p-788);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp-632);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p-632);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp-408);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p-408);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp-189);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p-189);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp-76);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p-76);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp-52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p-52);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp-25);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p-25);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp-13);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p-13);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp-3);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p-3);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp+3);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p+3);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp+6);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p+6);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp+13);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p+13);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp+19);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p+19);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp+154);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p+154);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp+298);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p+298);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp+455);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p+455);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp+692);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p+692);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp+901);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p+901);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47cp+1023);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.80eafb89ba47c1p+1023);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x.80eafb89ba47cp+1024);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x.80eafb89ba47c1p+1024);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x.080eafb89ba47cp+1025);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x.080eafb89ba47c1p+1025);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.c5e1463479f8ep+218);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.c5e1463479f8e8p+218);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.c5e1463479f8e80p+218);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.c5e1463479f8e800p+218);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.c5e1463479f8e8001p+218);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.42100a53adbd5p-1024);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.d542100a53adbp-1023);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.fffffffffffffp-1023);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.fffffffffffff9p-1023);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.fffffffffffff8p-1023);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.fffffffffffff7p-1023);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.fffffffffffff800000001p-1023);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1p-1022);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x2p-1022);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x3p-1022);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x1.0p-1022);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x000000000000000000000000000000000001.0p-1022);
DOES_PARSE_HEX_DOUBLE_LIKE_CPP(0x000000000000000000000000000000000001.000000000000000000p-1022);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0xCap0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0xCAp0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0xcAp0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0xcAP0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0xcaP0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0xcap0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0xcap1);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0xca.p1);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0xc.ap1);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x1.p0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x11.p0);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x11.p1);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x11.p2);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x11.p-2);
DOES_PARSE_HEX_FLOAT_AND_DOUBLE_LIKE_CPP(0x11.p-0);
}
TEST_CASE(invalid_hex_floats)
{
#define EXPECT_HEX_PARSE_TO_VALUE_AND_CONSUME_CHARS(string_value, double_value, chars_parsed) \
do { \
char const* c_str = string_value; \
auto result = parse_first_hexfloat_until_zero_character<double>(c_str); \
EXPECT(result.error == AK::FloatingPointError::None); \
EXPECT_EQ(bit_cast<u64>(result.value), bit_cast<u64>(static_cast<double>(double_value))); \
EXPECT_EQ(result.end_ptr - c_str, chars_parsed); \
} while (false)
EXPECT_HEX_PARSE_TO_VALUE_AND_CONSUME_CHARS("0xab.cdpef", 0xab.cdp0, 7);
EXPECT_HEX_PARSE_TO_VALUE_AND_CONSUME_CHARS("0xab.cdPef", 0xab.cdp0, 7);
EXPECT_HEX_PARSE_TO_VALUE_AND_CONSUME_CHARS("0xab.cdPEf", 0xab.cdp0, 7);
EXPECT_HEX_PARSE_TO_VALUE_AND_CONSUME_CHARS("0xab.cdPEF", 0xab.cdp0, 7);
EXPECT_HEX_PARSE_TO_VALUE_AND_CONSUME_CHARS("0xAB.cdPEF", 0xab.cdp0, 7);
EXPECT_HEX_PARSE_TO_VALUE_AND_CONSUME_CHARS("0xABCDPEF", 0xabcdp0, 6);
EXPECT_HEX_PARSE_TO_VALUE_AND_CONSUME_CHARS("0xCAPE", 0xCAp0, 4);
}
#define BENCHMARK_DOUBLE_PARSING(value, iterations) \
do { \
auto data = #value##sv; \
@ -602,3 +426,207 @@ BENCHMARK_CASE(inadequate_float)
{
BENCHMARK_DOUBLE_PARSING(7.4109846876186981626485318930233205854758970392148714663837852375101326090531312779794975454245398856969484704316857659638998506553390969459816219401617281718945106978546710679176872575177347315553307795408549809608457500958111373034747658096871009590975442271004757307809711118935784838675653998783503015228055934046593739791790738723868299395818481660169122019456499931289798411362062484498678713572180352209017023903285791732520220528974020802906854021606612375549983402671300035812486479041385743401875520901590172592547146296175134159774938718574737870961645638908718119841271673056017045493004705269590165763776884908267986972573366521765567941072508764337560846003984904972149117463085539556354188641513168478436313080237596295773983001708984375001e-324, 4);
}
TEST_CASE(signed_integer)
{
auto value = AK::parse_number<int>(StringView());
EXPECT(!value.has_value());
value = AK::parse_number<int>(""sv);
EXPECT(!value.has_value());
value = AK::parse_number<int>("a"sv);
EXPECT(!value.has_value());
value = AK::parse_number<int>("+"sv);
EXPECT(!value.has_value());
value = AK::parse_number<int>("-"sv);
EXPECT(!value.has_value());
auto actual = AK::parse_number<int>("0"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0);
actual = AK::parse_number<int>("1"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 1);
actual = AK::parse_number<int>("+1"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 1);
actual = AK::parse_number<int>("-1"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), -1);
actual = AK::parse_number<int>("01"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 1);
actual = AK::parse_number<int>("12345"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 12345);
actual = AK::parse_number<int>("-12345"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), -12345);
actual = AK::parse_number<int>(" \t-12345 \n\n"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), -12345);
auto actual_i8 = AK::parse_number<i8>("-1"sv);
EXPECT(actual_i8.has_value());
EXPECT_EQ(actual_i8.value(), -1);
EXPECT_EQ(sizeof(actual_i8.value()), (size_t)1);
actual_i8 = AK::parse_number<i8>("128"sv);
EXPECT(!actual_i8.has_value());
auto actual_i16 = AK::parse_number<i16>("-1"sv);
EXPECT(actual_i16.has_value());
EXPECT_EQ(actual_i16.value(), -1);
EXPECT_EQ(sizeof(actual_i16.value()), (size_t)2);
actual_i16 = AK::parse_number<i16>("32768"sv);
EXPECT(!actual_i16.has_value());
auto actual_i32 = AK::parse_number<i32>("-1"sv);
EXPECT(actual_i32.has_value());
EXPECT_EQ(actual_i32.value(), -1);
EXPECT_EQ(sizeof(actual_i32.value()), (size_t)4);
actual_i32 = AK::parse_number<i32>("2147483648"sv);
EXPECT(!actual_i32.has_value());
auto actual_i64 = AK::parse_number<i64>("-1"sv);
EXPECT(actual_i64.has_value());
EXPECT_EQ(actual_i64.value(), -1);
EXPECT_EQ(sizeof(actual_i64.value()), (size_t)8);
actual_i64 = AK::parse_number<i64>("9223372036854775808"sv);
EXPECT(!actual_i64.has_value());
}
TEST_CASE(unsigned_integer)
{
auto value = AK::parse_number<unsigned>(StringView());
EXPECT(!value.has_value());
value = AK::parse_number<unsigned>(""sv);
EXPECT(!value.has_value());
value = AK::parse_number<unsigned>("a"sv);
EXPECT(!value.has_value());
value = AK::parse_number<unsigned>("+"sv);
EXPECT(!value.has_value());
value = AK::parse_number<unsigned>("-"sv);
EXPECT(!value.has_value());
value = AK::parse_number<unsigned>("+1"sv);
EXPECT(!value.has_value());
value = AK::parse_number<unsigned>("-1"sv);
EXPECT(!value.has_value());
auto actual = AK::parse_number<unsigned>("0"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0u);
actual = AK::parse_number<unsigned>("1"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 1u);
actual = AK::parse_number<unsigned>("01"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 1u);
actual = AK::parse_number<unsigned>("12345"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 12345u);
actual = AK::parse_number<unsigned>(" \t12345 \n\n"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 12345u);
auto actual_u8 = AK::parse_number<u8>("255"sv);
EXPECT(actual_u8.has_value());
EXPECT_EQ(actual_u8.value(), 255u);
EXPECT_EQ(sizeof(actual_u8.value()), (size_t)1);
actual_u8 = AK::parse_number<u8>("256"sv);
EXPECT(!actual_u8.has_value());
auto actual_u16 = AK::parse_number<u16>("65535"sv);
EXPECT(actual_u16.has_value());
EXPECT_EQ(actual_u16.value(), 65535u);
EXPECT_EQ(sizeof(actual_u16.value()), (size_t)2);
actual_u16 = AK::parse_number<u16>("65536"sv);
EXPECT(!actual_u16.has_value());
auto actual_u32 = AK::parse_number<u32>("4294967295"sv);
EXPECT(actual_u32.has_value());
EXPECT_EQ(actual_u32.value(), 4294967295ul);
EXPECT_EQ(sizeof(actual_u32.value()), (size_t)4);
actual_u32 = AK::parse_number<u32>("4294967296"sv);
EXPECT(!actual_u32.has_value());
auto actual_u64 = AK::parse_number<u64>("18446744073709551615"sv);
EXPECT(actual_u64.has_value());
EXPECT_EQ(actual_u64.value(), 18446744073709551615ull);
EXPECT_EQ(sizeof(actual_u64.value()), (size_t)8);
actual_u64 = AK::parse_number<u64>("18446744073709551616"sv);
EXPECT(!actual_u64.has_value());
}
TEST_CASE(octal)
{
auto value = AK::parse_number<u16>(StringView(), AK::TrimWhitespace::No, 8);
EXPECT(!value.has_value());
value = AK::parse_number<u16>(""sv, AK::TrimWhitespace::No, 8);
EXPECT(!value.has_value());
value = AK::parse_number<u16>("a"sv, AK::TrimWhitespace::No, 8);
EXPECT(!value.has_value());
value = AK::parse_number<u16>("+"sv, AK::TrimWhitespace::No, 8);
EXPECT(!value.has_value());
value = AK::parse_number<u16>("-"sv, AK::TrimWhitespace::No, 8);
EXPECT(!value.has_value());
value = AK::parse_number<u16>("+1"sv, AK::TrimWhitespace::No, 8);
EXPECT(!value.has_value());
value = AK::parse_number<u16>("-1"sv, AK::TrimWhitespace::No, 8);
EXPECT(!value.has_value());
value = AK::parse_number<u16>("8"sv, AK::TrimWhitespace::No, 8);
EXPECT(!value.has_value());
auto actual = AK::parse_number<u16>("77777777"sv, AK::TrimWhitespace::No, 8);
EXPECT(!actual.has_value());
actual = AK::parse_number<u16>("0"sv, AK::TrimWhitespace::No, 8);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0u);
actual = AK::parse_number<u16>("1"sv, AK::TrimWhitespace::No, 8);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 1u);
actual = AK::parse_number<u16>("0755"sv, AK::TrimWhitespace::No, 8);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0755u);
actual = AK::parse_number<u16>("755"sv, AK::TrimWhitespace::No, 8);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0755u);
actual = AK::parse_number<u16>(" \t644 \n\n"sv, AK::TrimWhitespace::Yes, 8);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0644u);
actual = AK::parse_number<u16>("177777"sv, AK::TrimWhitespace::No, 8);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0177777u);
}

View file

@ -111,217 +111,6 @@ TEST_CASE(match_trailing_backslash)
EXPECT(AK::StringUtils::matches("x\\"sv, "x\\\\"sv));
}
TEST_CASE(convert_to_int)
{
auto value = AK::StringUtils::convert_to_int(StringView());
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_int(""sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_int("a"sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_int("+"sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_int("-"sv);
EXPECT(!value.has_value());
auto actual = AK::StringUtils::convert_to_int("0"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0);
actual = AK::StringUtils::convert_to_int("1"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 1);
actual = AK::StringUtils::convert_to_int("+1"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 1);
actual = AK::StringUtils::convert_to_int("-1"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), -1);
actual = AK::StringUtils::convert_to_int("01"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 1);
actual = AK::StringUtils::convert_to_int("12345"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 12345);
actual = AK::StringUtils::convert_to_int("-12345"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), -12345);
actual = AK::StringUtils::convert_to_int(" \t-12345 \n\n"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), -12345);
auto actual_i8 = AK::StringUtils::convert_to_int<i8>("-1"sv);
EXPECT(actual_i8.has_value());
EXPECT_EQ(actual_i8.value(), -1);
EXPECT_EQ(sizeof(actual_i8.value()), (size_t)1);
actual_i8 = AK::StringUtils::convert_to_int<i8>("128"sv);
EXPECT(!actual_i8.has_value());
auto actual_i16 = AK::StringUtils::convert_to_int<i16>("-1"sv);
EXPECT(actual_i16.has_value());
EXPECT_EQ(actual_i16.value(), -1);
EXPECT_EQ(sizeof(actual_i16.value()), (size_t)2);
actual_i16 = AK::StringUtils::convert_to_int<i16>("32768"sv);
EXPECT(!actual_i16.has_value());
auto actual_i32 = AK::StringUtils::convert_to_int<i32>("-1"sv);
EXPECT(actual_i32.has_value());
EXPECT_EQ(actual_i32.value(), -1);
EXPECT_EQ(sizeof(actual_i32.value()), (size_t)4);
actual_i32 = AK::StringUtils::convert_to_int<i32>("2147483648"sv);
EXPECT(!actual_i32.has_value());
auto actual_i64 = AK::StringUtils::convert_to_int<i64>("-1"sv);
EXPECT(actual_i64.has_value());
EXPECT_EQ(actual_i64.value(), -1);
EXPECT_EQ(sizeof(actual_i64.value()), (size_t)8);
actual_i64 = AK::StringUtils::convert_to_int<i64>("9223372036854775808"sv);
EXPECT(!actual_i64.has_value());
}
TEST_CASE(convert_to_uint)
{
auto value = AK::StringUtils::convert_to_uint(StringView());
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint(""sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint("a"sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint("+"sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint("-"sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint("+1"sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint("-1"sv);
EXPECT(!value.has_value());
auto actual = AK::StringUtils::convert_to_uint("0"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0u);
actual = AK::StringUtils::convert_to_uint("1"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 1u);
actual = AK::StringUtils::convert_to_uint("01"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 1u);
actual = AK::StringUtils::convert_to_uint("12345"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 12345u);
actual = AK::StringUtils::convert_to_uint(" \t12345 \n\n"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 12345u);
auto actual_u8 = AK::StringUtils::convert_to_uint<u8>("255"sv);
EXPECT(actual_u8.has_value());
EXPECT_EQ(actual_u8.value(), 255u);
EXPECT_EQ(sizeof(actual_u8.value()), (size_t)1);
actual_u8 = AK::StringUtils::convert_to_uint<u8>("256"sv);
EXPECT(!actual_u8.has_value());
auto actual_u16 = AK::StringUtils::convert_to_uint<u16>("65535"sv);
EXPECT(actual_u16.has_value());
EXPECT_EQ(actual_u16.value(), 65535u);
EXPECT_EQ(sizeof(actual_u16.value()), (size_t)2);
actual_u16 = AK::StringUtils::convert_to_uint<u16>("65536"sv);
EXPECT(!actual_u16.has_value());
auto actual_u32 = AK::StringUtils::convert_to_uint<u32>("4294967295"sv);
EXPECT(actual_u32.has_value());
EXPECT_EQ(actual_u32.value(), 4294967295ul);
EXPECT_EQ(sizeof(actual_u32.value()), (size_t)4);
actual_u32 = AK::StringUtils::convert_to_uint<u32>("4294967296"sv);
EXPECT(!actual_u32.has_value());
auto actual_u64 = AK::StringUtils::convert_to_uint<u64>("18446744073709551615"sv);
EXPECT(actual_u64.has_value());
EXPECT_EQ(actual_u64.value(), 18446744073709551615ull);
EXPECT_EQ(sizeof(actual_u64.value()), (size_t)8);
actual_u64 = AK::StringUtils::convert_to_uint<u64>("18446744073709551616"sv);
EXPECT(!actual_u64.has_value());
}
TEST_CASE(convert_to_uint_from_octal)
{
auto value = AK::StringUtils::convert_to_uint_from_octal<u16>(StringView());
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint_from_octal<u16>(""sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint_from_octal<u16>("a"sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint_from_octal<u16>("+"sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint_from_octal<u16>("-"sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint_from_octal<u16>("+1"sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint_from_octal<u16>("-1"sv);
EXPECT(!value.has_value());
value = AK::StringUtils::convert_to_uint_from_octal<u16>("8"sv);
EXPECT(!value.has_value());
auto actual = AK::StringUtils::convert_to_uint_from_octal<u16>("77777777"sv);
EXPECT(!actual.has_value());
actual = AK::StringUtils::convert_to_uint_from_octal<u16>("0"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0u);
actual = AK::StringUtils::convert_to_uint_from_octal<u16>("1"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 1u);
actual = AK::StringUtils::convert_to_uint_from_octal<u16>("0755"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0755u);
actual = AK::StringUtils::convert_to_uint_from_octal<u16>("755"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0755u);
actual = AK::StringUtils::convert_to_uint_from_octal<u16>(" \t644 \n\n"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0644u);
actual = AK::StringUtils::convert_to_uint_from_octal<u16>("177777"sv);
EXPECT_EQ(actual.has_value(), true);
EXPECT_EQ(actual.value(), 0177777u);
}
TEST_CASE(convert_to_floating_point)
{
auto number_string = " 123.45 "sv;
auto maybe_number = AK::StringUtils::convert_to_floating_point<float>(number_string, TrimWhitespace::Yes);
EXPECT_APPROXIMATE(maybe_number.value(), 123.45f);
}
TEST_CASE(ends_with)
{
ByteString test_string = "ABCDEF";

View file

@ -25,6 +25,7 @@
"name": "dirent",
"platform": "windows"
},
"fast-float",
{
"name": "ffmpeg",
"platform": "!android",
@ -152,6 +153,10 @@
"name": "dirent",
"version": "1.24#0"
},
{
"name": "fast-float",
"version": "8.0.2#0"
},
{
"name": "ffmpeg",
"version": "7.1.1#2"