From 7921d8ba916fc578b12c416bb05fead11827493d Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Thu, 15 Jul 2021 23:20:43 +0100 Subject: [PATCH] LibJS: Start implementing Temporal.Duration This patch adds the Duration object itself, its constructor and prototype (currently empty), and three required abstract operations. --- Userland/Libraries/LibJS/CMakeLists.txt | 3 + Userland/Libraries/LibJS/Forward.h | 1 + Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 1 + .../Libraries/LibJS/Runtime/GlobalObject.cpp | 2 + .../LibJS/Runtime/Temporal/Duration.cpp | 107 ++++++++++++++++++ .../LibJS/Runtime/Temporal/Duration.h | 50 ++++++++ .../Runtime/Temporal/DurationConstructor.cpp | 102 +++++++++++++++++ .../Runtime/Temporal/DurationConstructor.h | 28 +++++ .../Runtime/Temporal/DurationPrototype.cpp | 23 ++++ .../Runtime/Temporal/DurationPrototype.h | 22 ++++ .../LibJS/Runtime/Temporal/Temporal.cpp | 2 + .../LibJS/Runtime/Temporal/TimeZone.h | 2 +- .../builtins/Temporal/Duration/Duration.js | 35 ++++++ 13 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/Duration.h create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/DurationConstructor.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/DurationConstructor.h create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.h create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.js diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index a289b85195..2e743f4ded 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -125,6 +125,9 @@ set(SOURCES Runtime/Temporal/Calendar.cpp Runtime/Temporal/CalendarConstructor.cpp Runtime/Temporal/CalendarPrototype.cpp + Runtime/Temporal/Duration.cpp + Runtime/Temporal/DurationConstructor.cpp + Runtime/Temporal/DurationPrototype.cpp Runtime/Temporal/Instant.cpp Runtime/Temporal/InstantConstructor.cpp Runtime/Temporal/InstantPrototype.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 5032199e38..f386e1e7a7 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -78,6 +78,7 @@ #define JS_ENUMERATE_TEMPORAL_OBJECTS \ __JS_ENUMERATE(Calendar, calendar, CalendarPrototype, CalendarConstructor) \ + __JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \ __JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \ __JS_ENUMERATE(TimeZone, time_zone, TimeZonePrototype, TimeZoneConstructor) diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 6220ae4f41..e4b8470486 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -167,6 +167,7 @@ M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}") \ M(StringRepeatCountMustBe, "repeat count must be a {} number") \ M(TemporalInvalidCalendarIdentifier, "Invalid calendar identifier '{}'") \ + M(TemporalInvalidDuration, "Invalid duration") \ M(TemporalInvalidEpochNanoseconds, "Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17") \ M(TemporalInvalidISODate, "Invalid ISO date") \ M(TemporalInvalidTime, "Invalid time") \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index a56cff9e54..57011398d6 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -70,6 +70,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp new file mode 100644 index 0000000000..73c22bab55 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace JS::Temporal { + +// 7 Temporal.Duration Objects, https://tc39.es/proposal-temporal/#sec-temporal-duration-objects +Duration::Duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object& prototype) + : Object(prototype) + , m_years(years) + , m_months(months) + , m_weeks(weeks) + , m_days(days) + , m_hours(hours) + , m_minutes(minutes) + , m_seconds(seconds) + , m_milliseconds(milliseconds) + , m_microseconds(microseconds) + , m_nanoseconds(nanoseconds) +{ +} + +// 7.5.3 DurationSign ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ) +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.4 IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, 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) +{ + // 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); + + // 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 }) { + // a. If v is not finite, return false. + if (!isfinite(v)) + return false; + + // b. If v < 0 and sign > 0, return false. + if (v < 0 && sign > 0) + return false; + + // c. If v > 0 and sign < 0, return false. + if (v > 0 && sign < 0) + return false; + } + + // 3. Return true. + return true; +} + +// 7.5.7 CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalduration +Duration* create_temporal_duration(GlobalObject& global_object, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, FunctionObject* new_target) +{ + auto& vm = global_object.vm(); + + // 1. If ! IsValidDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) is false, throw a RangeError exception. + if (!is_valid_duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds)) { + vm.throw_exception(global_object, ErrorType::TemporalInvalidDuration); + return {}; + } + + // 2. If newTarget is not present, set it to %Temporal.Duration%. + if (!new_target) + new_target = global_object.temporal_duration_constructor(); + + // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Duration.prototype%", « [[InitializedTemporalDuration]], [[Years]], [[Months]], [[Weeks]], [[Days]], [[Hours]], [[Minutes]], [[Seconds]], [[Milliseconds]], [[Microseconds]], [[Nanoseconds]] »). + // 4. Set object.[[Years]] to years. + // 5. Set object.[[Months]] to months. + // 6. Set object.[[Weeks]] to weeks. + // 7. Set object.[[Days]] to days. + // 8. Set object.[[Hours]] to hours. + // 9. Set object.[[Minutes]] to minutes. + // 10. Set object.[[Seconds]] to seconds. + // 11. Set object.[[Milliseconds]] to milliseconds. + // 12. Set object.[[Microseconds]] to microseconds. + // 13. Set object.[[Nanoseconds]] to nanoseconds. + auto* object = ordinary_create_from_constructor(global_object, *new_target, &GlobalObject::temporal_duration_prototype, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); + if (vm.exception()) + return {}; + + // 14. Return object. + return object; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h new file mode 100644 index 0000000000..aeeb467ab4 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Temporal { + +class Duration final : public Object { + JS_OBJECT(Duration, Object); + +public: + explicit Duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object& prototype); + virtual ~Duration() override = default; + + double years() const { return m_years; } + double months() const { return m_months; } + double weeks() const { return m_weeks; } + double days() const { return m_days; } + double hours() const { return m_hours; } + double minutes() const { return m_minutes; } + double seconds() const { return m_seconds; } + double milliseconds() const { return m_milliseconds; } + double microseconds() const { return m_microseconds; } + double nanoseconds() const { return m_nanoseconds; } + +private: + // 7.4 Properties of Temporal.Duration Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances + + double m_years; // [[Years]] + double m_months; // [[Months]] + double m_weeks; // [[Weeks]] + double m_days; // [[Days]] + double m_hours; // [[Hours]] + double m_minutes; // [[Minutes]] + double m_seconds; // [[Seconds]] + double m_milliseconds; // [[Milliseconds]] + double m_microseconds; // [[Microseconds]] + double m_nanoseconds; // [[Nanoseconds]] +}; + +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); +Duration* create_temporal_duration(GlobalObject&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, FunctionObject* new_target = nullptr); + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/DurationConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/DurationConstructor.cpp new file mode 100644 index 0000000000..41a03f5473 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/DurationConstructor.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace JS::Temporal { + +// 7.1 The Temporal.Duration Constructor, https://tc39.es/proposal-temporal/#sec-temporal-duration-constructor +DurationConstructor::DurationConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.Duration.as_string(), *global_object.function_prototype()) +{ +} + +void DurationConstructor::initialize(GlobalObject& global_object) +{ + NativeFunction::initialize(global_object); + + auto& vm = this->vm(); + + // 7.2.1 Temporal.Duration.prototype, https://tc39.es/proposal-temporal/#sec-temporal-duration-prototype + define_direct_property(vm.names.prototype, global_object.temporal_duration_prototype(), 0); + + define_direct_property(vm.names.length, Value(0), Attribute::Configurable); +} + +// 7.1.1 Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ , minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ] ] ] ] ] ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration +Value DurationConstructor::call() +{ + auto& vm = this->vm(); + + // 1. If NewTarget is undefined, then + // a. Throw a TypeError exception. + vm.throw_exception(global_object(), ErrorType::ConstructorWithoutNew, "Temporal.Duration"); + return {}; +} + +// 7.1.1 Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ , minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ] ] ] ] ] ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration +Value DurationConstructor::construct(FunctionObject& new_target) +{ + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 2. Let y be ? ToIntegerOrInfinity(years). + auto y = vm.argument(0).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 3. Let mo be ? ToIntegerOrInfinity(months). + auto mo = vm.argument(1).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 4. Let w be ? ToIntegerOrInfinity(weeks). + auto w = vm.argument(2).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 5. Let d be ? ToIntegerOrInfinity(days). + auto d = vm.argument(3).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 6. Let h be ? ToIntegerOrInfinity(hours). + auto h = vm.argument(4).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 7. Let m be ? ToIntegerOrInfinity(minutes). + auto m = vm.argument(5).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 8. Let s be ? ToIntegerOrInfinity(seconds). + auto s = vm.argument(6).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 9. Let ms be ? ToIntegerOrInfinity(milliseconds). + auto ms = vm.argument(7).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 10. Let mis be ? ToIntegerOrInfinity(microseconds). + auto mis = vm.argument(8).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 11. Let ns be ? ToIntegerOrInfinity(nanoseconds). + auto ns = vm.argument(9).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 12. Return ? CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns, NewTarget). + return create_temporal_duration(global_object, y, mo, w, d, h, m, s, ms, mis, ns, &new_target); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/DurationConstructor.h b/Userland/Libraries/LibJS/Runtime/Temporal/DurationConstructor.h new file mode 100644 index 0000000000..74871c0c71 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/DurationConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Temporal { + +class DurationConstructor final : public NativeFunction { + JS_OBJECT(DurationConstructor, NativeFunction); + +public: + explicit DurationConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~DurationConstructor() 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/DurationPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp new file mode 100644 index 0000000000..b746949d2a --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS::Temporal { + +// 7.3 Properties of the Temporal.Duration Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-duration-prototype-object +DurationPrototype::DurationPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void DurationPrototype::initialize(GlobalObject& global_object) +{ + Object::initialize(global_object); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.h new file mode 100644 index 0000000000..b758b546aa --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Temporal { + +class DurationPrototype final : public Object { + JS_OBJECT(DurationPrototype, Object); + +public: + explicit DurationPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~DurationPrototype() override = default; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp index 83c208fb71..b4de9fe0b0 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -28,6 +29,7 @@ void Temporal::initialize(GlobalObject& global_object) define_direct_property(vm.names.now, heap().allocate(global_object, global_object), attr); define_direct_property(vm.names.Calendar, global_object.temporal_calendar_constructor(), attr); + define_direct_property(vm.names.Duration, global_object.temporal_duration_constructor(), attr); define_direct_property(vm.names.Instant, global_object.temporal_instant_constructor(), attr); define_direct_property(vm.names.TimeZone, global_object.temporal_time_zone_constructor(), attr); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h index d98ff00058..238f1a0be2 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h @@ -14,10 +14,10 @@ namespace JS::Temporal { class TimeZone final : public Object { JS_OBJECT(TimeZone, Object); +public: // Needs to store values in the range -8.64 * 10^13 to 8.64 * 10^13 using OffsetType = double; -public: explicit TimeZone(String identifier, Object& prototype); virtual ~TimeZone() override = default; diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.js new file mode 100644 index 0000000000..df1b3c660a --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.js @@ -0,0 +1,35 @@ +describe("errors", () => { + test("called without new", () => { + expect(() => { + Temporal.Duration(); + }).toThrowWithMessage(TypeError, "Temporal.Duration constructor must be called with 'new'"); + }); + + test("cannot mix arguments with different signs", () => { + expect(() => { + new Temporal.Duration(-1, 1); + }).toThrowWithMessage(RangeError, "Invalid duration"); + expect(() => { + new Temporal.Duration(1, -1); + }).toThrowWithMessage(RangeError, "Invalid duration"); + }); + + test("cannot pass Infinity", () => { + expect(() => { + new Temporal.Duration(Infinity); + }).toThrowWithMessage(RangeError, "Invalid duration"); + }); +}); + +describe("normal behavior", () => { + test("length is 0", () => { + expect(Temporal.Duration).toHaveLength(0); + }); + + test("basic functionality", () => { + const duration = new Temporal.Duration(); + expect(typeof duration).toBe("object"); + expect(duration).toBeInstanceOf(Temporal.Duration); + expect(Object.getPrototypeOf(duration)).toBe(Temporal.Duration.prototype); + }); +});