/* * Copyright (c) 2021-2022, Linus Groh * Copyright (c) 2024-2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #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 RFC 9557 / ISO 8601 grammar, https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar 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; } // 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 ::: // 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] auto has_time_designator = parse_time_designator(); if (!has_time_designator) { StateTransaction transaction { *this }; // It is a Syntax Error if ParseText(Time DateTimeUTCOffset[~Z], DateSpecMonthDay) is a Parse Node. // It is a Syntax Error if ParseText(Time DateTimeUTCOffset[~Z], DateSpecYearMonth) is a Parse Node. if (parse_date_spec_month_day() || parse_date_spec_year_month()) return false; } 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 ::: // DurationYearsPart DurationTime[opt] // DurationMonthsPart DurationTime[opt] // DurationWeeksPart DurationTime[opt] // DurationDaysPart DurationTime[opt] auto success = parse_duration_years_part() || parse_duration_months_part() || parse_duration_weeks_part() || parse_duration_days_part(); if (!success) return false; (void)parse_duration_time(); return true; } // https://tc39.es/proposal-temporal/#prod-Duration [[nodiscard]] bool parse_duration() { StateTransaction transaction { *this }; // Duration ::: // ASCIISign[opt] DurationDesignator DurationDate // ASCIISign[opt] DurationDesignator DurationTime (void)scoped_parse(m_state.parse_result.sign, [&]() { return parse_ascii_sign(); }); if (!parse_duration_designator()) return false; auto success = parse_duration_date() || parse_duration_time(); if (!success) return false; transaction.commit(); return true; } // https://tc39.es/proposal-temporal/#prod-DurationYearsPart [[nodiscard]] bool parse_duration_years_part() { StateTransaction transaction { *this }; // DurationYearsPart ::: // DecimalDigits[~Sep] YearsDesignator DurationMonthsPart // DecimalDigits[~Sep] YearsDesignator DurationWeeksPart // DecimalDigits[~Sep] YearsDesignator DurationDaysPart[opt] if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_years)) return false; if (!parse_years_designator()) return false; (void)(parse_duration_months_part() || parse_duration_weeks_part() || parse_duration_days_part()); transaction.commit(); return true; } // https://tc39.es/proposal-temporal/#prod-DurationMonthsPart [[nodiscard]] bool parse_duration_months_part() { StateTransaction transaction { *this }; // DurationMonthsPart ::: // DecimalDigits[~Sep] MonthsDesignator DurationWeeksPart // DecimalDigits[~Sep] MonthsDesignator DurationDaysPart[opt] if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_months)) return false; if (!parse_months_designator()) return false; (void)(parse_duration_weeks_part() || parse_duration_days_part()); transaction.commit(); return true; } // https://tc39.es/proposal-temporal/#prod-DurationWeeksPart [[nodiscard]] bool parse_duration_weeks_part() { StateTransaction transaction { *this }; // DurationWeeksPart ::: // DecimalDigits[~Sep] WeeksDesignator DurationDaysPart[opt] if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_weeks)) return false; if (!parse_weeks_designator()) return false; (void)parse_duration_days_part(); transaction.commit(); return true; } // https://tc39.es/proposal-temporal/#prod-DurationDaysPart [[nodiscard]] bool parse_duration_days_part() { StateTransaction transaction { *this }; // DurationDaysPart ::: // DecimalDigits[~Sep] DaysDesignator if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_days)) return false; if (!parse_days_designator()) return false; transaction.commit(); return true; } // https://tc39.es/proposal-temporal/#prod-DurationTime [[nodiscard]] bool parse_duration_time() { StateTransaction transaction { *this }; // DurationTime ::: // TimeDesignator DurationHoursPart // TimeDesignator DurationMinutesPart // TimeDesignator DurationSecondsPart if (!parse_time_designator()) return false; auto success = parse_duration_hours_part() || parse_duration_minutes_part() || parse_duration_seconds_part(); if (!success) return false; transaction.commit(); return true; } // https://tc39.es/proposal-temporal/#prod-DurationHoursPart [[nodiscard]] bool parse_duration_hours_part() { StateTransaction transaction { *this }; // 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 = scoped_parse(m_state.parse_result.duration_hours_fraction, [&]() { return parse_temporal_decimal_fraction(); }); if (!parse_hours_designator()) return false; if (!is_fractional) (void)(parse_duration_minutes_part() || parse_duration_seconds_part()); transaction.commit(); return true; } // https://tc39.es/proposal-temporal/#prod-DurationMinutesPart [[nodiscard]] bool parse_duration_minutes_part() { StateTransaction transaction { *this }; // 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 = scoped_parse(m_state.parse_result.duration_minutes_fraction, [&]() { return parse_temporal_decimal_fraction(); }); if (!parse_minutes_designator()) return false; if (!is_fractional) (void)parse_duration_seconds_part(); transaction.commit(); return true; } // https://tc39.es/proposal-temporal/#prod-DurationSecondsPart [[nodiscard]] bool parse_duration_seconds_part() { StateTransaction transaction { *this }; // DurationSecondsPart ::: // DecimalDigits[~Sep] TemporalDecimalFraction[opt] SecondsDesignator if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_seconds)) return false; (void)scoped_parse(m_state.parse_result.duration_seconds_fraction, [&]() { return parse_temporal_decimal_fraction(); }); if (!parse_seconds_designator()) return false; transaction.commit(); 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() { // 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/ecma262/#prod-DecimalDigits [[nodiscard]] bool parse_decimal_digits(Separator separator, Optional& result) { StateTransaction transaction { *this }; // FIXME: Implement [+Sep] if it's ever needed. VERIFY(separator == Separator::No); // DecimalDigits[Sep] :: // DecimalDigit // DecimalDigits[?Sep] DecimalDigit // [+Sep] DecimalDigits[+Sep] NumericLiteralSeparator DecimalDigit if (!parse_decimal_digit()) return {}; while (parse_decimal_digit()) ; result = transaction.parsed_string_view(); transaction.commit(); return true; } // https://tc39.es/ecma262/#prod-NonZeroDigit [[nodiscard]] bool 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/ecma262/#prod-ASCIISign [[nodiscard]] bool parse_ascii_sign() { // ASCIISign : one of // + - return m_state.lexer.consume_specific('+') || m_state.lexer.consume_specific('-'); } // 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() { // TemporalDecimalSeparator ::: one of // . , return m_state.lexer.consume_specific('.') || m_state.lexer.consume_specific(','); } // https://tc39.es/proposal-temporal/#prod-DurationDesignator [[nodiscard]] bool parse_duration_designator() { // DurationDesignator : one of // P p return m_state.lexer.consume_specific('P') || m_state.lexer.consume_specific('p'); } // https://tc39.es/proposal-temporal/#prod-YearsDesignator [[nodiscard]] bool parse_years_designator() { // YearsDesignator : one of // Y y return m_state.lexer.consume_specific('Y') || m_state.lexer.consume_specific('y'); } // https://tc39.es/proposal-temporal/#prod-MonthsDesignator [[nodiscard]] bool parse_months_designator() { // MonthsDesignator : one of // M m return m_state.lexer.consume_specific('M') || m_state.lexer.consume_specific('m'); } // https://tc39.es/proposal-temporal/#prod-WeeksDesignator [[nodiscard]] bool parse_weeks_designator() { // WeeksDesignator : one of // W w return m_state.lexer.consume_specific('W') || m_state.lexer.consume_specific('w'); } // https://tc39.es/proposal-temporal/#prod-DaysDesignator [[nodiscard]] bool parse_days_designator() { // DaysDesignator : one of // D d return m_state.lexer.consume_specific('D') || m_state.lexer.consume_specific('d'); } // https://tc39.es/proposal-temporal/#prod-HoursDesignator [[nodiscard]] bool parse_hours_designator() { // HoursDesignator : one of // H h return m_state.lexer.consume_specific('H') || m_state.lexer.consume_specific('h'); } // https://tc39.es/proposal-temporal/#prod-MinutesDesignator [[nodiscard]] bool parse_minutes_designator() { // MinutesDesignator : one of // M m return m_state.lexer.consume_specific('M') || m_state.lexer.consume_specific('m'); } // https://tc39.es/proposal-temporal/#prod-SecondsDesignator [[nodiscard]] bool parse_seconds_designator() { // SecondsDesignator : one of // S s 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; }; 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; }; #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) { ISO8601Parser parser { 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(); } 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; } }