From 3ddab2f4fe343f0eadce856663bd109659de293b Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Fri, 19 Nov 2021 19:05:17 +0000 Subject: [PATCH] LibJS: Implement parsing of TemporalMonthDayString --- Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 1 + .../Runtime/Temporal/AbstractOperations.cpp | 19 ++++++++---- .../LibJS/Runtime/Temporal/ISO8601.cpp | 31 +++++++++++++++++++ .../LibJS/Runtime/Temporal/ISO8601.h | 3 ++ .../PlainMonthDay/PlainMonthDay.from.js | 14 ++++++++- 5 files changed, 61 insertions(+), 7 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 46bf6524c1..df46a985db 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -216,6 +216,7 @@ M(TemporalInvalidEpochNanoseconds, "Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17") \ M(TemporalInvalidISODate, "Invalid ISO date") \ M(TemporalInvalidMonthCode, "Invalid month code") \ + M(TemporalInvalidMonthDayString, "Invalid month day string '{}'") \ 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") \ diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index 4268b2b5df..f0b9f730ca 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -1290,23 +1290,30 @@ ThrowCompletionOr parse_temporal_duration_string(GlobalObject& } // 13.41 ParseTemporalMonthDayString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalmonthdaystring -ThrowCompletionOr parse_temporal_month_day_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string) +ThrowCompletionOr parse_temporal_month_day_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 TemporalMonthDayString (see 13.33), then - // a. Throw a RangeError exception. - // TODO + auto parse_result = parse_iso8601(Production::TemporalMonthDayString, iso_string); + if (!parse_result.has_value()) { + // a. Throw a RangeError exception. + return vm.throw_completion(global_object, ErrorType::TemporalInvalidMonthDayString, 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. Let year be result.[[Year]]. Optional year = result.year; // 5. If no part of isoString is produced by the DateYear production, then - // a. Set year to undefined. - // TODO (this is the case when TemporalMonthDayString is a DateSpecMonthDay) + if (!parse_result->date_year.has_value()) { + // a. Set year to undefined. + year = {}; + } // 6. Return the Record { [[Year]]: year, [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Calendar]]: result.[[Calendar]] }. return TemporalMonthDay { .year = 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 1ba58b773d..64f292429e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp @@ -210,6 +210,24 @@ bool ISO8601Parser::parse_date_day() return true; } +// https://tc39.es/proposal-temporal/#prod-DateSpecMonthDay +bool ISO8601Parser::parse_date_spec_month_day() +{ + // TwoDashes : + // -- + // DateSpecMonthDay : + // TwoDashes[opt] DateMonth -[opt] DateDay + StateTransaction transaction { *this }; + m_state.lexer.consume_specific("--"sv); + if (!parse_date_month()) + return false; + m_state.lexer.consume_specific('-'); + if (!parse_date_day()) + return false; + transaction.commit(); + return true; +} + // https://tc39.es/proposal-temporal/#prod-Date bool ISO8601Parser::parse_date() { @@ -486,6 +504,18 @@ bool ISO8601Parser::parse_temporal_date_time_string() return parse_calendar_date_time(); } +// https://tc39.es/proposal-temporal/#prod-TemporalMonthDayString +bool ISO8601Parser::parse_temporal_month_day_string() +{ + // TemporalMonthDayString : + // DateSpecMonthDay + // DateTime + // NOTE: Reverse order here because `DateSpecMonthDay` 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_month_day(); +} + // https://tc39.es/proposal-temporal/#prod-TemporalTimeString bool ISO8601Parser::parse_temporal_time_string() { @@ -503,6 +533,7 @@ bool ISO8601Parser::parse_temporal_time_string() #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) 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 4d1bed1f89..a2bdf267c1 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h @@ -28,6 +28,7 @@ struct ParseResult { enum class Production { TemporalDateString, TemporalDateTimeString, + TemporalMonthDayString, TemporalTimeString, }; @@ -60,6 +61,7 @@ public: [[nodiscard]] bool parse_date_year(); [[nodiscard]] bool parse_date_month(); [[nodiscard]] bool parse_date_day(); + [[nodiscard]] bool parse_date_spec_month_day(); [[nodiscard]] bool parse_date(); [[nodiscard]] bool parse_time_hour(); [[nodiscard]] bool parse_time_minute(); @@ -80,6 +82,7 @@ public: [[nodiscard]] bool parse_calendar_date_time(); [[nodiscard]] bool parse_temporal_date_string(); [[nodiscard]] bool parse_temporal_date_time_string(); + [[nodiscard]] bool parse_temporal_month_day_string(); [[nodiscard]] bool parse_temporal_time_string(); private: diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainMonthDay/PlainMonthDay.from.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainMonthDay/PlainMonthDay.from.js index b9b01c4d32..1abdc8ddb9 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainMonthDay/PlainMonthDay.from.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainMonthDay/PlainMonthDay.from.js @@ -35,8 +35,14 @@ describe("correct behavior", () => { expect(plainMonthDay.day).toBe(6); }); + test("from month day string", () => { + const plainMonthDay = Temporal.PlainMonthDay.from("--07-06"); + expect(plainMonthDay.monthCode).toBe("M07"); + expect(plainMonthDay.day).toBe(6); + }); + // Un-skip once ParseISODateTime, ToTemporalMonthDay & ParseTemporalMonthDayString are fully implemented - test.skip("PlainMonthDay string argument", () => { + test.skip("from date time string", () => { const plainMonthDay = Temporal.PlainMonthDay.from("2021-07-06T23:42:01Z"); expect(plainMonthDay.monthCode).toBe("M07"); expect(plainMonthDay.day).toBe(6); @@ -55,4 +61,10 @@ describe("errors", () => { Temporal.PlainMonthDay.from({ day: 1 }); }).toThrowWithMessage(TypeError, "Required property month is missing or undefined"); }); + + test("invalid month day string", () => { + expect(() => { + Temporal.PlainMonthDay.from("foo"); + }).toThrowWithMessage(RangeError, "Invalid month day string 'foo'"); + }); });