diff options
author | Linus Groh <mail@linusgroh.de> | 2021-07-28 18:36:52 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-07-28 21:57:30 +0100 |
commit | ae4bf8fc329bfaf23d112868167418904d97e761 (patch) | |
tree | 40658d5cd45b913ea72202156163487499ecd80f /Userland/Libraries | |
parent | 321f2c09270c7b7351170fa4884bb00521ad9459 (diff) | |
download | serenity-ae4bf8fc329bfaf23d112868167418904d97e761.zip |
LibJS: Start implementing Temporal.PlainTime
This commit adds the PlainTime object itself, its constructor and
prototype (currently empty), and the CreateTemporalTime abstract
operation.
Diffstat (limited to 'Userland/Libraries')
12 files changed, 347 insertions, 2 deletions
diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index aa5f064083..008ee31f03 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -141,6 +141,8 @@ set(SOURCES Runtime/Temporal/PlainDateTimeConstructor.cpp Runtime/Temporal/PlainDateTimePrototype.cpp Runtime/Temporal/PlainTime.cpp + Runtime/Temporal/PlainTimeConstructor.cpp + Runtime/Temporal/PlainTimePrototype.cpp Runtime/Temporal/PlainYearMonth.cpp Runtime/Temporal/Temporal.cpp Runtime/Temporal/TimeZone.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 27835581d9..4d73840641 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -82,6 +82,7 @@ __JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \ __JS_ENUMERATE(PlainDate, plain_date, PlainDatePrototype, PlainDateConstructor) \ __JS_ENUMERATE(PlainDateTime, plain_date_time, PlainDateTimePrototype, PlainDateTimeConstructor) \ + __JS_ENUMERATE(PlainTime, plain_time, PlainTimePrototype, PlainTimeConstructor) \ __JS_ENUMERATE(TimeZone, time_zone, TimeZonePrototype, TimeZoneConstructor) #define JS_ENUMERATE_ITERATOR_PROTOTYPES \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index cbc7c0fd22..39f366061d 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -177,6 +177,7 @@ M(TemporalInvalidOffsetNanosecondsValue, "Invalid offset nanoseconds value, must be in range -86400 * 10^9 to 86400 * 10^9") \ M(TemporalInvalidPlainDate, "Invalid plain date") \ M(TemporalInvalidPlainDateTime, "Invalid plain date time") \ + M(TemporalInvalidPlainTime, "Invalid plain time") \ M(TemporalInvalidTime, "Invalid time") \ M(TemporalInvalidTimeZoneName, "Invalid time zone name") \ M(TemporalMissingRequiredProperty, "Required property {} is missing or undefined") \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index fc096f4fd7..bdaaa84c07 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -79,6 +79,8 @@ #include <LibJS/Runtime/Temporal/PlainDatePrototype.h> #include <LibJS/Runtime/Temporal/PlainDateTimeConstructor.h> #include <LibJS/Runtime/Temporal/PlainDateTimePrototype.h> +#include <LibJS/Runtime/Temporal/PlainTimeConstructor.h> +#include <LibJS/Runtime/Temporal/PlainTimePrototype.h> #include <LibJS/Runtime/Temporal/Temporal.h> #include <LibJS/Runtime/Temporal/TimeZoneConstructor.h> #include <LibJS/Runtime/Temporal/TimeZonePrototype.h> diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp index 740256a6e1..e29a72f124 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp @@ -1,14 +1,37 @@ /* * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ +#include <LibJS/Runtime/AbstractOperations.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/Temporal/Calendar.h> #include <LibJS/Runtime/Temporal/PlainTime.h> -#include <LibJS/Runtime/Value.h> +#include <LibJS/Runtime/Temporal/PlainTimeConstructor.h> namespace JS::Temporal { +// 4 Temporal.PlainTime Objects, https://tc39.es/proposal-temporal/#sec-temporal-plaintime-objects +PlainTime::PlainTime(u8 iso_hour, u8 iso_minute, u8 iso_second, u16 iso_millisecond, u16 iso_microsecond, u16 iso_nanosecond, Calendar& calendar, Object& prototype) + : Object(prototype) + , m_iso_hour(iso_hour) + , m_iso_minute(iso_minute) + , m_iso_second(iso_second) + , m_iso_millisecond(iso_millisecond) + , m_iso_microsecond(iso_microsecond) + , m_iso_nanosecond(iso_nanosecond) + , m_calendar(calendar) +{ +} + +void PlainTime::visit_edges(Visitor& visitor) +{ + visitor.visit(&m_calendar); +} + // 4.5.5 IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidtime bool is_valid_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond) { @@ -107,4 +130,38 @@ DaysAndTime balance_time(i64 hour, i64 minute, i64 second, i64 millisecond, i64 }; } +// 4.5.8 CreateTemporalTime ( hour, minute, second, millisecond, microsecond, nanosecond [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltime +PlainTime* create_temporal_time(GlobalObject& global_object, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, FunctionObject* new_target) +{ + auto& vm = global_object.vm(); + + // 1. Assert: hour, minute, second, millisecond, microsecond and nanosecond are integers. + + // 2. If ! IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception. + if (!is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond)) { + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainTime); + return {}; + } + + // 3. If newTarget is not present, set it to %Temporal.PlainTime%. + if (!new_target) + new_target = global_object.temporal_plain_time_constructor(); + + // 4. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainTime.prototype%", ยซ [[InitializedTemporalTime]], [[ISOHour]], [[ISOMinute]], [[ISOSecond]], [[ISOMillisecond]], [[ISOMicrosecond]], [[ISONanosecond]], [[Calendar]] ยป). + // 5. Set object.[[ISOHour]] to hour. + // 6. Set object.[[ISOMinute]] to minute. + // 7. Set object.[[ISOSecond]] to second. + // 8. Set object.[[ISOMillisecond]] to millisecond. + // 9. Set object.[[ISOMicrosecond]] to microsecond. + // 10. Set object.[[ISONanosecond]] to nanosecond. + // 11. Set object.[[Calendar]] to ? GetISO8601Calendar(). + // NOTE: No exception check needed for GetISO8601Calendar, see https://github.com/tc39/proposal-temporal/pull/1643 + auto* object = ordinary_create_from_constructor<PlainTime>(global_object, *new_target, &GlobalObject::temporal_plain_time_prototype, hour, minute, second, millisecond, microsecond, nanosecond, *get_iso8601_calendar(global_object)); + if (vm.exception()) + return {}; + + // 12. Return object. + return object; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h index 1c85fa946f..fabf4c10df 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h @@ -1,15 +1,45 @@ /* * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once -#include <LibJS/Runtime/Value.h> +#include <LibJS/Runtime/Object.h> namespace JS::Temporal { +class PlainTime final : public Object { + JS_OBJECT(PlainDateTime, Object); + +public: + PlainTime(u8 iso_hour, u8 iso_minute, u8 iso_second, u16 iso_millisecond, u16 iso_microsecond, u16 iso_nanosecond, Calendar& calendar, Object& prototype); + virtual ~PlainTime() override = default; + + [[nodiscard]] u8 iso_hour() const { return m_iso_hour; } + [[nodiscard]] u8 iso_minute() const { return m_iso_minute; } + [[nodiscard]] u8 iso_second() const { return m_iso_second; } + [[nodiscard]] u16 iso_millisecond() const { return m_iso_millisecond; } + [[nodiscard]] u16 iso_microsecond() const { return m_iso_microsecond; } + [[nodiscard]] u16 iso_nanosecond() const { return m_iso_nanosecond; } + [[nodiscard]] Calendar const& calendar() const { return m_calendar; } + [[nodiscard]] Calendar& calendar() { return m_calendar; } + +private: + virtual void visit_edges(Visitor&) override; + + // 4.4 Properties of Temporal.PlainTime Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plaintime-instances + u8 m_iso_hour { 0 }; // [[ISOHour]] + u8 m_iso_minute { 0 }; // [[ISOMinute]] + u8 m_iso_second { 0 }; // [[ISOSecond]] + u16 m_iso_millisecond { 0 }; // [[ISOMillisecond]] + u16 m_iso_microsecond { 0 }; // [[ISOMicrosecond]] + u16 m_iso_nanosecond { 0 }; // [[ISONanosecond]] + Calendar& m_calendar; // [[Calendar]] (always the built-in ISO 8601 calendar) +}; + struct DaysAndTime { i32 days; u8 hour; @@ -22,5 +52,6 @@ struct DaysAndTime { bool is_valid_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond); DaysAndTime balance_time(i64 hour, i64 minute, i64 second, i64 millisecond, i64 microsecond, i64 nanosecond); +PlainTime* create_temporal_time(GlobalObject&, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, FunctionObject* new_target = nullptr); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimeConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimeConstructor.cpp new file mode 100644 index 0000000000..ba1c62aa74 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimeConstructor.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Temporal/PlainTime.h> +#include <LibJS/Runtime/Temporal/PlainTimeConstructor.h> + +namespace JS::Temporal { + +// 4.1 The Temporal.PlainTime Constructor, https://tc39.es/proposal-temporal/#sec-temporal-plaintime-constructor +PlainTimeConstructor::PlainTimeConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.PlainTime.as_string(), *global_object.function_prototype()) +{ +} + +void PlainTimeConstructor::initialize(GlobalObject& global_object) +{ + NativeFunction::initialize(global_object); + + auto& vm = this->vm(); + + // 4.2.1 Temporal.PlainTime.prototype, https://tc39.es/proposal-temporal/#sec-temporal-plaintime-prototype + define_direct_property(vm.names.prototype, global_object.temporal_plain_time_prototype(), 0); + + define_direct_property(vm.names.length, Value(0), Attribute::Configurable); +} + +// 4.1.1 Temporal.PlainTime ( [ hour [ , minute [ , second [ , millisecond [ , microsecond [ , nanosecond ] ] ] ] ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime +Value PlainTimeConstructor::call() +{ + auto& vm = this->vm(); + + // 1. If NewTarget is undefined, throw a TypeError exception. + vm.throw_exception<TypeError>(global_object(), ErrorType::ConstructorWithoutNew, "Temporal.PlainTime"); + return {}; +} + +// 4.1.1 Temporal.PlainTime ( [ hour [ , minute [ , second [ , millisecond [ , microsecond [ , nanosecond ] ] ] ] ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime +Value PlainTimeConstructor::construct(FunctionObject& new_target) +{ + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 2. Let hour be ? ToIntegerOrInfinity(hour). + auto hour = vm.argument(0).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 3. If hour is +โ or -โ, throw a RangeError exception. + if (Value(hour).is_infinity()) { + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainTime); + return {}; + } + + // 4. Let minute be ? ToIntegerOrInfinity(hour). + auto minute = vm.argument(1).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 5. If minute is +โ or -โ, throw a RangeError exception. + if (Value(minute).is_infinity()) { + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainTime); + return {}; + } + + // 6. Let second be ? ToIntegerOrInfinity(hour). + auto second = vm.argument(2).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 7. If second is +โ or -โ, throw a RangeError exception. + if (Value(second).is_infinity()) { + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainTime); + return {}; + } + + // 8. Let millisecond be ? ToIntegerOrInfinity(hour). + auto millisecond = vm.argument(3).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 9. If millisecond is +โ or -โ, throw a RangeError exception. + if (Value(millisecond).is_infinity()) { + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainTime); + return {}; + } + + // 10. Let microsecond be ? ToIntegerOrInfinity(hour). + auto microsecond = vm.argument(4).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 11. If microsecond is +โ or -โ, throw a RangeError exception. + if (Value(microsecond).is_infinity()) { + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainTime); + return {}; + } + + // 12. Let nanosecond be ? ToIntegerOrInfinity(hour). + auto nanosecond = vm.argument(5).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 13. If nanosecond is +โ or -โ, throw a RangeError exception. + if (Value(nanosecond).is_infinity()) { + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainTime); + return {}; + } + + // IMPLEMENTATION DEFINED: This is an optimization that allows us to treat these doubles as normal integers from this point onwards. + // This does not change the exposed behaviour as the call to CreateTemporalTime will immediately check that these values are valid + // ISO values (for hours: 0 - 23, for minutes and seconds: 0 - 59, milliseconds, microseconds, and nanoseconds: 0 - 999) all of which + // are subsets of this check. + if (!AK::is_within_range<u8>(hour) || !AK::is_within_range<u8>(minute) || !AK::is_within_range<u8>(second) || !AK::is_within_range<u16>(millisecond) || !AK::is_within_range<u16>(microsecond) || !AK::is_within_range<u16>(nanosecond)) { + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainTime); + return {}; + } + + // 14. Return ? CreateTemporalTime(hour, minute, second, millisecond, microsecond, nanosecond, NewTarget). + return create_temporal_time(global_object, hour, minute, second, millisecond, microsecond, nanosecond, &new_target); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimeConstructor.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimeConstructor.h new file mode 100644 index 0000000000..2ecd73e123 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimeConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibJS/Runtime/NativeFunction.h> + +namespace JS::Temporal { + +class PlainTimeConstructor final : public NativeFunction { + JS_OBJECT(PlainTimeConstructor, NativeFunction); + +public: + explicit PlainTimeConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~PlainTimeConstructor() override = default; + + virtual Value call() override; + virtual Value construct(FunctionObject& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp new file mode 100644 index 0000000000..f12b3a6993 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Temporal/PlainTimePrototype.h> + +namespace JS::Temporal { + +// 4.3 Properties of the Temporal.PlainTime Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-plaintime-prototype-object +PlainTimePrototype::PlainTimePrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void PlainTimePrototype::initialize(GlobalObject& global_object) +{ + Object::initialize(global_object); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.h new file mode 100644 index 0000000000..61fc38fc99 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibJS/Runtime/Object.h> + +namespace JS::Temporal { + +class PlainTimePrototype final : public Object { + JS_OBJECT(PlainTimePrototype, Object); + +public: + explicit PlainTimePrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~PlainTimePrototype() override = default; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp index 1a8b5b16db..1cb1e0dcc7 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp @@ -11,6 +11,7 @@ #include <LibJS/Runtime/Temporal/Now.h> #include <LibJS/Runtime/Temporal/PlainDateConstructor.h> #include <LibJS/Runtime/Temporal/PlainDateTimeConstructor.h> +#include <LibJS/Runtime/Temporal/PlainTimeConstructor.h> #include <LibJS/Runtime/Temporal/Temporal.h> #include <LibJS/Runtime/Temporal/TimeZoneConstructor.h> @@ -38,6 +39,7 @@ void Temporal::initialize(GlobalObject& global_object) define_direct_property(vm.names.Instant, global_object.temporal_instant_constructor(), attr); define_direct_property(vm.names.PlainDate, global_object.temporal_plain_date_constructor(), attr); define_direct_property(vm.names.PlainDateTime, global_object.temporal_plain_date_time_constructor(), attr); + define_direct_property(vm.names.PlainTime, global_object.temporal_plain_time_constructor(), attr); define_direct_property(vm.names.TimeZone, global_object.temporal_time_zone_constructor(), attr); } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainTime/PlainTime.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainTime/PlainTime.js new file mode 100644 index 0000000000..bc5ccf9922 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainTime/PlainTime.js @@ -0,0 +1,50 @@ +describe("errors", () => { + test("called without new", () => { + expect(() => { + Temporal.PlainTime(); + }).toThrowWithMessage( + TypeError, + "Temporal.PlainTime constructor must be called with 'new'" + ); + }); + + test("cannot pass Infinity", () => { + for (let i = 0; i < 6; ++i) { + const args = Array(6).fill(0); + + args[i] = Infinity; + expect(() => { + new Temporal.PlainTime(...args); + }).toThrowWithMessage(RangeError, "Invalid plain time"); + + args[i] = -Infinity; + expect(() => { + new Temporal.PlainTime(...args); + }).toThrowWithMessage(RangeError, "Invalid plain time"); + } + }); + + test("cannot pass invalid ISO time", () => { + const badValues = [24, 60, 60, 1000, 1000, 1000]; + for (let i = 0; i < 6; ++i) { + const args = [0, 0, 0, 0, 0, 0]; + args[i] = badValues[i]; + expect(() => { + new Temporal.PlainTime(...args); + }).toThrowWithMessage(RangeError, "Invalid plain time"); + } + }); +}); + +describe("normal behavior", () => { + test("length is 0", () => { + expect(Temporal.PlainTime).toHaveLength(0); + }); + + test("basic functionality", () => { + const plainTime = new Temporal.PlainTime(19, 46, 32, 123, 456, 789); + expect(typeof plainTime).toBe("object"); + expect(plainTime).toBeInstanceOf(Temporal.PlainTime); + expect(Object.getPrototypeOf(plainTime)).toBe(Temporal.PlainTime.prototype); + }); +}); |