From 3b1de431cc35c8886dd498ade0c9a90b268a95a1 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Fri, 19 Nov 2021 19:19:29 +0000 Subject: [PATCH] LibJS: Implement parsing of TemporalYearMonthString --- Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 1 + .../Runtime/Temporal/AbstractOperations.cpp | 13 +++++--- .../LibJS/Runtime/Temporal/ISO8601.cpp | 30 ++++++++++++++++++- .../LibJS/Runtime/Temporal/ISO8601.h | 3 ++ .../PlainYearMonth/PlainYearMonth.from.js | 15 +++++++++- 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index df46a985db..68da54a227 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -228,6 +228,7 @@ M(TemporalInvalidTimeString, "Invalid time string '{}'") \ M(TemporalInvalidTimeZoneName, "Invalid time zone name") \ M(TemporalInvalidUnitRange, "Invalid unit range, {} is larger than {}") \ + M(TemporalInvalidYearMonthString, "Invalid year month string '{}'") \ M(TemporalInvalidZonedDateTimeOffset, "Invalid offset for the provided date and time in the current time zone") \ M(TemporalMissingOptionsObject, "Required options object is missing or undefined") \ M(TemporalMissingStartingPoint, "A starting point is required for balancing {}") \ diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index f0b9f730ca..638569debd 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -1466,16 +1466,21 @@ ThrowCompletionOr parse_temporal_time_zone_string(GlobalObject } // 13.45 ParseTemporalYearMonthString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalyearmonthstring -ThrowCompletionOr parse_temporal_year_month_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string) +ThrowCompletionOr parse_temporal_year_month_string(GlobalObject& global_object, String const& iso_string) { + auto& vm = global_object.vm(); + // 1. Assert: Type(isoString) is String. // 2. If isoString does not satisfy the syntax of a TemporalYearMonthString (see 13.33), then - // a. Throw a RangeError exception. - // TODO + auto parse_result = parse_iso8601(Production::TemporalYearMonthString, iso_string); + if (!parse_result.has_value()) { + // a. Throw a RangeError exception. + return vm.throw_completion(global_object, ErrorType::TemporalInvalidYearMonthString, iso_string); + } // 3. Let result be ? ParseISODateTime(isoString). - auto result = TRY(parse_iso_date_time(global_object, {})); + auto result = TRY(parse_iso_date_time(global_object, *parse_result)); // 4. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Calendar]]: result.[[Calendar]] }. return TemporalYearMonth { .year = result.year, .month = result.month, .day = result.day, .calendar = move(result.calendar) }; diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp index 64f292429e..4a680640c3 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp @@ -210,6 +210,21 @@ bool ISO8601Parser::parse_date_day() return true; } +// https://tc39.es/proposal-temporal/#prod-DateSpecYearMonth +bool ISO8601Parser::parse_date_spec_year_month() +{ + // DateSpecYearMonth : + // DateYear -[opt] DateMonth + StateTransaction transaction { *this }; + if (!parse_date_year()) + return false; + m_state.lexer.consume_specific('-'); + if (!parse_date_month()) + return false; + transaction.commit(); + return true; +} + // https://tc39.es/proposal-temporal/#prod-DateSpecMonthDay bool ISO8601Parser::parse_date_spec_month_day() { @@ -528,13 +543,26 @@ bool ISO8601Parser::parse_temporal_time_string() || parse_time(); } +// https://tc39.es/proposal-temporal/#prod-TemporalYearMonthString +bool ISO8601Parser::parse_temporal_year_month_string() +{ + // TemporalYearMonthString : + // DateSpecYearMonth + // DateTime + // NOTE: Reverse order here because `DateSpecYearMonth` can be a subset of `DateTime`, + // so we'd not attempt to parse that but may not exhaust the input string. + return parse_date_time() + || parse_date_spec_year_month(); +} + } #define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS \ __JS_ENUMERATE(TemporalDateString, parse_temporal_date_string) \ __JS_ENUMERATE(TemporalDateTimeString, parse_temporal_date_time_string) \ __JS_ENUMERATE(TemporalMonthDayString, parse_temporal_month_day_string) \ - __JS_ENUMERATE(TemporalTimeString, parse_temporal_time_string) + __JS_ENUMERATE(TemporalTimeString, parse_temporal_time_string) \ + __JS_ENUMERATE(TemporalYearMonthString, parse_temporal_year_month_string) Optional parse_iso8601(Production production, StringView input) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h index a2bdf267c1..911f71e0d6 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h @@ -30,6 +30,7 @@ enum class Production { TemporalDateTimeString, TemporalMonthDayString, TemporalTimeString, + TemporalYearMonthString, }; Optional parse_iso8601(Production, StringView); @@ -61,6 +62,7 @@ public: [[nodiscard]] bool parse_date_year(); [[nodiscard]] bool parse_date_month(); [[nodiscard]] bool parse_date_day(); + [[nodiscard]] bool parse_date_spec_year_month(); [[nodiscard]] bool parse_date_spec_month_day(); [[nodiscard]] bool parse_date(); [[nodiscard]] bool parse_time_hour(); @@ -84,6 +86,7 @@ public: [[nodiscard]] bool parse_temporal_date_time_string(); [[nodiscard]] bool parse_temporal_month_day_string(); [[nodiscard]] bool parse_temporal_time_string(); + [[nodiscard]] bool parse_temporal_year_month_string(); private: struct State { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.from.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.from.js index a47b44de36..0c612222d3 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.from.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.from.js @@ -55,8 +55,15 @@ describe("correct behavior", () => { expect(plainYearMonth.inLeapYear).toBeFalse(); }); + test("from year month string", () => { + const plainYearMonth = Temporal.PlainYearMonth.from("2021-07"); + expect(plainYearMonth.year).toBe(2021); + expect(plainYearMonth.month).toBe(7); + expect(plainYearMonth.monthCode).toBe("M07"); + }); + // Un-skip once ParseISODateTime & ParseTemporalYearMonthString are fully implemented - test.skip("PlainYearMonth string argument", () => { + test.skip("from date time string", () => { const plainYearMonth = Temporal.PlainYearMonth.from("2021-07-06T23:42:01Z"); expect(plainYearMonth.year).toBe(2021); expect(plainYearMonth.month).toBe(7); @@ -80,4 +87,10 @@ describe("errors", () => { Temporal.PlainYearMonth.from({ month: 1 }); }).toThrowWithMessage(TypeError, "Required property year is missing or undefined"); }); + + test("invalid year month string", () => { + expect(() => { + Temporal.PlainYearMonth.from("foo"); + }).toThrowWithMessage(RangeError, "Invalid year month string 'foo'"); + }); });