LibJS: Start implementing Temporal.Duration

This patch adds the Duration object itself, its constructor and
prototype (currently empty), and three required abstract operations.
This commit is contained in:
Linus Groh 2021-07-15 23:20:43 +01:00
parent 71234b3716
commit 7921d8ba91
Notes: sideshowbarker 2024-07-18 08:58:16 +09:00
13 changed files with 377 additions and 1 deletions

View File

@ -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

View File

@ -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)

View File

@ -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") \

View File

@ -70,6 +70,8 @@
#include <LibJS/Runtime/SymbolPrototype.h>
#include <LibJS/Runtime/Temporal/CalendarConstructor.h>
#include <LibJS/Runtime/Temporal/CalendarPrototype.h>
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
#include <LibJS/Runtime/Temporal/DurationPrototype.h>
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
#include <LibJS/Runtime/Temporal/InstantPrototype.h>
#include <LibJS/Runtime/Temporal/Temporal.h>

View File

@ -0,0 +1,107 @@
/*
* 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/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
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<RangeError>(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<Duration>(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;
}
}

View File

@ -0,0 +1,50 @@
/*
* 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 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);
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
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<TypeError>(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);
}
}

View File

@ -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 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; }
};
}

View File

@ -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/DurationPrototype.h>
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);
}
}

View File

@ -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 DurationPrototype final : public Object {
JS_OBJECT(DurationPrototype, Object);
public:
explicit DurationPrototype(GlobalObject&);
virtual void initialize(GlobalObject&) override;
virtual ~DurationPrototype() override = default;
};
}

View File

@ -6,6 +6,7 @@
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/CalendarConstructor.h>
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
#include <LibJS/Runtime/Temporal/Now.h>
#include <LibJS/Runtime/Temporal/Temporal.h>
@ -28,6 +29,7 @@ void Temporal::initialize(GlobalObject& global_object)
define_direct_property(vm.names.now, heap().allocate<Now>(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);
}

View File

@ -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;

View File

@ -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);
});
});