diff --git a/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp b/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp index 7526486fc3c..60cbd93b55d 100644 --- a/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp +++ b/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp @@ -7,10 +7,70 @@ #include #include +#include #include +#include namespace JS::Temporal { +enum class Extended { + No, + Yes, +}; + +enum class Separator { + No, + Yes, +}; + +enum class TimeRequired { + No, + Yes, +}; + +enum class ZDesignator { + No, + Yes, +}; + +enum class Zoned { + No, + Yes, +}; + +// 13.30.1 Static Semantics: IsValidMonthDay, https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar-static-semantics-isvalidmonthday +static bool is_valid_month_day(ParseResult const& result) +{ + // 1. If DateDay is "31" and DateMonth is "02", "04", "06", "09", "11", return false. + if (result.date_day == "31"sv && result.date_month->is_one_of("02"sv, "04"sv, "06"sv, "09"sv, "11"sv)) + return false; + + // 2. If DateMonth is "02" and DateDay is "30", return false. + if (result.date_month == "02"sv && result.date_day == "30"sv) + return false; + + // 3. Return true. + return true; +} + +// 13.30.2 Static Semantics: IsValidDate, https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar-static-semantics-isvaliddate +static bool is_valid_date(ParseResult const& result) +{ + // 1. If IsValidMonthDay of DateSpec is false, return false. + if (!is_valid_month_day(result)) + return false; + + // 2. Let year be ℝ(StringToNumber(CodePointsToString(DateYear))). + auto year = string_to_number(*result.date_year); + + // 3. If DateMonth is "02" and DateDay is "29" and MathematicalInLeapYear(EpochTimeForYear(year)) = 0, return false. + if (result.date_month == "02"sv && result.date_day == "29"sv && mathematical_in_leap_year(epoch_time_for_year(year)) == 0) + return false; + + // 4. Return true. + return true; +} + // 13.30 ISO 8601 grammar, https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar class ISO8601Parser { public: @@ -26,23 +86,718 @@ public: [[nodiscard]] GenericLexer const& lexer() const { return m_state.lexer; } [[nodiscard]] ParseResult const& parse_result() const { return m_state.parse_result; } - enum class Separator { - No, - Yes, - }; + // https://tc39.es/proposal-temporal/#prod-TemporalDateTimeString + [[nodiscard]] bool parse_temporal_date_time_string() + { + // TemporalDateTimeString[Zoned] ::: + // AnnotatedDateTime[?Zoned, ~TimeRequired] + return parse_annotated_date_time(Zoned::No, TimeRequired::No); + } + + // https://tc39.es/proposal-temporal/#prod-TemporalDateTimeString + [[nodiscard]] bool parse_temporal_zoned_date_time_string() + { + // TemporalDateTimeString[Zoned] ::: + // AnnotatedDateTime[?Zoned, ~TimeRequired] + return parse_annotated_date_time(Zoned::Yes, TimeRequired::No); + } // https://tc39.es/proposal-temporal/#prod-TemporalDurationString [[nodiscard]] bool parse_temporal_duration_string() { - // TemporalDurationString : + // TemporalDurationString ::: // Duration return parse_duration(); } + // https://tc39.es/proposal-temporal/#prod-TemporalInstantString + [[nodiscard]] bool parse_temporal_instant_string() + { + // TemporalInstantString ::: + // Date DateTimeSeparator Time DateTimeUTCOffset[+Z] TimeZoneAnnotation[opt] Annotations[opt] + if (!parse_date()) + return false; + if (!parse_date_time_separator()) + return false; + if (!parse_time()) + return false; + if (!parse_date_time_utc_offset(ZDesignator::Yes)) + return false; + + (void)parse_time_zone_annotation(); + (void)parse_annotations(); + + return true; + } + + // https://tc39.es/proposal-temporal/#prod-TemporalMonthDayString + [[nodiscard]] bool parse_temporal_month_day_string() + { + // TemporalMonthDayString ::: + // AnnotatedMonthDay + // AnnotatedDateTime[~Zoned, ~TimeRequired] + // NOTE: Reverse order here because `AnnotatedMonthDay` can be a subset of `AnnotatedDateTime`. + return parse_annotated_date_time(Zoned::No, TimeRequired::No) || parse_annotated_month_day(); + } + + // https://tc39.es/proposal-temporal/#prod-TemporalTimeString + [[nodiscard]] bool parse_temporal_time_string() + { + // TemporalTimeString ::: + // AnnotatedTime + // AnnotatedDateTime[~Zoned, +TimeRequired] + // NOTE: Reverse order here because `AnnotatedTime` can be a subset of `AnnotatedDateTime`. + return parse_annotated_date_time(Zoned::No, TimeRequired::Yes) || parse_annotated_time(); + } + + // https://tc39.es/proposal-temporal/#prod-TemporalYearMonthString + [[nodiscard]] bool parse_temporal_year_month_string() + { + // TemporalYearMonthString ::: + // AnnotatedYearMonth + // AnnotatedDateTime[~Zoned, ~TimeRequired] + // NOTE: Reverse order here because `AnnotatedYearMonth` can be a subset of `AnnotatedDateTime`. + return parse_annotated_date_time(Zoned::No, TimeRequired::No) || parse_annotated_year_month(); + } + + // https://tc39.es/proposal-temporal/#prod-AnnotatedDateTime + [[nodiscard]] bool parse_annotated_date_time(Zoned zoned, TimeRequired time_required) + { + // AnnotatedDateTime[Zoned, TimeRequired] ::: + // [~Zoned] DateTime[~Z, ?TimeRequired] TimeZoneAnnotation[opt] Annotations[opt] + // [+Zoned] DateTime[+Z, ?TimeRequired] TimeZoneAnnotation Annotations[opt] + if (!parse_date_time(zoned == Zoned::Yes ? ZDesignator::Yes : ZDesignator::No, time_required)) + return false; + + if (!parse_time_zone_annotation()) { + if (zoned == Zoned::Yes) + return false; + } + + (void)parse_annotations(); + + return true; + } + + // https://tc39.es/proposal-temporal/#prod-AnnotatedMonthDay + [[nodiscard]] bool parse_annotated_month_day() + { + // AnnotatedMonthDay ::: + // DateSpecMonthDay TimeZoneAnnotation[opt] Annotations[opt] + if (!parse_date_spec_month_day()) + return false; + + (void)parse_time_zone_annotation(); + (void)parse_annotations(); + + return true; + } + + // https://tc39.es/proposal-temporal/#prod-AnnotatedTime + [[nodiscard]] bool parse_annotated_time() + { + // AnnotatedTime ::: + // TimeDesignator Time DateTimeUTCOffset[~Z][opt] TimeZoneAnnotation[opt] Annotations[opt] + // Time DateTimeUTCOffset[~Z][opt] TimeZoneAnnotation[opt] Annotations[opt] + (void)parse_time_designator(); + + if (!parse_time()) + return false; + + (void)parse_date_time_utc_offset(ZDesignator::No); + (void)parse_time_zone_annotation(); + (void)parse_annotations(); + + return true; + } + + // https://tc39.es/proposal-temporal/#prod-AnnotatedYearMonth + [[nodiscard]] bool parse_annotated_year_month() + { + // AnnotatedYearMonth ::: + // DateSpecYearMonth TimeZoneAnnotation[opt] Annotations[opt] + if (!parse_date_spec_year_month()) + return false; + + (void)parse_time_zone_annotation(); + (void)parse_annotations(); + + return true; + } + + // https://tc39.es/proposal-temporal/#prod-DateTime + [[nodiscard]] bool parse_date_time(ZDesignator z_designator, TimeRequired time_required) + { + StateTransaction transaction { *this }; + + // DateTime[Z, TimeRequired] ::: + // [~TimeRequired] Date + // Date DateTimeSeparator Time DateTimeUTCOffset[?Z][opt] + if (!parse_date()) + return false; + + if (parse_date_time_separator()) { + if (!parse_time()) + return false; + + (void)parse_date_time_utc_offset(z_designator); + } else if (time_required == TimeRequired::Yes) { + return false; + } + + transaction.commit(); + return true; + } + + // https://tc39.es/proposal-temporal/#prod-Date + [[nodiscard]] bool parse_date() + { + // Date ::: + // DateSpec[+Extended] + // DateSpec[~Extended] + return parse_date_spec(Extended::Yes) || parse_date_spec(Extended::No); + } + + // https://tc39.es/proposal-temporal/#prod-DateSpec + [[nodiscard]] bool parse_date_spec(Extended extended) + { + StateTransaction transaction { *this }; + + // DateSpec[Extended] ::: + // DateYear DateSeparator[?Extended] DateMonth DateSeparator[?Extended] DateDay + if (!parse_date_year()) + return false; + if (!parse_date_separator(extended)) + return false; + if (!parse_date_month()) + return false; + if (!parse_date_separator(extended)) + return false; + if (!parse_date_day()) + return false; + + // It is a Syntax Error if IsValidDate of DateSpec is false. + if (!is_valid_date(m_state.parse_result)) + return false; + + transaction.commit(); + return true; + } + + // https://tc39.es/proposal-temporal/#prod-DateSpecMonthDay + [[nodiscard]] bool parse_date_spec_month_day() + { + StateTransaction transaction { *this }; + + // DateSpecMonthDay ::: + // --[opt] DateMonth DateSeparator[+Extended] DateDay + // --[opt] DateMonth DateSeparator[~Extended] DateDay + (void)m_state.lexer.consume_specific("--"sv); + + if (!parse_date_month()) + return false; + if (!parse_date_separator(Extended::Yes) && !parse_date_separator(Extended::No)) + return false; + if (!parse_date_day()) + return false; + + // It is a Syntax Error if IsValidMonthDay of DateSpecMonthDay is false. + if (!is_valid_month_day(m_state.parse_result)) + return false; + + transaction.commit(); + return true; + } + + // https://tc39.es/proposal-temporal/#prod-DateSpecYearMonth + [[nodiscard]] bool parse_date_spec_year_month() + { + StateTransaction transaction { *this }; + + // DateSpecYearMonth ::: + // DateYear DateSeparator[+Extended] DateMonth + // DateYear DateSeparator[~Extended] DateMonth + if (!parse_date_year()) + return false; + if (!parse_date_separator(Extended::Yes) && !parse_date_separator(Extended::No)) + return false; + if (!parse_date_month()) + return false; + + transaction.commit(); + return true; + } + + // https://tc39.es/proposal-temporal/#prod-DateYear + [[nodiscard]] bool parse_date_year() + { + StateTransaction transaction { *this }; + + // DateYear ::: + // DecimalDigit DecimalDigit DecimalDigit DecimalDigit + // ASCIISign DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit + size_t digit_count = parse_ascii_sign() ? 6 : 4; + + for (size_t i = 0; i < digit_count; ++i) { + if (!parse_decimal_digit()) + return false; + } + + // It is a Syntax Error if DateYear is "-000000". + if (transaction.parsed_string_view() == "-000000"sv) + return false; + + m_state.parse_result.date_year = transaction.parsed_string_view(); + transaction.commit(); + return true; + } + + // https://tc39.es/proposal-temporal/#prod-DateMonth + [[nodiscard]] bool parse_date_month() + { + StateTransaction transaction { *this }; + + // DateMonth ::: + // 0 NonZeroDigit + // 10 + // 11 + // 12 + 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 + [[nodiscard]] bool parse_date_day() + { + StateTransaction transaction { *this }; + + // DateDay ::: + // 0 NonZeroDigit + // 1 DecimalDigit + // 2 DecimalDigit + // 30 + // 31 + 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-DateTimeUTCOffset + [[nodiscard]] bool parse_date_time_utc_offset(ZDesignator z_designator) + { + // DateTimeUTCOffset[Z] ::: + // [+Z] UTCDesignator + // UTCOffset[+SubMinutePrecision] + if (z_designator == ZDesignator::Yes) { + if (parse_utc_designator()) + return true; + } + + return parse_utc_offset(SubMinutePrecision::Yes, m_state.parse_result.date_time_offset); + } + + // https://tc39.es/proposal-temporal/#prod-Time + [[nodiscard]] bool parse_time() + { + // Time ::: + // TimeSpec[+Extended] + // TimeSpec[~Extended] + return parse_time_spec(); + } + + // https://tc39.es/proposal-temporal/#prod-TimeSpec + [[nodiscard]] bool parse_time_spec() + { + StateTransaction transaction { *this }; + + auto parse_time_hour = [&]() { + return scoped_parse(m_state.parse_result.time_hour, [&]() { return parse_hour(); }); + }; + auto parse_time_minute = [&]() { + return scoped_parse(m_state.parse_result.time_minute, [&]() { return parse_minute_second(); }); + }; + auto parse_time_fraction = [&]() { + return scoped_parse(m_state.parse_result.time_fraction, [&]() { return parse_temporal_decimal_fraction(); }); + }; + + // TimeSpec[Extended] ::: + // Hour + // Hour TimeSeparator[?Extended] MinuteSecond + // Hour TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] TimeSecond TemporalDecimalFraction[opt] + if (!parse_time_hour()) + return false; + + if (parse_time_separator(Extended::Yes)) { + if (!parse_time_minute()) + return false; + + if (parse_time_separator(Extended::Yes)) { + if (!parse_time_second()) + return false; + + (void)parse_time_fraction(); + } + } else if (parse_time_minute() && parse_time_second()) { + (void)parse_time_fraction(); + } + + transaction.commit(); + return true; + } + + // https://tc39.es/proposal-temporal/#prod-TimeSecond + [[nodiscard]] bool parse_time_second() + { + StateTransaction transaction { *this }; + + // TimeSecond ::: + // MinuteSecond + // 60 + 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-TimeZoneAnnotation + [[nodiscard]] bool parse_time_zone_annotation() + { + StateTransaction transaction { *this }; + + // TimeZoneAnnotation ::: + // [ AnnotationCriticalFlag[opt] TimeZoneIdentifier ] + if (!m_state.lexer.consume_specific('[')) + return false; + + (void)parse_annotation_critical_flag(); + if (!parse_time_zone_identifier()) + return false; + + if (!m_state.lexer.consume_specific(']')) + return false; + + transaction.commit(); + return true; + } + + // https://tc39.es/proposal-temporal/#prod-TimeZoneIdentifier + [[nodiscard]] bool parse_time_zone_identifier() + { + StateTransaction transaction { *this }; + + // TimeZoneIdentifier ::: + // UTCOffset[~SubMinutePrecision] + // TimeZoneIANAName + auto success = parse_utc_offset(SubMinutePrecision::No, m_state.parse_result.time_zone_offset) || parse_time_zone_iana_name(); + if (!success) + return false; + + m_state.parse_result.time_zone_identifier = transaction.parsed_string_view(); + transaction.commit(); + return true; + } + + // https://tc39.es/proposal-temporal/#prod-TimeZoneIANAName + [[nodiscard]] bool parse_time_zone_iana_name() + { + StateTransaction transaction { *this }; + + // TimeZoneIANAName ::: + // TimeZoneIANANameComponent + // TimeZoneIANAName / TimeZoneIANANameComponent + if (!parse_time_zone_iana_name_component()) + return false; + + while (m_state.lexer.consume_specific('/')) { + if (!parse_time_zone_iana_name_component()) + return false; + } + + m_state.parse_result.time_zone_iana_name = transaction.parsed_string_view(); + transaction.commit(); + return true; + } + + // https://tc39.es/proposal-temporal/#prod-TimeZoneIANANameComponent + [[nodiscard]] bool parse_time_zone_iana_name_component() + { + // TimeZoneIANANameComponent ::: + // TZLeadingChar + // TimeZoneIANANameComponent TZChar + if (!parse_tz_leading_char()) + return false; + while (parse_tz_leading_char()) + ; + while (parse_tz_char()) + ; + + return true; + } + + // https://tc39.es/proposal-temporal/#prod-TZLeadingChar + [[nodiscard]] bool parse_tz_leading_char() + { + // TZLeadingChar ::: + // Alpha + // . + // _ + return parse_alpha() || m_state.lexer.consume_specific('.') || m_state.lexer.consume_specific('_'); + } + + // https://tc39.es/proposal-temporal/#prod-TZChar + [[nodiscard]] bool parse_tz_char() + { + // TZChar ::: + // TZLeadingChar + // DecimalDigit + // - + // + + return parse_tz_leading_char() || parse_decimal_digit() || m_state.lexer.consume_specific('.') || m_state.lexer.consume_specific('+'); + } + + // https://tc39.es/proposal-temporal/#prod-Annotations + [[nodiscard]] bool parse_annotations() + { + // Annotations ::: + // Annotation Annotationsopt + if (!parse_annotation()) + return false; + while (parse_annotation()) + ; + return true; + } + + // https://tc39.es/proposal-temporal/#prod-Annotation + [[nodiscard]] bool parse_annotation() + { + StateTransaction transaction { *this }; + + Optional key; + Optional value; + + // Annotation ::: + // [ AnnotationCriticalFlag[opt] AnnotationKey = AnnotationValue ] + if (!m_state.lexer.consume_specific('[')) + return false; + + auto critical = parse_annotation_critical_flag(); + + if (!scoped_parse(key, [&]() { return parse_annotation_key(); })) + return false; + if (!m_state.lexer.consume_specific('=')) + return false; + if (!scoped_parse(value, [&]() { return parse_annotation_value(); })) + return false; + + if (!m_state.lexer.consume_specific(']')) + return false; + + m_state.parse_result.annotations.empend(critical, *key, *value); + transaction.commit(); + return true; + } + + // https://tc39.es/proposal-temporal/#prod-AnnotationKey + [[nodiscard]] bool parse_annotation_key() + { + // AnnotationKey ::: + // AKeyLeadingChar + // AnnotationKey AKeyChar + if (!parse_annotation_key_leading_char()) + return false; + while (parse_annotation_key_leading_char()) + ; + while (parse_annotation_key_char()) + ; + + return true; + } + + // https://tc39.es/proposal-temporal/#prod-AKeyLeadingChar + [[nodiscard]] bool parse_annotation_key_leading_char() + { + // AKeyLeadingChar ::: + // LowercaseAlpha + // _ + return parse_lowercase_alpha() || m_state.lexer.consume_specific('_'); + } + + // https://tc39.es/proposal-temporal/#prod-AKeyChar + [[nodiscard]] bool parse_annotation_key_char() + { + // AKeyChar ::: + // AKeyLeadingChar + // DecimalDigit + // - + return parse_annotation_key_leading_char() || parse_decimal_digit() || m_state.lexer.consume_specific('-'); + } + + // https://tc39.es/proposal-temporal/#prod-AnnotationValue + [[nodiscard]] bool parse_annotation_value() + { + // AnnotationValue ::: + // AnnotationValueComponent + // AnnotationValueComponent - AnnotationValue + if (!parse_annotation_value_component()) + return false; + + while (m_state.lexer.consume_specific('-')) { + if (!parse_annotation_value_component()) + return false; + } + + return true; + } + + // https://tc39.es/proposal-temporal/#prod-AnnotationValueComponent + [[nodiscard]] bool parse_annotation_value_component() + { + // AnnotationValueComponent ::: + // Alpha AnnotationValueComponent[opt] + // DecimalDigit AnnotationValueComponent[opt] + auto parse_component = [&]() { return parse_alpha() || parse_decimal_digit(); }; + + if (!parse_component()) + return false; + while (parse_component()) + ; + + return true; + } + + // https://tc39.es/proposal-temporal/#prod-UTCOffset + [[nodiscard]] bool parse_utc_offset(SubMinutePrecision sub_minute_precision, Optional& result) + { + StateTransaction transaction { *this }; + TimeZoneOffset time_zone_offset; + + auto parse_utc_sign = [&]() { + return scoped_parse(time_zone_offset.sign, [&]() { return parse_ascii_sign(); }); + }; + auto parse_utc_hours = [&]() { + return scoped_parse(time_zone_offset.hours, [&]() { return parse_hour(); }); + }; + auto parse_utc_minutes = [&]() { + return scoped_parse(time_zone_offset.minutes, [&]() { return parse_minute_second(); }); + }; + auto parse_utc_seconds = [&]() { + return scoped_parse(time_zone_offset.seconds, [&]() { return parse_minute_second(); }); + }; + auto parse_utc_fraction = [&]() { + return scoped_parse(time_zone_offset.fraction, [&]() { return parse_temporal_decimal_fraction(); }); + }; + + // UTCOffset[SubMinutePrecision] ::: + // ASCIISign Hour + // ASCIISign Hour TimeSeparator[+Extended] MinuteSecond + // ASCIISign Hour TimeSeparator[~Extended] MinuteSecond + // [+SubMinutePrecision] ASCIISign Hour TimeSeparator[+Extended] MinuteSecond TimeSeparator[+Extended] MinuteSecond TemporalDecimalFraction[opt] + // [+SubMinutePrecision] ASCIISign Hour TimeSeparator[~Extended] MinuteSecond TimeSeparator[~Extended] MinuteSecond TemporalDecimalFraction[opt] + if (!parse_utc_sign()) + return false; + if (!parse_utc_hours()) + return false; + + if (parse_time_separator(Extended::Yes)) { + if (!parse_utc_minutes()) + return false; + + if (sub_minute_precision == SubMinutePrecision::Yes && parse_time_separator(Extended::Yes)) { + if (!parse_utc_seconds()) + return false; + + (void)parse_utc_fraction(); + } + } else if (parse_utc_minutes()) { + if (sub_minute_precision == SubMinutePrecision::Yes && parse_utc_seconds()) + (void)parse_utc_fraction(); + } + + time_zone_offset.source_text = transaction.parsed_string_view(); + result = move(time_zone_offset); + + transaction.commit(); + return true; + } + + // https://tc39.es/ecma262/#prod-Hour + [[nodiscard]] bool parse_hour() + { + // Hour ::: + // 0 DecimalDigit + // 1 DecimalDigit + // 20 + // 21 + // 22 + // 23 + 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; + } + + return true; + } + + // https://tc39.es/ecma262/#prod-MinuteSecond + [[nodiscard]] bool parse_minute_second() + { + // MinuteSecond ::: + // 0 DecimalDigit + // 1 DecimalDigit + // 2 DecimalDigit + // 3 DecimalDigit + // 4 DecimalDigit + // 5 DecimalDigit + 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; + + return true; + } + // https://tc39.es/proposal-temporal/#prod-DurationDate [[nodiscard]] bool parse_duration_date() { - // DurationDate : + // DurationDate ::: // DurationYearsPart DurationTime[opt] // DurationMonthsPart DurationTime[opt] // DurationWeeksPart DurationTime[opt] @@ -63,7 +818,7 @@ public: // Duration ::: // ASCIISign[opt] DurationDesignator DurationDate // ASCIISign[opt] DurationDesignator DurationTime - (void)parse_ascii_sign(); + (void)scoped_parse(m_state.parse_result.sign, [&]() { return parse_ascii_sign(); }); if (!parse_duration_designator()) return false; @@ -81,7 +836,7 @@ public: { StateTransaction transaction { *this }; - // DurationYearsPart : + // DurationYearsPart ::: // DecimalDigits[~Sep] YearsDesignator DurationMonthsPart // DecimalDigits[~Sep] YearsDesignator DurationWeeksPart // DecimalDigits[~Sep] YearsDesignator DurationDaysPart[opt] @@ -102,7 +857,7 @@ public: { StateTransaction transaction { *this }; - // DurationMonthsPart : + // DurationMonthsPart ::: // DecimalDigits[~Sep] MonthsDesignator DurationWeeksPart // DecimalDigits[~Sep] MonthsDesignator DurationDaysPart[opt] if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_months)) @@ -122,7 +877,7 @@ public: { StateTransaction transaction { *this }; - // DurationWeeksPart : + // DurationWeeksPart ::: // DecimalDigits[~Sep] WeeksDesignator DurationDaysPart[opt] if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_weeks)) return false; @@ -141,7 +896,7 @@ public: { StateTransaction transaction { *this }; - // DurationDaysPart : + // DurationDaysPart ::: // DecimalDigits[~Sep] DaysDesignator if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_days)) return false; @@ -158,7 +913,7 @@ public: { StateTransaction transaction { *this }; - // DurationTime : + // DurationTime ::: // TimeDesignator DurationHoursPart // TimeDesignator DurationMinutesPart // TimeDesignator DurationSecondsPart @@ -178,14 +933,14 @@ public: { StateTransaction transaction { *this }; - // DurationHoursPart : + // DurationHoursPart ::: // DecimalDigits[~Sep] TemporalDecimalFraction HoursDesignator // DecimalDigits[~Sep] HoursDesignator DurationMinutesPart // DecimalDigits[~Sep] HoursDesignator DurationSecondsPart[opt] if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_hours)) return false; - auto is_fractional = parse_temporal_decimal_fraction(m_state.parse_result.duration_hours_fraction); + auto is_fractional = scoped_parse(m_state.parse_result.duration_hours_fraction, [&]() { return parse_temporal_decimal_fraction(); }); if (!parse_hours_designator()) return false; @@ -201,13 +956,13 @@ public: { StateTransaction transaction { *this }; - // DurationMinutesPart : + // DurationMinutesPart ::: // DecimalDigits[~Sep] TemporalDecimalFraction MinutesDesignator // DecimalDigits[~Sep] MinutesDesignator DurationSecondsPart[opt] if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_minutes)) return false; - auto is_fractional = parse_temporal_decimal_fraction(m_state.parse_result.duration_minutes_fraction); + auto is_fractional = scoped_parse(m_state.parse_result.duration_minutes_fraction, [&]() { return parse_temporal_decimal_fraction(); }); if (!parse_minutes_designator()) return false; @@ -223,12 +978,12 @@ public: { StateTransaction transaction { *this }; - // DurationSecondsPart : + // DurationSecondsPart ::: // DecimalDigits[~Sep] TemporalDecimalFraction[opt] SecondsDesignator if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_seconds)) return false; - (void)parse_temporal_decimal_fraction(m_state.parse_result.duration_seconds_fraction); + (void)scoped_parse(m_state.parse_result.duration_seconds_fraction, [&]() { return parse_temporal_decimal_fraction(); }); if (!parse_seconds_designator()) return false; @@ -237,6 +992,56 @@ public: return true; } + // https://tc39.es/ecma262/#prod-TemporalDecimalFraction + [[nodiscard]] bool parse_temporal_decimal_fraction() + { + // TemporalDecimalFraction ::: + // TemporalDecimalSeparator DecimalDigit + // TemporalDecimalSeparator DecimalDigit DecimalDigit + // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit + // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit + // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit + // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit + // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit + // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit + // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit + if (!parse_temporal_decimal_separator()) + return false; + 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-Alpha + [[nodiscard]] bool parse_alpha() + { + // Alpha ::: one of + // A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z + if (m_state.lexer.next_is(is_ascii_alpha)) { + m_state.lexer.consume(); + return true; + } + return false; + } + + // https://tc39.es/proposal-temporal/#prod-LowercaseAlpha + [[nodiscard]] bool parse_lowercase_alpha() + { + // LowercaseAlpha ::: one of + // a b c d e f g h i j k l m n o p q r s t u v w x y z + if (m_state.lexer.next_is(is_ascii_lower_alpha)) { + m_state.lexer.consume(); + return true; + } + return false; + } + // https://tc39.es/ecma262/#prod-DecimalDigit [[nodiscard]] bool parse_decimal_digit() { @@ -272,53 +1077,66 @@ public: return true; } - // https://tc39.es/ecma262/#prod-TemporalDecimalFraction - [[nodiscard]] bool parse_temporal_decimal_fraction(Optional& result) + // https://tc39.es/ecma262/#prod-NonZeroDigit + [[nodiscard]] bool parse_non_zero_digit() { - StateTransaction transaction { *this }; - - // TemporalDecimalFraction ::: - // TemporalDecimalSeparator DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit - if (!parse_temporal_decimal_separator()) - return false; - if (!parse_decimal_digit()) - return false; - - for (size_t i = 0; i < 8; ++i) { - if (!parse_decimal_digit()) - break; + // 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; } - - result = transaction.parsed_string_view(); - - transaction.commit(); - return true; + return false; } // https://tc39.es/ecma262/#prod-ASCIISign [[nodiscard]] bool parse_ascii_sign() { - StateTransaction transaction { *this }; - // ASCIISign : one of // + - - if (!m_state.lexer.next_is(is_any_of("+-"sv))) - return false; + return m_state.lexer.consume_specific('+') || m_state.lexer.consume_specific('-'); + } - m_state.parse_result.sign = m_state.lexer.consume(); - - transaction.commit(); + // https://tc39.es/proposal-temporal/#prod-DateSeparator + [[nodiscard]] bool parse_date_separator(Extended extended) + { + // DateSeparator[Extended] ::: + // [+Extended] - + // [~Extended] [empty] + if (extended == Extended::Yes) + return m_state.lexer.consume_specific('-'); return true; } + // https://tc39.es/ecma262/#prod-TimeSeparator + [[nodiscard]] bool parse_time_separator(Extended extended) + { + // TimeSeparator[Extended] ::: + // [+Extended] : + // [~Extended] [empty] + if (extended == Extended::Yes) + return m_state.lexer.consume_specific(':'); + return true; + } + + // https://tc39.es/proposal-temporal/#prod-TimeDesignator + [[nodiscard]] bool parse_time_designator() + { + // TimeDesignator : one of + // T t + return m_state.lexer.consume_specific('T') || m_state.lexer.consume_specific('t'); + } + + // https://tc39.es/proposal-temporal/#prod-DateTimeSeparator + [[nodiscard]] bool 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/ecma262/#prod-TemporalDecimalSeparator [[nodiscard]] bool parse_temporal_decimal_separator() { @@ -335,14 +1153,6 @@ public: return m_state.lexer.consume_specific('P') || m_state.lexer.consume_specific('p'); } - // https://tc39.es/proposal-temporal/#prod-TimeDesignator - [[nodiscard]] bool parse_time_designator() - { - // TimeDesignator : one of - // T t - return m_state.lexer.consume_specific('T') || m_state.lexer.consume_specific('t'); - } - // https://tc39.es/proposal-temporal/#prod-YearsDesignator [[nodiscard]] bool parse_years_designator() { @@ -399,7 +1209,48 @@ public: return m_state.lexer.consume_specific('S') || m_state.lexer.consume_specific('s'); } + // https://tc39.es/proposal-temporal/#prod-UTCDesignator + [[nodiscard]] bool parse_utc_designator() + { + StateTransaction transaction { *this }; + + // UTCDesignator : one of + // Z z + auto success = m_state.lexer.consume_specific('Z') || m_state.lexer.consume_specific('z'); + if (!success) + return false; + + m_state.parse_result.utc_designator = transaction.parsed_string_view(); + transaction.commit(); + return true; + } + + // https://tc39.es/proposal-temporal/#prod-AnnotationCriticalFlag + [[nodiscard]] bool parse_annotation_critical_flag() + { + // AnnotationCriticalFlag ::: + // ! + return m_state.lexer.consume_specific('!'); + } + private: + template + [[nodiscard]] bool scoped_parse(Optional& storage, Parser&& parser) + { + StateTransaction transaction { *this }; + + if (!parser()) + return false; + + if constexpr (IsSame) + storage = transaction.parsed_string_view()[0]; + else + storage = transaction.parsed_string_view(); + + transaction.commit(); + return true; + } + struct State { GenericLexer lexer; ParseResult parse_result; @@ -436,8 +1287,17 @@ private: State m_state; }; -#define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS \ - __JS_ENUMERATE(TemporalDurationString, parse_temporal_duration_string) +#define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS \ + __JS_ENUMERATE(AnnotationValue, parse_annotation_value) \ + __JS_ENUMERATE(DateMonth, parse_date_month) \ + __JS_ENUMERATE(TemporalDateTimeString, parse_temporal_date_time_string) \ + __JS_ENUMERATE(TemporalDurationString, parse_temporal_duration_string) \ + __JS_ENUMERATE(TemporalInstantString, parse_temporal_instant_string) \ + __JS_ENUMERATE(TemporalMonthDayString, parse_temporal_month_day_string) \ + __JS_ENUMERATE(TemporalTimeString, parse_temporal_time_string) \ + __JS_ENUMERATE(TemporalYearMonthString, parse_temporal_year_month_string) \ + __JS_ENUMERATE(TemporalZonedDateTimeString, parse_temporal_zoned_date_time_string) \ + __JS_ENUMERATE(TimeZoneIdentifier, parse_time_zone_identifier) Optional parse_iso8601(Production production, StringView input) { @@ -462,4 +1322,20 @@ Optional parse_iso8601(Production production, StringView input) return parser.parse_result(); } +Optional parse_utc_offset(StringView input, SubMinutePrecision sub_minute_precision) +{ + ISO8601Parser parser { input }; + + Optional utc_offset; + + if (!parser.parse_utc_offset(sub_minute_precision, utc_offset)) + return {}; + + // 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 utc_offset; +} + } diff --git a/Libraries/LibJS/Runtime/Temporal/ISO8601.h b/Libraries/LibJS/Runtime/Temporal/ISO8601.h index 0396a8661aa..298732868e9 100644 --- a/Libraries/LibJS/Runtime/Temporal/ISO8601.h +++ b/Libraries/LibJS/Runtime/Temporal/ISO8601.h @@ -9,11 +9,42 @@ #include #include +#include namespace JS::Temporal { +struct Annotation { + bool critical { false }; + StringView key; + StringView value; +}; + +struct TimeZoneOffset { + Optional sign; + Optional hours; + Optional minutes; + Optional seconds; + Optional fraction; + StringView source_text; +}; + struct ParseResult { Optional sign; + + Optional date_year; + Optional date_month; + Optional date_day; + Optional time_hour; + Optional time_minute; + Optional time_second; + Optional time_fraction; + Optional date_time_offset; + + Optional utc_designator; + Optional time_zone_identifier; + Optional time_zone_iana_name; + Optional time_zone_offset; + Optional duration_years; Optional duration_months; Optional duration_weeks; @@ -24,12 +55,30 @@ struct ParseResult { Optional duration_minutes_fraction; Optional duration_seconds; Optional duration_seconds_fraction; + + Vector annotations; }; enum class Production { + AnnotationValue, + DateMonth, + TemporalDateTimeString, TemporalDurationString, + TemporalInstantString, + TemporalMonthDayString, + TemporalTimeString, + TemporalYearMonthString, + TemporalZonedDateTimeString, + TimeZoneIdentifier, }; Optional parse_iso8601(Production, StringView); +enum class SubMinutePrecision { + No, + Yes, +}; + +Optional parse_utc_offset(StringView, SubMinutePrecision); + }