mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 00:17:46 +00:00
LibJS: Handle non-decimal integer literals in Value::to_number
Implements support for parsing binary and octal literals, and fixes instances where a hex literal is parsed in ways the spec doesn't allow.
This commit is contained in:
parent
583c0dc782
commit
f4b3bb519f
2 changed files with 172 additions and 19 deletions
|
@ -471,6 +471,92 @@ FLATTEN ThrowCompletionOr<Value> Value::to_numeric(VM& vm) const
|
|||
return primitive.to_number(vm);
|
||||
}
|
||||
|
||||
constexpr bool is_ascii_number(u32 code_point)
|
||||
{
|
||||
return is_ascii_digit(code_point) || code_point == '.' || (code_point == 'e' || code_point == 'E') || code_point == '+' || code_point == '-';
|
||||
}
|
||||
|
||||
struct NumberParseResult {
|
||||
StringView literal;
|
||||
u8 base;
|
||||
};
|
||||
|
||||
static Optional<NumberParseResult> parse_number_text(StringView text)
|
||||
{
|
||||
NumberParseResult result {};
|
||||
|
||||
auto check_prefix = [&](auto lower_prefix, auto upper_prefix) {
|
||||
if (text.length() <= 2)
|
||||
return false;
|
||||
if (!text.starts_with(lower_prefix) && !text.starts_with(upper_prefix))
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
// https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type
|
||||
if (check_prefix("0b"sv, "0B"sv)) {
|
||||
if (!all_of(text.substring_view(2), is_ascii_binary_digit))
|
||||
return {};
|
||||
|
||||
result.literal = text.substring_view(2);
|
||||
result.base = 2;
|
||||
} else if (check_prefix("0o"sv, "0O"sv)) {
|
||||
if (!all_of(text.substring_view(2), is_ascii_octal_digit))
|
||||
return {};
|
||||
|
||||
result.literal = text.substring_view(2);
|
||||
result.base = 8;
|
||||
} else if (check_prefix("0x"sv, "0X"sv)) {
|
||||
if (!all_of(text.substring_view(2), is_ascii_hex_digit))
|
||||
return {};
|
||||
|
||||
result.literal = text.substring_view(2);
|
||||
result.base = 16;
|
||||
} else {
|
||||
if (!all_of(text, is_ascii_number))
|
||||
return {};
|
||||
|
||||
result.literal = text;
|
||||
result.base = 10;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 7.1.4.1.1 StringToNumber ( str ), https://tc39.es/ecma262/#sec-stringtonumber
|
||||
static Optional<Value> string_to_number(StringView string)
|
||||
{
|
||||
// 1. Let text be StringToCodePoints(str).
|
||||
String text = string.trim_whitespace();
|
||||
|
||||
// 2. Let literal be ParseText(text, StringNumericLiteral).
|
||||
if (text.is_empty())
|
||||
return Value(0);
|
||||
if (text == "Infinity" || text == "+Infinity")
|
||||
return js_infinity();
|
||||
if (text == "-Infinity")
|
||||
return js_negative_infinity();
|
||||
|
||||
auto result = parse_number_text(text);
|
||||
|
||||
// 3. If literal is a List of errors, return NaN.
|
||||
if (!result.has_value())
|
||||
return js_nan();
|
||||
|
||||
// 4. Return StringNumericValue of literal.
|
||||
if (result->base != 10) {
|
||||
auto bigint = Crypto::UnsignedBigInteger::from_base(result->base, result->literal);
|
||||
return Value(bigint.to_double());
|
||||
}
|
||||
|
||||
char* endptr;
|
||||
auto parsed_double = strtod(text.characters(), &endptr);
|
||||
if (*endptr)
|
||||
return js_nan();
|
||||
|
||||
return Value(parsed_double);
|
||||
}
|
||||
|
||||
// 7.1.4 ToNumber ( argument ), https://tc39.es/ecma262/#sec-tonumber
|
||||
ThrowCompletionOr<Value> Value::to_number(VM& vm) const
|
||||
{
|
||||
|
@ -485,25 +571,8 @@ ThrowCompletionOr<Value> Value::to_number(VM& vm) const
|
|||
return Value(0);
|
||||
case BOOLEAN_TAG:
|
||||
return Value(as_bool() ? 1 : 0);
|
||||
case STRING_TAG: {
|
||||
String string = Utf8View(as_string().string()).trim(whitespace_characters, AK::TrimMode::Both).as_string();
|
||||
if (string.is_empty())
|
||||
return Value(0);
|
||||
if (string == "Infinity" || string == "+Infinity")
|
||||
return js_infinity();
|
||||
if (string == "-Infinity")
|
||||
return js_negative_infinity();
|
||||
char* endptr;
|
||||
auto parsed_double = strtod(string.characters(), &endptr);
|
||||
if (*endptr)
|
||||
return js_nan();
|
||||
// NOTE: Per the spec only exactly [+-]Infinity should result in infinity
|
||||
// but strtod gives infinity for any case-insensitive 'infinity' or 'inf' string.
|
||||
if (isinf(parsed_double) && string.contains('i', AK::CaseSensitivity::CaseInsensitive))
|
||||
return js_nan();
|
||||
|
||||
return Value(parsed_double);
|
||||
}
|
||||
case STRING_TAG:
|
||||
return string_to_number(as_string().string().view());
|
||||
case SYMBOL_TAG:
|
||||
return vm.throw_completion<TypeError>(ErrorType::Convert, "symbol", "number");
|
||||
case BIGINT_TAG:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue