LibJS: Start implementing Temporal.Calendar

Just like the previous Temporal.{Instant,TimeZone} commits, this patch
adds the Calendar object itself, its constructor and prototype
(currently empty), and two required abstract operations.
This commit is contained in:
Linus Groh 2021-07-14 21:01:12 +01:00
parent 48b66c7a68
commit a2f1d79765
Notes: sideshowbarker 2024-07-18 09:00:34 +09:00
12 changed files with 250 additions and 3 deletions

View File

@ -123,6 +123,8 @@ set(SOURCES
Runtime/SymbolPrototype.cpp
Runtime/Temporal/AbstractOperations.cpp
Runtime/Temporal/Calendar.cpp
Runtime/Temporal/CalendarConstructor.cpp
Runtime/Temporal/CalendarPrototype.cpp
Runtime/Temporal/Instant.cpp
Runtime/Temporal/InstantConstructor.cpp
Runtime/Temporal/InstantPrototype.cpp

View File

@ -76,8 +76,9 @@
__JS_ENUMERATE(Float32Array, float32_array, Float32ArrayPrototype, Float32ArrayConstructor, float) \
__JS_ENUMERATE(Float64Array, float64_array, Float64ArrayPrototype, Float64ArrayConstructor, double)
#define JS_ENUMERATE_TEMPORAL_OBJECTS \
__JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \
#define JS_ENUMERATE_TEMPORAL_OBJECTS \
__JS_ENUMERATE(Calendar, calendar, CalendarPrototype, CalendarConstructor) \
__JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \
__JS_ENUMERATE(TimeZone, time_zone, TimeZonePrototype, TimeZoneConstructor)
#define JS_ENUMERATE_ITERATOR_PROTOTYPES \

View File

@ -166,8 +166,9 @@
M(StringNonGlobalRegExp, "RegExp argument is non-global") \
M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}") \
M(StringRepeatCountMustBe, "repeat count must be a {} number") \
M(TemporalInvalidISODate, "Invalid ISO date") \
M(TemporalInvalidCalendarIdentifier, "Invalid calendar identifier '{}'") \
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") \
M(TemporalInvalidTimeZoneName, "Invalid time zone name") \
M(ThisHasNotBeenInitialized, "|this| has not been initialized") \

View File

@ -68,6 +68,8 @@
#include <LibJS/Runtime/StringPrototype.h>
#include <LibJS/Runtime/SymbolConstructor.h>
#include <LibJS/Runtime/SymbolPrototype.h>
#include <LibJS/Runtime/Temporal/CalendarConstructor.h>
#include <LibJS/Runtime/Temporal/CalendarPrototype.h>
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
#include <LibJS/Runtime/Temporal/InstantPrototype.h>
#include <LibJS/Runtime/Temporal/Temporal.h>

View File

@ -4,11 +4,55 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/CalendarConstructor.h>
#include <LibJS/Runtime/Value.h>
namespace JS::Temporal {
// 12 Temporal.Calendar Objects, https://tc39.es/proposal-temporal/#sec-temporal-calendar-objects
Calendar::Calendar(String identifier, Object& prototype)
: Object(prototype)
, m_identifier(move(identifier))
{
}
// 12.1.1 CreateTemporalCalendar ( identifier [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalcalendar
Calendar* create_temporal_calendar(GlobalObject& global_object, String const& identifier, FunctionObject* new_target)
{
auto& vm = global_object.vm();
// 1. Assert: ! IsBuiltinCalendar(identifier) is true.
VERIFY(is_builtin_calendar(identifier));
// 2. If newTarget is not provided, set newTarget to %Temporal.Calendar%.
if (!new_target)
new_target = global_object.temporal_calendar_constructor();
// 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Calendar.prototype%", « [[InitializedTemporalCalendar]], [[Identifier]] »).
// 4. Set object.[[Identifier]] to identifier.
auto* object = ordinary_create_from_constructor<Calendar>(global_object, *new_target, &GlobalObject::temporal_calendar_prototype, identifier);
if (vm.exception())
return {};
// 5. Return object.
return object;
}
// 12.1.2 IsBuiltinCalendar ( id ), https://tc39.es/proposal-temporal/#sec-temporal-isbuiltincalendar
// NOTE: This is the minimum IsBuiltinCalendar implementation for engines without ECMA-402.
bool is_builtin_calendar(String const& identifier)
{
// 1. If id is not "iso8601", return false.
if (identifier != "iso8601"sv)
return false;
// 2. Return true.
return true;
}
// 12.1.30 IsISOLeapYear ( year ), https://tc39.es/proposal-temporal/#sec-temporal-isisoleapyear
bool is_iso_leap_year(i32 year)
{

View File

@ -1,15 +1,35 @@
/*
* 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/Object.h>
#include <LibJS/Runtime/Value.h>
namespace JS::Temporal {
class Calendar final : public Object {
JS_OBJECT(Calendar, Object);
public:
explicit Calendar(String identifier, Object& prototype);
virtual ~Calendar() override = default;
String const& identifier() const { return m_identifier; }
private:
// 12.5 Properties of Temporal.Calendar Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-calendar-instances
// [[Identifier]]
String m_identifier;
};
Calendar* create_temporal_calendar(GlobalObject&, String const& identifier, FunctionObject* new_target = nullptr);
bool is_builtin_calendar(String const& identifier);
bool is_iso_leap_year(i32 year);
i32 iso_days_in_month(i32 year, i32 month);

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/CalendarConstructor.h>
namespace JS::Temporal {
// 12.2 The Temporal.Calendar Constructor, https://tc39.es/proposal-temporal/#sec-temporal-calendar-constructor
CalendarConstructor::CalendarConstructor(GlobalObject& global_object)
: NativeFunction(vm().names.Calendar.as_string(), *global_object.function_prototype())
{
}
void CalendarConstructor::initialize(GlobalObject& global_object)
{
NativeFunction::initialize(global_object);
auto& vm = this->vm();
// 12.3.1 Temporal.Calendar.prototype, https://tc39.es/proposal-temporal/#sec-temporal-calendar-prototype
define_direct_property(vm.names.prototype, global_object.temporal_calendar_prototype(), 0);
define_direct_property(vm.names.length, Value(1), Attribute::Configurable);
}
// 12.2.1 Temporal.Calendar ( id ), https://tc39.es/proposal-temporal/#sec-temporal.calendar
Value CalendarConstructor::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.Calendar");
return {};
}
// 12.2.1 Temporal.Calendar ( id ), https://tc39.es/proposal-temporal/#sec-temporal.calendar
Value CalendarConstructor::construct(FunctionObject& new_target)
{
auto& vm = this->vm();
auto& global_object = this->global_object();
// 2. Set id to ? ToString(id).
auto identifier = vm.argument(0).to_string(global_object);
if (vm.exception())
return {};
// 3. If ! IsBuiltinCalendar(id) is false, then
if (!is_builtin_calendar(identifier)) {
// a. Throw a RangeError exception.
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidCalendarIdentifier, identifier);
return {};
}
// 4. Return ? CreateTemporalCalendar(id, NewTarget).
return create_temporal_calendar(global_object, identifier, &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 CalendarConstructor final : public NativeFunction {
JS_OBJECT(CalendarConstructor, NativeFunction);
public:
explicit CalendarConstructor(GlobalObject&);
virtual void initialize(GlobalObject&) override;
virtual ~CalendarConstructor() 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/CalendarPrototype.h>
namespace JS::Temporal {
// 12.4 Properties of the Temporal.Calendar Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-calendar-prototype-object
CalendarPrototype::CalendarPrototype(GlobalObject& global_object)
: Object(*global_object.object_prototype())
{
}
void CalendarPrototype::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 CalendarPrototype final : public Object {
JS_OBJECT(CalendarPrototype, Object);
public:
explicit CalendarPrototype(GlobalObject&);
virtual void initialize(GlobalObject&) override;
virtual ~CalendarPrototype() override = default;
};
}

View File

@ -5,6 +5,7 @@
*/
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/CalendarConstructor.h>
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
#include <LibJS/Runtime/Temporal/Now.h>
#include <LibJS/Runtime/Temporal/Temporal.h>
@ -26,6 +27,7 @@ void Temporal::initialize(GlobalObject& global_object)
u8 attr = Attribute::Writable | Attribute::Configurable;
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.Instant, global_object.temporal_instant_constructor(), attr);
define_direct_property(vm.names.TimeZone, global_object.temporal_time_zone_constructor(), attr);
}

View File

@ -0,0 +1,38 @@
describe("errors", () => {
test("called without new", () => {
expect(() => {
Temporal.Calendar();
}).toThrowWithMessage(TypeError, "Temporal.Calendar constructor must be called with 'new'");
});
test("argument must be coercible to string", () => {
expect(() => {
new Temporal.Calendar({
toString() {
throw new Error();
},
});
}).toThrow(Error);
});
test("invalid calendar identifier", () => {
expect(() => {
new Temporal.Calendar("foo");
}).toThrowWithMessage(RangeError, "Invalid calendar identifier 'foo'");
});
});
describe("normal behavior", () => {
test("length is 1", () => {
expect(Temporal.Calendar).toHaveLength(1);
});
test("basic functionality", () => {
const calendar = new Temporal.Calendar("iso8601");
// FIXME: Enable this once Temporal.Calendar.prototype.id is implemented
// expect(calendar.id).toBe("iso8601");
expect(typeof calendar).toBe("object");
expect(calendar).toBeInstanceOf(Temporal.Calendar);
expect(Object.getPrototypeOf(calendar)).toBe(Temporal.Calendar.prototype);
});
});