From c8d24042300c5552e6b70ba924e68acfa4a91dc2 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sat, 16 Nov 2024 13:14:14 -0500 Subject: [PATCH] LibJS: Update spec steps for the few remaining Temporal AOs --- .../Runtime/Temporal/AbstractOperations.cpp | 43 ++++++------- .../Runtime/Temporal/AbstractOperations.h | 40 ++++++------ Libraries/LibJS/Runtime/Temporal/Duration.cpp | 64 +++++++++---------- Libraries/LibJS/Runtime/Temporal/Duration.h | 2 +- 4 files changed, 68 insertions(+), 81 deletions(-) diff --git a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index d808b31893d..462bf1abdd5 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2021-2022, Idan Horowitz * Copyright (c) 2021-2023, Linus Groh * Copyright (c) 2021, Luke Wilde + * Copyright (c) 2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -11,7 +12,7 @@ namespace JS::Temporal { -// 13.2 GetOptionsObject ( options ), https://tc39.es/proposal-temporal/#sec-getoptionsobject +// 14.4.1.1 GetOptionsObject ( options ), https://tc39.es/proposal-temporal/#sec-getoptionsobject ThrowCompletionOr get_options_object(VM& vm, Value options) { auto& realm = *vm.current_realm(); @@ -22,7 +23,7 @@ ThrowCompletionOr get_options_object(VM& vm, Value options) return Object::create(realm, nullptr).ptr(); } - // 2. If Type(options) is Object, then + // 2. If options is an Object, then if (options.is_object()) { // a. Return options. return &options.as_object(); @@ -32,7 +33,7 @@ ThrowCompletionOr get_options_object(VM& vm, Value options) return vm.throw_completion(ErrorType::NotAnObject, "Options"); } -// 13.3 GetOption ( options, property, type, values, fallback ), https://tc39.es/proposal-temporal/#sec-getoption +// 14.4.1.2 GetOption ( options, property, type, values, default ), https://tc39.es/proposal-temporal/#sec-getoption ThrowCompletionOr get_option(VM& vm, Object const& options, PropertyKey const& property, OptionType type, ReadonlySpan values, OptionDefault const& default_) { VERIFY(property.is_string()); @@ -42,51 +43,43 @@ ThrowCompletionOr get_option(VM& vm, Object const& options, PropertyKey c // 2. If value is undefined, then if (value.is_undefined()) { - // a. If default is required, throw a RangeError exception. - if (default_.has()) + // a. If default is REQUIRED, throw a RangeError exception. + if (default_.has()) return vm.throw_completion(ErrorType::OptionIsNotValidValue, "undefined"sv, property.as_string()); // b. Return default. return default_.visit( - [](GetOptionRequired) -> ThrowCompletionOr { VERIFY_NOT_REACHED(); }, - [](Empty) -> ThrowCompletionOr { return js_undefined(); }, - [](bool b) -> ThrowCompletionOr { return Value(b); }, - [](double d) -> ThrowCompletionOr { return Value(d); }, - [&vm](StringView s) -> ThrowCompletionOr { return PrimitiveString::create(vm, s); }); + [](DefaultRequired) -> Value { VERIFY_NOT_REACHED(); }, + [](Empty) -> Value { return js_undefined(); }, + [](bool default_) -> Value { return Value { default_ }; }, + [](double default_) -> Value { return Value { default_ }; }, + [&](StringView default_) -> Value { return PrimitiveString::create(vm, default_); }); } - // 5. If type is "boolean", then + // 3. If type is BOOLEAN, then if (type == OptionType::Boolean) { // a. Set value to ToBoolean(value). - value = Value(value.to_boolean()); + value = Value { value.to_boolean() }; } - // 6. Else if type is "number", then - else if (type == OptionType::Number) { - // a. Set value to ? ToNumber(value). - value = TRY(value.to_number(vm)); - - // b. If value is NaN, throw a RangeError exception. - if (value.is_nan()) - return vm.throw_completion(ErrorType::OptionIsNotValidValue, vm.names.NaN.as_string(), property.as_string()); - } - // 7. Else, + // 4. Else, else { - // a. Assert: type is "string". + // a. Assert: type is STRING. VERIFY(type == OptionType::String); // b. Set value to ? ToString(value). value = TRY(value.to_primitive_string(vm)); } - // 8. If values is not undefined and values does not contain an element equal to value, throw a RangeError exception. + // 5. If values is not EMPTY and values does not contain value, throw a RangeError exception. if (!values.is_empty()) { // NOTE: Every location in the spec that invokes GetOption with type=boolean also has values=undefined. VERIFY(value.is_string()); + if (auto value_string = value.as_string().utf8_string(); !values.contains_slow(value_string)) return vm.throw_completion(ErrorType::OptionIsNotValidValue, value_string, property.as_string()); } - // 9. Return value. + // 6. Return value. return value; } diff --git a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index 9a9834e29ea..fbd42458fa9 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2021, Idan Horowitz * Copyright (c) 2021-2023, Linus Groh + * Copyright (c) 2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -15,14 +16,28 @@ namespace JS::Temporal { +// 13.39 ToIntegerIfIntegral ( argument ), https://tc39.es/proposal-temporal/#sec-tointegerifintegral +template +ThrowCompletionOr to_integer_if_integral(VM& vm, Value argument, ErrorType error_type, Args&&... args) +{ + // 1. Let number be ? ToNumber(argument). + auto number = TRY(argument.to_number(vm)); + + // 2. If number is not an integral Number, throw a RangeError exception. + if (!number.is_integral_number()) + return vm.throw_completion(error_type, forward(args)...); + + // 3. Return โ„(number). + return number.as_double(); +} + enum class OptionType { Boolean, String, - Number }; -struct GetOptionRequired { }; -using OptionDefault = Variant; +struct DefaultRequired { }; +using OptionDefault = Variant; ThrowCompletionOr get_options_object(VM&, Value options); ThrowCompletionOr get_option(VM&, Object const& options, PropertyKey const& property, OptionType type, ReadonlySpan values, OptionDefault const&); @@ -33,23 +48,4 @@ ThrowCompletionOr get_option(VM& vm, Object const& options, PropertyKey c return get_option(vm, options, property, type, ReadonlySpan { values }, default_); } -// 13.41 ToIntegerIfIntegral ( argument ), https://tc39.es/proposal-temporal/#sec-tointegerifintegral -template -ThrowCompletionOr to_integer_if_integral(VM& vm, Value argument, ErrorType error_type, Args... args) -{ - // 1. Let number be ? ToNumber(argument). - auto number = TRY(argument.to_number(vm)); - - // 2. If number is NaN, +0๐”ฝ, or -0๐”ฝ, return 0. - if (number.is_nan() || number.is_positive_zero() || number.is_negative_zero()) - return 0; - - // 3. If IsIntegralNumber(number) is false, throw a RangeError exception. - if (!number.is_integral_number()) - return vm.template throw_completion(error_type, args...); - - // 4. Return โ„(number). - return number.as_double(); -} - } diff --git a/Libraries/LibJS/Runtime/Temporal/Duration.cpp b/Libraries/LibJS/Runtime/Temporal/Duration.cpp index 7df784d155c..26273329f4f 100644 --- a/Libraries/LibJS/Runtime/Temporal/Duration.cpp +++ b/Libraries/LibJS/Runtime/Temporal/Duration.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2021-2023, Linus Groh * Copyright (c) 2021, Luke Wilde * Copyright (c) 2024, Shannon Booth + * Copyright (c) 2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -15,43 +16,36 @@ namespace JS::Temporal { -// 7.5.10 DurationSign ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-durationsign -i8 duration_sign(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds) -{ - // 1. For each value v of ยซ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ยป, do - for (auto& v : { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }) { - // a. If v < 0, return -1. - if (v < 0) - return -1; - - // b. If v > 0, return 1. - if (v > 0) - return 1; - } - - // 2. Return 0. - return 0; -} - -// 7.5.11 IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidduration +// 7.5.16 IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidduration bool is_valid_duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds) { - // 1. Let sign be ! DurationSign(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - auto sign = duration_sign(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); + // 1. Let sign be 0. + auto sign = 0; // 2. For each value v of ยซ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ยป, do - for (auto& v : { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }) { + for (auto value : { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }) { // a. If ๐”ฝ(v) is not finite, return false. - if (!isfinite(v)) + if (!isfinite(value)) return false; - // b. If v < 0 and sign > 0, return false. - if (v < 0 && sign > 0) - return false; + // b. If v < 0, then + if (value < 0) { + // i. If sign > 0, return false. + if (sign > 0) + return false; - // c. If v > 0 and sign < 0, return false. - if (v > 0 && sign < 0) - return false; + // ii. Set sign to -1. + sign = -1; + } + // c. Else if v > 0, then + else if (value > 0) { + // i. If sign < 0, return false. + if (sign < 0) + return false; + + // ii. Set sign to 1. + sign = 1; + } } // 3. If abs(years) โ‰ฅ 2**32, return false. @@ -66,8 +60,12 @@ bool is_valid_duration(double years, double months, double weeks, double days, d if (AK::fabs(weeks) > NumericLimits::max()) return false; - // 6. Let normalizedSeconds be days ร— 86,400 + hours ร— 3600 + minutes ร— 60 + seconds + โ„(๐”ฝ(milliseconds)) ร— 10**-3 + โ„(๐”ฝ(microseconds)) ร— 10**-6 + โ„(๐”ฝ(nanoseconds)) ร— 10**-9. - // 7. NOTE: The above step cannot be implemented directly using floating-point arithmetic. Multiplying by 10**-3, 10**-6, and 10**-9 respectively may be imprecise when milliseconds, microseconds, or nanoseconds is an unsafe integer. This multiplication can be implemented in C++ with an implementation of std::remquo() with sufficient bits in the quotient. String manipulation will also give an exact result, since the multiplication is by a power of 10. + // 6. Let totalFractionalSeconds be days ร— 86,400 + hours ร— 3600 + minutes ร— 60 + seconds + โ„(๐”ฝ(milliseconds)) ร— 10**-3 + โ„(๐”ฝ(microseconds)) ร— 10**-6 + โ„(๐”ฝ(nanoseconds)) ร— 10**-9. + // 7. NOTE: The above step cannot be implemented directly using floating-point arithmetic. Multiplying by 10**-3, + // 10**-6, and 10**-9 respectively may be imprecise when milliseconds, microseconds, or nanoseconds is an + // unsafe integer. This multiplication can be implemented in C++ with an implementation of std::remquo() + // with sufficient bits in the quotient. String manipulation will also give an exact result, since the + // multiplication is by a power of 10. static Crypto::SignedBigInteger days_to_nanoseconds { 8.64e13 }; static Crypto::SignedBigInteger hours_to_nanoseconds { 3.6e12 }; static Crypto::SignedBigInteger minutes_to_nanoseconds { 6e10 }; @@ -84,7 +82,7 @@ bool is_valid_duration(double years, double months, double weeks, double days, d normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { nanoseconds }); // 8. If abs(normalizedSeconds) โ‰ฅ 2**53, return false. - static auto maximum_time = Crypto::SignedBigInteger { MAX_ARRAY_LIKE_INDEX }.plus(Crypto::SignedBigInteger { 1 }).multiplied_by(seconds_to_nanoseconds); + static auto maximum_time = Crypto::SignedBigInteger { MAX_ARRAY_LIKE_INDEX }.plus(1_bigint).multiplied_by(seconds_to_nanoseconds); if (normalized_nanoseconds.is_negative()) normalized_nanoseconds.negate(); @@ -92,7 +90,7 @@ bool is_valid_duration(double years, double months, double weeks, double days, d if (normalized_nanoseconds >= maximum_time) return false; - // 3. Return true. + // 9. Return true. return true; } diff --git a/Libraries/LibJS/Runtime/Temporal/Duration.h b/Libraries/LibJS/Runtime/Temporal/Duration.h index a38c13e75fb..e8c7769df65 100644 --- a/Libraries/LibJS/Runtime/Temporal/Duration.h +++ b/Libraries/LibJS/Runtime/Temporal/Duration.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2021-2023, Linus Groh * Copyright (c) 2024, Shannon Booth + * Copyright (c) 2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -11,7 +12,6 @@ namespace JS::Temporal { -i8 duration_sign(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds); bool is_valid_duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds); }