mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 13:27:35 +00:00
LibJS: Implement conversion of strings to BigInts according to the spec
The spec defines a StringToBigInt AO which allows for converting binary, octal, decimal, and hexadecimal strings to a BigInt. Our conversion was only allowing for decimal strings.
This commit is contained in:
parent
94a346c9b9
commit
281b0411f2
3 changed files with 141 additions and 4 deletions
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/AllOf.h>
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Utf8View.h>
|
||||
|
@ -528,10 +529,15 @@ ThrowCompletionOr<BigInt*> Value::to_bigint(GlobalObject& global_object) const
|
|||
case Type::Double:
|
||||
return vm.throw_completion<TypeError>(global_object, ErrorType::Convert, "number", "BigInt");
|
||||
case Type::String: {
|
||||
auto& string = primitive.as_string().string();
|
||||
if (!is_valid_bigint_value(string))
|
||||
return vm.throw_completion<SyntaxError>(global_object, ErrorType::BigIntInvalidValue, string);
|
||||
return js_bigint(vm, Crypto::SignedBigInteger::from_base(10, string.trim_whitespace()));
|
||||
// 1. Let n be ! StringToBigInt(prim).
|
||||
auto bigint = primitive.string_to_bigint(global_object);
|
||||
|
||||
// 2. If n is undefined, throw a SyntaxError exception.
|
||||
if (!bigint.has_value())
|
||||
return vm.throw_completion<SyntaxError>(global_object, ErrorType::BigIntInvalidValue, primitive);
|
||||
|
||||
// 3. Return n.
|
||||
return bigint.release_value();
|
||||
}
|
||||
case Type::Symbol:
|
||||
return vm.throw_completion<TypeError>(global_object, ErrorType::Convert, "symbol", "BigInt");
|
||||
|
@ -540,6 +546,76 @@ ThrowCompletionOr<BigInt*> Value::to_bigint(GlobalObject& global_object) const
|
|||
}
|
||||
}
|
||||
|
||||
struct BigIntParseResult {
|
||||
StringView literal;
|
||||
u8 base { 10 };
|
||||
bool is_negative { false };
|
||||
};
|
||||
|
||||
static Optional<BigIntParseResult> parse_bigint_text(StringView text)
|
||||
{
|
||||
BigIntParseResult result {};
|
||||
|
||||
auto parse_for_prefixed_base = [&](auto lower_prefix, auto upper_prefix, auto validator) {
|
||||
if (text.length() <= 2)
|
||||
return false;
|
||||
if (!text.starts_with(lower_prefix) && !text.starts_with(upper_prefix))
|
||||
return false;
|
||||
return all_of(text.substring_view(2), validator);
|
||||
};
|
||||
|
||||
if (parse_for_prefixed_base("0b"sv, "0B"sv, is_ascii_binary_digit)) {
|
||||
result.literal = text.substring_view(2);
|
||||
result.base = 2;
|
||||
} else if (parse_for_prefixed_base("0o"sv, "0O"sv, is_ascii_octal_digit)) {
|
||||
result.literal = text.substring_view(2);
|
||||
result.base = 8;
|
||||
} else if (parse_for_prefixed_base("0x"sv, "0X"sv, is_ascii_hex_digit)) {
|
||||
result.literal = text.substring_view(2);
|
||||
result.base = 16;
|
||||
} else {
|
||||
if (text.starts_with('-')) {
|
||||
text = text.substring_view(1);
|
||||
result.is_negative = true;
|
||||
} else if (text.starts_with('+')) {
|
||||
text = text.substring_view(1);
|
||||
}
|
||||
|
||||
if (!all_of(text, is_ascii_digit))
|
||||
return {};
|
||||
|
||||
result.literal = text;
|
||||
result.base = 10;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 7.1.14 StringToBigInt ( str ), https://tc39.es/ecma262/#sec-stringtobigint
|
||||
Optional<BigInt*> Value::string_to_bigint(GlobalObject& global_object) const
|
||||
{
|
||||
VERIFY(is_string());
|
||||
|
||||
// 1. Let text be ! StringToCodePoints(str).
|
||||
auto text = as_string().string().view().trim_whitespace();
|
||||
|
||||
// 2. Let literal be ParseText(text, StringIntegerLiteral).
|
||||
auto result = parse_bigint_text(text);
|
||||
|
||||
// 3. If literal is a List of errors, return undefined.
|
||||
if (!result.has_value())
|
||||
return {};
|
||||
|
||||
// 4. Let mv be the MV of literal.
|
||||
// 5. Assert: mv is an integer.
|
||||
auto bigint = Crypto::SignedBigInteger::from_base(result->base, result->literal);
|
||||
if (result->is_negative && (bigint != BIGINT_ZERO))
|
||||
bigint.negate();
|
||||
|
||||
// 6. Return ℤ(mv).
|
||||
return js_bigint(global_object.vm(), move(bigint));
|
||||
}
|
||||
|
||||
// 7.1.15 ToBigInt64 ( argument ), https://tc39.es/ecma262/#sec-tobigint64
|
||||
ThrowCompletionOr<i64> Value::to_bigint_int64(GlobalObject& global_object) const
|
||||
{
|
||||
|
|
|
@ -333,6 +333,7 @@ public:
|
|||
ThrowCompletionOr<FunctionObject*> get_method(GlobalObject&, PropertyKey const&) const;
|
||||
|
||||
String to_string_without_side_effects() const;
|
||||
Optional<BigInt*> string_to_bigint(GlobalObject& global_object) const;
|
||||
|
||||
Value value_or(Value fallback) const
|
||||
{
|
||||
|
|
|
@ -29,6 +29,40 @@ describe("correct behavior", () => {
|
|||
test("constructor with objects", () => {
|
||||
expect(BigInt([])).toBe(0n);
|
||||
});
|
||||
|
||||
test("base-2 strings", () => {
|
||||
expect(BigInt("0b0")).toBe(0n);
|
||||
expect(BigInt("0B0")).toBe(0n);
|
||||
expect(BigInt("0b1")).toBe(1n);
|
||||
expect(BigInt("0B1")).toBe(1n);
|
||||
expect(BigInt("0b10")).toBe(2n);
|
||||
expect(BigInt("0B10")).toBe(2n);
|
||||
expect(BigInt(`0b${"1".repeat(100)}`)).toBe(1267650600228229401496703205375n);
|
||||
});
|
||||
|
||||
test("base-8 strings", () => {
|
||||
expect(BigInt("0o0")).toBe(0n);
|
||||
expect(BigInt("0O0")).toBe(0n);
|
||||
expect(BigInt("0o1")).toBe(1n);
|
||||
expect(BigInt("0O1")).toBe(1n);
|
||||
expect(BigInt("0o7")).toBe(7n);
|
||||
expect(BigInt("0O7")).toBe(7n);
|
||||
expect(BigInt("0o10")).toBe(8n);
|
||||
expect(BigInt("0O10")).toBe(8n);
|
||||
expect(BigInt(`0o1${"7".repeat(33)}`)).toBe(1267650600228229401496703205375n);
|
||||
});
|
||||
|
||||
test("base-16 strings", () => {
|
||||
expect(BigInt("0x0")).toBe(0n);
|
||||
expect(BigInt("0X0")).toBe(0n);
|
||||
expect(BigInt("0x1")).toBe(1n);
|
||||
expect(BigInt("0X1")).toBe(1n);
|
||||
expect(BigInt("0xf")).toBe(15n);
|
||||
expect(BigInt("0Xf")).toBe(15n);
|
||||
expect(BigInt("0x10")).toBe(16n);
|
||||
expect(BigInt("0X10")).toBe(16n);
|
||||
expect(BigInt(`0x${"f".repeat(25)}`)).toBe(1267650600228229401496703205375n);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
|
@ -65,4 +99,30 @@ describe("errors", () => {
|
|||
}).toThrowWithMessage(RangeError, "Cannot convert non-integral number to BigInt");
|
||||
});
|
||||
});
|
||||
|
||||
test("invalid string for base", () => {
|
||||
["0b", "0b2", "0B02", "-0b1", "-0B1"].forEach(value => {
|
||||
expect(() => {
|
||||
BigInt(value);
|
||||
}).toThrowWithMessage(SyntaxError, `Invalid value for BigInt: ${value}`);
|
||||
});
|
||||
|
||||
["0o", "0o8", "0O08", "-0o1", "-0O1"].forEach(value => {
|
||||
expect(() => {
|
||||
BigInt(value);
|
||||
}).toThrowWithMessage(SyntaxError, `Invalid value for BigInt: ${value}`);
|
||||
});
|
||||
|
||||
["0x", "0xg", "0X0g", "-0x1", "-0X1"].forEach(value => {
|
||||
expect(() => {
|
||||
BigInt(value);
|
||||
}).toThrowWithMessage(SyntaxError, `Invalid value for BigInt: ${value}`);
|
||||
});
|
||||
|
||||
["a", "-1a"].forEach(value => {
|
||||
expect(() => {
|
||||
BigInt(value);
|
||||
}).toThrowWithMessage(SyntaxError, `Invalid value for BigInt: ${value}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue