diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index f5d29ca974..6b54a4034c 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -161,6 +161,7 @@ set(SOURCES Runtime/Temporal/Instant.cpp Runtime/Temporal/InstantConstructor.cpp Runtime/Temporal/InstantPrototype.cpp + Runtime/Temporal/ISO8601.cpp Runtime/Temporal/Now.cpp Runtime/Temporal/PlainDate.cpp Runtime/Temporal/PlainDateConstructor.cpp diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index 225e049386..26185f5120 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -1052,22 +1053,21 @@ BigInt* round_number_to_increment(GlobalObject& global_object, BigInt const& x, } // 13.34 ParseISODateTime ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime -ThrowCompletionOr parse_iso_date_time(GlobalObject& global_object, [[maybe_unused]] String const& iso_string) +ThrowCompletionOr parse_iso_date_time(GlobalObject& global_object, ParseResult const& parse_result) { auto& vm = global_object.vm(); // 1. Assert: Type(isoString) is String. // 2. Let year, month, day, hour, minute, second, fraction, and calendar be the parts of isoString produced respectively by the DateYear, DateMonth, DateDay, TimeHour, TimeMinute, TimeSecond, TimeFractionalPart, and CalendarName productions, or undefined if not present. - Optional year_part; - Optional month_part; - Optional day_part; - Optional hour_part; - Optional minute_part; - Optional second_part; - Optional fraction_part; - Optional calendar_part; - return vm.throw_completion(global_object, ErrorType::NotImplemented, "ParseISODateTime"); + auto year_part = parse_result.date_year; + auto month_part = parse_result.date_month; + auto day_part = parse_result.date_day; + auto hour_part = parse_result.time_hour; + auto minute_part = parse_result.time_minute; + auto second_part = parse_result.time_second; + auto fraction_part = parse_result.time_fractional_part; + auto calendar_part = parse_result.calendar_name; // 3. Let year be the part of isoString produced by the DateYear production. // 4. If the first code unit of year is 0x2212 (MINUS SIGN), replace it with the code unit 0x002D (HYPHEN-MINUS). @@ -1075,10 +1075,10 @@ ThrowCompletionOr parse_iso_date_time(GlobalObject& global_object, if (year_part.has_value() && year_part->starts_with("\xE2\x88\x92"sv)) normalized_year = String::formatted("-{}", year_part->substring_view(3)); else - normalized_year = year_part.value_or(""); + normalized_year = year_part.value_or("0"); // 5. Set year to ! ToIntegerOrInfinity(year). - i32 year = MUST(Value(js_string(vm, normalized_year)).to_integer_or_infinity(global_object)); + auto year = *normalized_year.to_int(); u8 month; // 6. If month is undefined, then @@ -1168,7 +1168,7 @@ ThrowCompletionOr parse_temporal_instant_string(GlobalObject& g // TODO // 3. Let result be ! ParseISODateTime(isoString). - auto result = MUST(parse_iso_date_time(global_object, iso_string)); + auto result = MUST(parse_iso_date_time(global_object, {})); // 4. Let timeZoneResult be ? ParseTemporalTimeZoneString(isoString). auto time_zone_result = TRY(parse_temporal_time_zone_string(global_object, iso_string)); @@ -1199,7 +1199,7 @@ ThrowCompletionOr parse_temporal_zoned_date_time_string(G // TODO // 3. Let result be ! ParseISODateTime(isoString). - auto result = MUST(parse_iso_date_time(global_object, iso_string)); + auto result = MUST(parse_iso_date_time(global_object, {})); // 4. Let timeZoneResult be ? ParseTemporalTimeZoneString(isoString). auto time_zone_result = TRY(parse_temporal_time_zone_string(global_object, iso_string)); @@ -1240,7 +1240,7 @@ ThrowCompletionOr parse_temporal_calendar_string(GlobalObject& global_ob } // 13.38 ParseTemporalDateString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatestring -ThrowCompletionOr parse_temporal_date_string(GlobalObject& global_object, String const& iso_string) +ThrowCompletionOr parse_temporal_date_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string) { // 1. Assert: Type(isoString) is String. @@ -1249,14 +1249,14 @@ ThrowCompletionOr parse_temporal_date_string(GlobalObject& global_ // TODO // 3. Let result be ? ParseISODateTime(isoString). - auto result = TRY(parse_iso_date_time(global_object, iso_string)); + auto result = TRY(parse_iso_date_time(global_object, {})); // 4. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Calendar]]: result.[[Calendar]] }. return TemporalDate { .year = result.year, .month = result.month, .day = result.day, .calendar = move(result.calendar) }; } // 13.39 ParseTemporalDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatetimestring -ThrowCompletionOr parse_temporal_date_time_string(GlobalObject& global_object, String const& iso_string) +ThrowCompletionOr parse_temporal_date_time_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string) { // 1. Assert: Type(isoString) is String. @@ -1265,7 +1265,7 @@ ThrowCompletionOr parse_temporal_date_time_string(GlobalObject& glo // TODO // 3. Let result be ? ParseISODateTime(isoString). - auto result = TRY(parse_iso_date_time(global_object, iso_string)); + auto result = TRY(parse_iso_date_time(global_object, {})); // 4. Return result. return result; @@ -1280,7 +1280,7 @@ 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, String const& iso_string) +ThrowCompletionOr parse_temporal_month_day_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string) { // 1. Assert: Type(isoString) is String. @@ -1289,7 +1289,7 @@ ThrowCompletionOr parse_temporal_month_day_string(GlobalObject // TODO // 3. Let result be ? ParseISODateTime(isoString). - auto result = TRY(parse_iso_date_time(global_object, iso_string)); + auto result = TRY(parse_iso_date_time(global_object, {})); // 4. Let year be result.[[Year]]. Optional year = result.year; @@ -1303,7 +1303,7 @@ ThrowCompletionOr parse_temporal_month_day_string(GlobalObject } // 13.42 ParseTemporalRelativeToString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalrelativetostring -ThrowCompletionOr parse_temporal_relative_to_string(GlobalObject& global_object, String const& iso_string) +ThrowCompletionOr parse_temporal_relative_to_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string) { // 1. Assert: Type(isoString) is String. @@ -1312,7 +1312,7 @@ ThrowCompletionOr parse_temporal_relative_to_string(Globa // TODO // 3. Let result be ! ParseISODateTime(isoString). - auto result = MUST(parse_iso_date_time(global_object, iso_string)); + auto result = MUST(parse_iso_date_time(global_object, {})); bool z; Optional offset; @@ -1345,7 +1345,7 @@ ThrowCompletionOr parse_temporal_time_string(GlobalObject& global_ // TODO // 3. Let result be ? ParseISODateTime(isoString). - auto result = TRY(parse_iso_date_time(global_object, iso_string)); + auto result = TRY(parse_iso_date_time(global_object, {})); // 4. Return the Record { [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[Calendar]]: result.[[Calendar]] }. return TemporalTime { .hour = result.hour, .minute = result.minute, .second = result.second, .millisecond = result.millisecond, .microsecond = result.microsecond, .nanosecond = result.nanosecond, .calendar = move(result.calendar) }; @@ -1444,7 +1444,7 @@ 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, String const& iso_string) +ThrowCompletionOr parse_temporal_year_month_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string) { // 1. Assert: Type(isoString) is String. @@ -1453,7 +1453,7 @@ ThrowCompletionOr parse_temporal_year_month_string(GlobalObje // TODO // 3. Let result be ? ParseISODateTime(isoString). - auto result = TRY(parse_iso_date_time(global_object, iso_string)); + auto result = TRY(parse_iso_date_time(global_object, {})); // 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/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index e553e4f3d8..08e1bbf2fe 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace JS::Temporal { @@ -126,7 +127,7 @@ double sign(double); double constrain_to_range(double x, double minimum, double maximum); i64 round_number_to_increment(double, u64 increment, StringView rounding_mode); BigInt* round_number_to_increment(GlobalObject&, BigInt const&, u64 increment, StringView rounding_mode); -ThrowCompletionOr parse_iso_date_time(GlobalObject&, String const& iso_string); +ThrowCompletionOr parse_iso_date_time(GlobalObject&, ParseResult const& parse_result); ThrowCompletionOr parse_temporal_instant_string(GlobalObject&, String const& iso_string); ThrowCompletionOr parse_temporal_zoned_date_time_string(GlobalObject&, String const& iso_string); ThrowCompletionOr parse_temporal_calendar_string(GlobalObject&, String const& iso_string); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp new file mode 100644 index 0000000000..f92446a261 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS::Temporal { + +namespace Detail { + +// https://tc39.es/proposal-temporal/#prod-DecimalDigit +bool ISO8601Parser::parse_decimal_digit() +{ + // DecimalDigit : one of + // 0 1 2 3 4 5 6 7 8 9 + if (m_state.lexer.next_is(is_ascii_digit)) { + m_state.lexer.consume(); + return true; + } + return false; +} + +// https://tc39.es/proposal-temporal/#prod-NonZeroDigit +bool ISO8601Parser::parse_non_zero_digit() +{ + // NonZeroDigit : one of + // 1 2 3 4 5 6 7 8 9 + if (m_state.lexer.next_is(is_ascii_digit) && !m_state.lexer.next_is('0')) { + m_state.lexer.consume(); + return true; + } + return false; +} + +// https://tc39.es/proposal-temporal/#prod-ASCIISign +bool ISO8601Parser::parse_ascii_sign() +{ + // ASCIISign : one of + // + - + return m_state.lexer.consume_specific('+') + || m_state.lexer.consume_specific('-'); +} + +// https://tc39.es/proposal-temporal/#prod-Sign +bool ISO8601Parser::parse_sign() +{ + // Sign : + // ASCIISign + // U+2212 + StateTransaction transaction { *this }; + auto success = parse_ascii_sign() + || m_state.lexer.consume_specific("\xE2\x88\x92"sv); + if (!success) + return false; + m_state.parse_result.sign = transaction.parsed_string_view(); + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-Hour +bool ISO8601Parser::parse_hour() +{ + // Hour : + // 0 DecimalDigit + // 1 DecimalDigit + // 20 + // 21 + // 22 + // 23 + StateTransaction transaction { *this }; + if (m_state.lexer.consume_specific('0') || m_state.lexer.consume_specific('1')) { + if (!parse_decimal_digit()) + return false; + } else { + auto success = m_state.lexer.consume_specific("20"sv) + || m_state.lexer.consume_specific("21"sv) + || m_state.lexer.consume_specific("22"sv) + || m_state.lexer.consume_specific("23"sv); + if (!success) + return false; + } + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-MinuteSecond +bool ISO8601Parser::parse_minute_second() +{ + // MinuteSecond : + // 0 DecimalDigit + // 1 DecimalDigit + // 2 DecimalDigit + // 3 DecimalDigit + // 4 DecimalDigit + // 5 DecimalDigit + StateTransaction transaction { *this }; + auto success = m_state.lexer.consume_specific('0') + || m_state.lexer.consume_specific('1') + || m_state.lexer.consume_specific('2') + || m_state.lexer.consume_specific('3') + || m_state.lexer.consume_specific('4') + || m_state.lexer.consume_specific('5'); + if (!success) + return false; + if (!parse_decimal_digit()) + return false; + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-DecimalSeparator +bool ISO8601Parser::parse_decimal_separator() +{ + // DecimalSeparator : one of + // . , + return m_state.lexer.consume_specific('.') + || m_state.lexer.consume_specific(','); +} + +// https://tc39.es/proposal-temporal/#prod-DateTimeSeparator +bool ISO8601Parser::parse_date_time_separator() +{ + // DateTimeSeparator : + // + // T + // t + return m_state.lexer.consume_specific(' ') + || m_state.lexer.consume_specific('T') + || m_state.lexer.consume_specific('t'); +} + +// https://tc39.es/proposal-temporal/#prod-DateYear +bool ISO8601Parser::parse_date_year() +{ + // DateFourDigitYear : + // DecimalDigit DecimalDigit DecimalDigit DecimalDigit + // DateExtendedYear : + // Sign DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit + // DateYear : + // DateFourDigitYear + // DateExtendedYear + StateTransaction transaction { *this }; + if (parse_sign()) { + for (size_t i = 0; i < 6; ++i) { + if (!parse_decimal_digit()) + return false; + } + } else { + for (size_t i = 0; i < 4; ++i) { + if (!parse_decimal_digit()) + return false; + } + } + m_state.parse_result.date_year = transaction.parsed_string_view(); + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-DateMonth +bool ISO8601Parser::parse_date_month() +{ + // DateMonth : + // 0 NonZeroDigit + // 10 + // 11 + // 12 + StateTransaction transaction { *this }; + if (m_state.lexer.consume_specific('0')) { + if (!parse_non_zero_digit()) + return false; + } else { + auto success = m_state.lexer.consume_specific("10"sv) + || m_state.lexer.consume_specific("11"sv) + || m_state.lexer.consume_specific("12"sv); + if (!success) + return false; + } + m_state.parse_result.date_month = transaction.parsed_string_view(); + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-DateDay +bool ISO8601Parser::parse_date_day() +{ + // DateDay : + // 0 NonZeroDigit + // 1 DecimalDigit + // 2 DecimalDigit + // 30 + // 31 + StateTransaction transaction { *this }; + if (m_state.lexer.consume_specific('0')) { + if (!parse_non_zero_digit()) + return false; + } else if (m_state.lexer.consume_specific('1') || m_state.lexer.consume_specific('2')) { + if (!parse_decimal_digit()) + return false; + } else { + auto success = m_state.lexer.consume_specific("30"sv) + || m_state.lexer.consume_specific("31"sv); + if (!success) + return false; + } + m_state.parse_result.date_day = transaction.parsed_string_view(); + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-Date +bool ISO8601Parser::parse_date() +{ + // Date : + // DateYear - DateMonth - DateDay + // DateYear DateMonth DateDay + StateTransaction transaction { *this }; + if (!parse_date_year()) + return false; + auto with_dashes = m_state.lexer.consume_specific('-'); + if (!parse_date_month()) + return false; + if (with_dashes && !m_state.lexer.consume_specific('-')) + return false; + if (!parse_date_day()) + return false; + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-TimeHour +bool ISO8601Parser::parse_time_hour() +{ + // TimeHour : + // Hour + StateTransaction transaction { *this }; + if (!parse_hour()) + return false; + m_state.parse_result.time_hour = transaction.parsed_string_view(); + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-TimeMinute +bool ISO8601Parser::parse_time_minute() +{ + // TimeMinute : + // MinuteSecond + StateTransaction transaction { *this }; + if (!parse_minute_second()) + return false; + m_state.parse_result.time_minute = transaction.parsed_string_view(); + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-TimeSecond +bool ISO8601Parser::parse_time_second() +{ + // TimeSecond : + // MinuteSecond + // 60 + StateTransaction transaction { *this }; + auto success = parse_minute_second() + || m_state.lexer.consume_specific("60"sv); + if (!success) + return false; + m_state.parse_result.time_second = transaction.parsed_string_view(); + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-FractionalPart +bool ISO8601Parser::parse_fractional_part() +{ + // FractionalPart : + // DecimalDigit DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] + if (!parse_decimal_digit()) + return false; + for (size_t i = 0; i < 8; ++i) { + if (!parse_decimal_digit()) + break; + } + return true; +} + +// https://tc39.es/proposal-temporal/#prod-TimeFractionalPart +bool ISO8601Parser::parse_time_fractional_part() +{ + // TimeFractionalPart : + // FractionalPart + StateTransaction transaction { *this }; + if (!parse_fractional_part()) + return false; + m_state.parse_result.time_fractional_part = transaction.parsed_string_view(); + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-Fraction +bool ISO8601Parser::parse_fraction() +{ + // Fraction : + // DecimalSeparator TimeFractionalPart + StateTransaction transaction { *this }; + if (!parse_decimal_separator()) + return false; + if (!parse_time_fractional_part()) + return false; + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-TimeFraction +bool ISO8601Parser::parse_time_fraction() +{ + // TimeFraction : + // Fraction + return parse_fraction(); +} + +// https://tc39.es/proposal-temporal/#prod-TimeZoneOffsetRequired +bool ISO8601Parser::parse_time_zone_offset_required() +{ + // TimeZoneOffsetRequired : + // TimeZoneUTCOffset TimeZoneBracketedAnnotation[opt] + return false; +} + +// https://tc39.es/proposal-temporal/#prod-TimeZoneNameRequired +bool ISO8601Parser::parse_time_zone_name_required() +{ + // TimeZoneNameRequired : + // TimeZoneUTCOffset[opt] TimeZoneBracketedAnnotation + return false; +} + +// https://tc39.es/proposal-temporal/#prod-TimeZone +bool ISO8601Parser::parse_time_zone() +{ + // TimeZone : + // TimeZoneOffsetRequired + // TimeZoneNameRequired + return parse_time_zone_offset_required() + || parse_time_zone_name_required(); +} + +// https://tc39.es/proposal-temporal/#prod-CalendarName +bool ISO8601Parser::parse_calendar_name() +{ + // CalChar : + // Alpha + // DecimalDigit + // CalendarNameComponent : + // CalChar CalChar CalChar CalChar[opt] CalChar[opt] CalChar[opt] CalChar[opt] CalChar[opt] + // CalendarNameTail : + // CalendarNameComponent + // CalendarNameComponent - CalendarNameTail + // CalendarName : + // CalendarNameTail + auto parse_calendar_name_component = [&] { + for (size_t i = 0; i < 8; ++i) { + if (!m_state.lexer.next_is(is_ascii_alphanumeric)) + return i > 2; + m_state.lexer.consume(); + } + return true; + }; + StateTransaction transaction { *this }; + do { + if (!parse_calendar_name_component()) + return false; + } while (m_state.lexer.consume_specific('-')); + m_state.parse_result.calendar_name = transaction.parsed_string_view(); + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-Calendar +bool ISO8601Parser::parse_calendar() +{ + // Calendar : + // [u-ca= CalendarName ] + StateTransaction transaction { *this }; + if (!m_state.lexer.consume_specific("[u-ca="sv)) + return false; + if (!parse_calendar_name()) + return false; + if (!m_state.lexer.consume_specific(']')) + return false; + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-TimeSpec +bool ISO8601Parser::parse_time_spec() +{ + // TimeSpec : + // TimeHour + // TimeHour : TimeMinute + // TimeHour TimeMinute + // TimeHour : TimeMinute : TimeSecond TimeFraction[opt] + // TimeHour TimeMinute TimeSecond TimeFraction[opt] + StateTransaction transaction { *this }; + if (!parse_time_hour()) + return false; + if (m_state.lexer.consume_specific(':')) { + if (!parse_time_minute()) + return false; + if (m_state.lexer.consume_specific(':')) { + if (!parse_time_second()) + return false; + (void)parse_time_fraction(); + } + } else if (parse_time_minute()) { + if (parse_time_second()) + (void)parse_time_fraction(); + } + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-TimeSpecSeparator +bool ISO8601Parser::parse_time_spec_separator() +{ + // TimeSpecSeparator : + // DateTimeSeparator TimeSpec + StateTransaction transaction { *this }; + if (!parse_date_time_separator()) + return false; + if (!parse_time_spec()) + return false; + transaction.commit(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-DateTime +bool ISO8601Parser::parse_date_time() +{ + // DateTime : + // Date TimeSpecSeparator[opt] TimeZone[opt] + if (!parse_date()) + return false; + (void)parse_time_spec_separator(); + (void)parse_time_zone(); + return true; +} + +// https://tc39.es/proposal-temporal/#prod-CalendarDateTime +bool ISO8601Parser::parse_calendar_date_time() +{ + // CalendarDateTime : + // DateTime Calendar[opt] + if (!parse_date_time()) + return false; + (void)parse_calendar(); + return true; +} + +} + +#define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS + +Optional parse_iso8601(Production production, StringView input) +{ + auto parser = Detail::ISO8601Parser { input }; + + switch (production) { +#define __JS_ENUMERATE(ProductionName, parse_production) \ + case Production::ProductionName: \ + if (!parser.parse_production()) \ + return {}; \ + break; + JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS +#undef __JS_ENUMERATE + default: + VERIFY_NOT_REACHED(); + } + + // If we parsed successfully but didn't reach the end, the string doesn't match the given production. + if (!parser.lexer().is_eof()) + return {}; + + return parser.parse_result(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h new file mode 100644 index 0000000000..a868c165b5 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace JS::Temporal { + +struct ParseResult { + Optional sign; + Optional date_year; + Optional date_month; + Optional date_day; + Optional time_hour; + Optional time_minute; + Optional time_second; + Optional time_fractional_part; + Optional calendar_name; +}; + +enum class Production { +}; + +Optional parse_iso8601(Production, StringView); + +namespace Detail { + +class ISO8601Parser { +public: + explicit ISO8601Parser(StringView input) + : m_input(input) + , m_state({ + .lexer = GenericLexer { input }, + .parse_result = {}, + }) + { + } + + [[nodiscard]] GenericLexer const& lexer() const { return m_state.lexer; } + [[nodiscard]] ParseResult const& parse_result() const { return m_state.parse_result; } + + [[nodiscard]] bool parse_decimal_digit(); + [[nodiscard]] bool parse_non_zero_digit(); + [[nodiscard]] bool parse_ascii_sign(); + [[nodiscard]] bool parse_sign(); + [[nodiscard]] bool parse_hour(); + [[nodiscard]] bool parse_minute_second(); + [[nodiscard]] bool parse_decimal_separator(); + [[nodiscard]] bool parse_date_time_separator(); + [[nodiscard]] bool parse_date_year(); + [[nodiscard]] bool parse_date_month(); + [[nodiscard]] bool parse_date_day(); + [[nodiscard]] bool parse_date(); + [[nodiscard]] bool parse_time_hour(); + [[nodiscard]] bool parse_time_minute(); + [[nodiscard]] bool parse_time_second(); + [[nodiscard]] bool parse_fractional_part(); + [[nodiscard]] bool parse_time_fractional_part(); + [[nodiscard]] bool parse_fraction(); + [[nodiscard]] bool parse_time_fraction(); + [[nodiscard]] bool parse_time_zone_offset_required(); + [[nodiscard]] bool parse_time_zone_name_required(); + [[nodiscard]] bool parse_time_zone(); + [[nodiscard]] bool parse_calendar_name(); + [[nodiscard]] bool parse_calendar(); + [[nodiscard]] bool parse_time_spec(); + [[nodiscard]] bool parse_time_spec_separator(); + [[nodiscard]] bool parse_date_time(); + [[nodiscard]] bool parse_calendar_date_time(); + +private: + struct State { + GenericLexer lexer; + ParseResult parse_result; + }; + + struct StateTransaction { + explicit StateTransaction(ISO8601Parser& parser) + : m_parser(parser) + , m_saved_state(parser.m_state) + , m_start_index(parser.m_state.lexer.tell()) + { + } + + ~StateTransaction() + { + if (!m_commit) + m_parser.m_state = move(m_saved_state); + } + + void commit() { m_commit = true; } + StringView parsed_string_view() const + { + return m_parser.m_input.substring_view(m_start_index, m_parser.m_state.lexer.tell() - m_start_index); + } + + private: + ISO8601Parser& m_parser; + State m_saved_state; + size_t m_start_index { 0 }; + bool m_commit { false }; + }; + + StringView m_input; + State m_state; +}; + +} + +}