mirror of
				https://github.com/LadybirdBrowser/ladybird.git
				synced 2025-10-30 21:01:00 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1357 lines
		
	
	
	
		
			45 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1357 lines
		
	
	
	
		
			45 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | ||
|  * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
 | ||
|  * Copyright (c) 2024-2025, Tim Flynn <trflynn89@ladybird.org>
 | ||
|  *
 | ||
|  * SPDX-License-Identifier: BSD-2-Clause
 | ||
|  */
 | ||
| 
 | ||
| #include <AK/CharacterTypes.h>
 | ||
| #include <AK/GenericLexer.h>
 | ||
| #include <LibJS/Runtime/Temporal/DateEquations.h>
 | ||
| #include <LibJS/Runtime/Temporal/ISO8601.h>
 | ||
| #include <LibJS/Runtime/Value.h>
 | ||
| 
 | ||
| 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.31.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.31.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.31 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-AmbiguousTemporalTimeString
 | ||
|     [[nodiscard]] bool parse_ambiguous_temporal_time_string()
 | ||
|     {
 | ||
|         // AmbiguousTemporalTimeString :::
 | ||
|         //     DateSpecMonthDay TimeZoneAnnotation[opt] Annotations[opt]
 | ||
|         //     DateSpecYearMonth TimeZoneAnnotation[opt] Annotations[opt]
 | ||
|         return parse_annotated_month_day() || parse_annotated_year_month();
 | ||
|     }
 | ||
| 
 | ||
|     // 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;
 | ||
| 
 | ||
|         // Note the prohibition on invalid combinations of month and day in 13.31.3.
 | ||
|         // https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar-static-semantics-early-errors
 | ||
|         // 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;
 | ||
| 
 | ||
|         // Note the prohibition on invalid combinations of month and day in 13.31.3.
 | ||
|         // https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar-static-semantics-early-errors
 | ||
|         // 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;
 | ||
|         }
 | ||
| 
 | ||
|         // Note the prohibition on negative zero in 13.31.3.
 | ||
|         // https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar-static-semantics-early-errors
 | ||
|         // 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<StringView> key;
 | ||
|         Optional<StringView> 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<TimeZoneOffset>& 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<StringView>& 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 :::
 | ||
|         //     <SP>
 | ||
|         //     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<typename Parser, typename T>
 | ||
|     [[nodiscard]] bool scoped_parse(Optional<T>& storage, Parser&& parser)
 | ||
|     {
 | ||
|         StateTransaction transaction { *this };
 | ||
| 
 | ||
|         if (!parser())
 | ||
|             return false;
 | ||
| 
 | ||
|         if constexpr (IsSame<T, char>)
 | ||
|             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(AmbiguousTemporalTimeString, parse_ambiguous_temporal_time_string)  \
 | ||
|     __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<ParseResult> 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<TimeZoneOffset> parse_utc_offset(StringView input, SubMinutePrecision sub_minute_precision)
 | ||
| {
 | ||
|     ISO8601Parser parser { input };
 | ||
| 
 | ||
|     Optional<TimeZoneOffset> 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;
 | ||
| }
 | ||
| 
 | ||
| }
 | 
