mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 07:52:45 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			931 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			931 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #include <AK/CharacterTypes.h>
 | |
| #include <LibJS/Runtime/Temporal/ISO8601.h>
 | |
| 
 | |
| namespace JS::Temporal {
 | |
| 
 | |
| namespace Detail {
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-DecimalDigit
 | |
| bool ISO8601Parser::parse_decimal_digit()
 | |
| {
 | |
|     // DecimalDigit : one of
 | |
|     //     0 1 2 3 4 5 6 7 8 9
 | |
|     if (m_state.lexer.next_is(is_ascii_digit)) {
 | |
|         m_state.lexer.consume();
 | |
|         return true;
 | |
|     }
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-NonZeroDigit
 | |
| bool ISO8601Parser::parse_non_zero_digit()
 | |
| {
 | |
|     // NonZeroDigit : one of
 | |
|     //     1 2 3 4 5 6 7 8 9
 | |
|     if (m_state.lexer.next_is(is_ascii_digit) && !m_state.lexer.next_is('0')) {
 | |
|         m_state.lexer.consume();
 | |
|         return true;
 | |
|     }
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-ASCIISign
 | |
| bool ISO8601Parser::parse_ascii_sign()
 | |
| {
 | |
|     // ASCIISign : one of
 | |
|     //     + -
 | |
|     return m_state.lexer.consume_specific('+')
 | |
|         || m_state.lexer.consume_specific('-');
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-Sign
 | |
| bool ISO8601Parser::parse_sign()
 | |
| {
 | |
|     // Sign :
 | |
|     //     ASCIISign
 | |
|     //     U+2212
 | |
|     StateTransaction transaction { *this };
 | |
|     auto success = parse_ascii_sign()
 | |
|         || m_state.lexer.consume_specific("\xE2\x88\x92"sv);
 | |
|     if (!success)
 | |
|         return false;
 | |
|     m_state.parse_result.sign = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-Hour
 | |
| bool ISO8601Parser::parse_hour()
 | |
| {
 | |
|     // Hour :
 | |
|     //     0 DecimalDigit
 | |
|     //     1 DecimalDigit
 | |
|     //     20
 | |
|     //     21
 | |
|     //     22
 | |
|     //     23
 | |
|     StateTransaction transaction { *this };
 | |
|     if (m_state.lexer.consume_specific('0') || m_state.lexer.consume_specific('1')) {
 | |
|         if (!parse_decimal_digit())
 | |
|             return false;
 | |
|     } else {
 | |
|         auto success = m_state.lexer.consume_specific("20"sv)
 | |
|             || m_state.lexer.consume_specific("21"sv)
 | |
|             || m_state.lexer.consume_specific("22"sv)
 | |
|             || m_state.lexer.consume_specific("23"sv);
 | |
|         if (!success)
 | |
|             return false;
 | |
|     }
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-MinuteSecond
 | |
| bool ISO8601Parser::parse_minute_second()
 | |
| {
 | |
|     // MinuteSecond :
 | |
|     //     0 DecimalDigit
 | |
|     //     1 DecimalDigit
 | |
|     //     2 DecimalDigit
 | |
|     //     3 DecimalDigit
 | |
|     //     4 DecimalDigit
 | |
|     //     5 DecimalDigit
 | |
|     StateTransaction transaction { *this };
 | |
|     auto success = m_state.lexer.consume_specific('0')
 | |
|         || m_state.lexer.consume_specific('1')
 | |
|         || m_state.lexer.consume_specific('2')
 | |
|         || m_state.lexer.consume_specific('3')
 | |
|         || m_state.lexer.consume_specific('4')
 | |
|         || m_state.lexer.consume_specific('5');
 | |
|     if (!success)
 | |
|         return false;
 | |
|     if (!parse_decimal_digit())
 | |
|         return false;
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-DecimalSeparator
 | |
| bool ISO8601Parser::parse_decimal_separator()
 | |
| {
 | |
|     // DecimalSeparator : one of
 | |
|     //     . ,
 | |
|     return m_state.lexer.consume_specific('.')
 | |
|         || m_state.lexer.consume_specific(',');
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-DateTimeSeparator
 | |
| bool ISO8601Parser::parse_date_time_separator()
 | |
| {
 | |
|     // DateTimeSeparator :
 | |
|     //     <SP>
 | |
|     //     T
 | |
|     //     t
 | |
|     return m_state.lexer.consume_specific(' ')
 | |
|         || m_state.lexer.consume_specific('T')
 | |
|         || m_state.lexer.consume_specific('t');
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-UTCDesignator
 | |
| bool ISO8601Parser::parse_utc_designator()
 | |
| {
 | |
|     // UTCDesignator : one of
 | |
|     //     Z z
 | |
|     StateTransaction transaction { *this };
 | |
|     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-DateYear
 | |
| bool ISO8601Parser::parse_date_year()
 | |
| {
 | |
|     // DateFourDigitYear :
 | |
|     //     DecimalDigit DecimalDigit DecimalDigit DecimalDigit
 | |
|     // DateExtendedYear :
 | |
|     //     Sign DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit
 | |
|     // DateYear :
 | |
|     //     DateFourDigitYear
 | |
|     //     DateExtendedYear
 | |
|     StateTransaction transaction { *this };
 | |
|     if (parse_sign()) {
 | |
|         for (size_t i = 0; i < 6; ++i) {
 | |
|             if (!parse_decimal_digit())
 | |
|                 return false;
 | |
|         }
 | |
|     } else {
 | |
|         for (size_t i = 0; i < 4; ++i) {
 | |
|             if (!parse_decimal_digit())
 | |
|                 return false;
 | |
|         }
 | |
|     }
 | |
|     m_state.parse_result.date_year = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-DateMonth
 | |
| bool ISO8601Parser::parse_date_month()
 | |
| {
 | |
|     // DateMonth :
 | |
|     //     0 NonZeroDigit
 | |
|     //     10
 | |
|     //     11
 | |
|     //     12
 | |
|     StateTransaction transaction { *this };
 | |
|     if (m_state.lexer.consume_specific('0')) {
 | |
|         if (!parse_non_zero_digit())
 | |
|             return false;
 | |
|     } else {
 | |
|         auto success = m_state.lexer.consume_specific("10"sv)
 | |
|             || m_state.lexer.consume_specific("11"sv)
 | |
|             || m_state.lexer.consume_specific("12"sv);
 | |
|         if (!success)
 | |
|             return false;
 | |
|     }
 | |
|     m_state.parse_result.date_month = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-DateDay
 | |
| bool ISO8601Parser::parse_date_day()
 | |
| {
 | |
|     // DateDay :
 | |
|     //     0 NonZeroDigit
 | |
|     //     1 DecimalDigit
 | |
|     //     2 DecimalDigit
 | |
|     //     30
 | |
|     //     31
 | |
|     StateTransaction transaction { *this };
 | |
|     if (m_state.lexer.consume_specific('0')) {
 | |
|         if (!parse_non_zero_digit())
 | |
|             return false;
 | |
|     } else if (m_state.lexer.consume_specific('1') || m_state.lexer.consume_specific('2')) {
 | |
|         if (!parse_decimal_digit())
 | |
|             return false;
 | |
|     } else {
 | |
|         auto success = m_state.lexer.consume_specific("30"sv)
 | |
|             || m_state.lexer.consume_specific("31"sv);
 | |
|         if (!success)
 | |
|             return false;
 | |
|     }
 | |
|     m_state.parse_result.date_day = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-DateSpecYearMonth
 | |
| bool ISO8601Parser::parse_date_spec_year_month()
 | |
| {
 | |
|     // DateSpecYearMonth :
 | |
|     //     DateYear -[opt] DateMonth
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_date_year())
 | |
|         return false;
 | |
|     m_state.lexer.consume_specific('-');
 | |
|     if (!parse_date_month())
 | |
|         return false;
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-DateSpecMonthDay
 | |
| bool ISO8601Parser::parse_date_spec_month_day()
 | |
| {
 | |
|     // TwoDashes :
 | |
|     //     --
 | |
|     // DateSpecMonthDay :
 | |
|     //     TwoDashes[opt] DateMonth -[opt] DateDay
 | |
|     StateTransaction transaction { *this };
 | |
|     m_state.lexer.consume_specific("--"sv);
 | |
|     if (!parse_date_month())
 | |
|         return false;
 | |
|     m_state.lexer.consume_specific('-');
 | |
|     if (!parse_date_day())
 | |
|         return false;
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-Date
 | |
| bool ISO8601Parser::parse_date()
 | |
| {
 | |
|     // Date :
 | |
|     //     DateYear - DateMonth - DateDay
 | |
|     //     DateYear DateMonth DateDay
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_date_year())
 | |
|         return false;
 | |
|     auto with_dashes = m_state.lexer.consume_specific('-');
 | |
|     if (!parse_date_month())
 | |
|         return false;
 | |
|     if (with_dashes && !m_state.lexer.consume_specific('-'))
 | |
|         return false;
 | |
|     if (!parse_date_day())
 | |
|         return false;
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeHour
 | |
| bool ISO8601Parser::parse_time_hour()
 | |
| {
 | |
|     // TimeHour :
 | |
|     //     Hour
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_hour())
 | |
|         return false;
 | |
|     m_state.parse_result.time_hour = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeMinute
 | |
| bool ISO8601Parser::parse_time_minute()
 | |
| {
 | |
|     // TimeMinute :
 | |
|     //     MinuteSecond
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_minute_second())
 | |
|         return false;
 | |
|     m_state.parse_result.time_minute = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeSecond
 | |
| bool ISO8601Parser::parse_time_second()
 | |
| {
 | |
|     // TimeSecond :
 | |
|     //     MinuteSecond
 | |
|     //     60
 | |
|     StateTransaction transaction { *this };
 | |
|     auto success = parse_minute_second()
 | |
|         || m_state.lexer.consume_specific("60"sv);
 | |
|     if (!success)
 | |
|         return false;
 | |
|     m_state.parse_result.time_second = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-FractionalPart
 | |
| bool ISO8601Parser::parse_fractional_part()
 | |
| {
 | |
|     // FractionalPart :
 | |
|     //     DecimalDigit DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt]
 | |
|     if (!parse_decimal_digit())
 | |
|         return false;
 | |
|     for (size_t i = 0; i < 8; ++i) {
 | |
|         if (!parse_decimal_digit())
 | |
|             break;
 | |
|     }
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeFractionalPart
 | |
| bool ISO8601Parser::parse_time_fractional_part()
 | |
| {
 | |
|     // TimeFractionalPart :
 | |
|     //     FractionalPart
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_fractional_part())
 | |
|         return false;
 | |
|     m_state.parse_result.time_fractional_part = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-Fraction
 | |
| bool ISO8601Parser::parse_fraction()
 | |
| {
 | |
|     // Fraction :
 | |
|     //     DecimalSeparator TimeFractionalPart
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_decimal_separator())
 | |
|         return false;
 | |
|     if (!parse_time_fractional_part())
 | |
|         return false;
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeFraction
 | |
| bool ISO8601Parser::parse_time_fraction()
 | |
| {
 | |
|     // TimeFraction :
 | |
|     //     Fraction
 | |
|     return parse_fraction();
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetSign
 | |
| bool ISO8601Parser::parse_time_zone_utc_offset_sign()
 | |
| {
 | |
|     // TimeZoneUTCOffsetSign :
 | |
|     //     Sign
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_sign())
 | |
|         return false;
 | |
|     m_state.parse_result.time_zone_utc_offset_sign = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetHour
 | |
| bool ISO8601Parser::parse_time_zone_utc_offset_hour()
 | |
| {
 | |
|     // TimeZoneUTCOffsetHour :
 | |
|     //     Hour
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_hour())
 | |
|         return false;
 | |
|     m_state.parse_result.time_zone_utc_offset_hour = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetMinute
 | |
| bool ISO8601Parser::parse_time_zone_utc_offset_minute()
 | |
| {
 | |
|     // TimeZoneUTCOffsetMinute :
 | |
|     //     MinuteSecond
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_minute_second())
 | |
|         return false;
 | |
|     m_state.parse_result.time_zone_utc_offset_minute = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetSecond
 | |
| bool ISO8601Parser::parse_time_zone_utc_offset_second()
 | |
| {
 | |
|     // TimeZoneUTCOffsetSecond :
 | |
|     //     MinuteSecond
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_minute_second())
 | |
|         return false;
 | |
|     m_state.parse_result.time_zone_utc_offset_second = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetFractionalPart
 | |
| bool ISO8601Parser::parse_time_zone_utc_offset_fractional_part()
 | |
| {
 | |
|     // TimeZoneUTCOffsetFractionalPart :
 | |
|     //     FractionalPart
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_fractional_part())
 | |
|         return false;
 | |
|     m_state.parse_result.time_zone_utc_offset_fractional_part = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetFraction
 | |
| bool ISO8601Parser::parse_time_zone_utc_offset_fraction()
 | |
| {
 | |
|     // TimeZoneUTCOffsetFraction :
 | |
|     //     DecimalSeparator TimeZoneUTCOffsetFractionalPart
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_decimal_separator())
 | |
|         return false;
 | |
|     if (!parse_time_zone_utc_offset_fractional_part())
 | |
|         return false;
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneNumericUTCOffset
 | |
| bool ISO8601Parser::parse_time_zone_numeric_utc_offset()
 | |
| {
 | |
|     // TimeZoneNumericUTCOffset :
 | |
|     //     TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour
 | |
|     //     TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour : TimeZoneUTCOffsetMinute
 | |
|     //     TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour TimeZoneUTCOffsetMinute
 | |
|     //     TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour : TimeZoneUTCOffsetMinute : TimeZoneUTCOffsetSecond TimeZoneUTCOffsetFraction[opt]
 | |
|     //     TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour TimeZoneUTCOffsetMinute TimeZoneUTCOffsetSecond TimeZoneUTCOffsetFraction[opt]
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_time_zone_utc_offset_sign())
 | |
|         return false;
 | |
|     if (!parse_time_zone_utc_offset_hour())
 | |
|         return false;
 | |
|     if (m_state.lexer.consume_specific(':')) {
 | |
|         if (!parse_time_zone_utc_offset_minute())
 | |
|             return false;
 | |
|         if (m_state.lexer.consume_specific(':')) {
 | |
|             if (!parse_time_zone_utc_offset_second())
 | |
|                 return false;
 | |
|             (void)parse_time_zone_utc_offset_fraction();
 | |
|         }
 | |
|     } else if (parse_time_zone_utc_offset_minute()) {
 | |
|         if (parse_time_zone_utc_offset_second())
 | |
|             (void)parse_time_zone_utc_offset_fraction();
 | |
|     }
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffset
 | |
| bool ISO8601Parser::parse_time_zone_utc_offset()
 | |
| {
 | |
|     // TimeZoneUTCOffset :
 | |
|     //     TimeZoneNumericUTCOffset
 | |
|     //     UTCDesignator
 | |
|     return parse_time_zone_numeric_utc_offset()
 | |
|         || parse_utc_designator();
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetName
 | |
| bool ISO8601Parser::parse_time_zone_utc_offset_name()
 | |
| {
 | |
|     // TimeZoneUTCOffsetName :
 | |
|     //     Sign Hour
 | |
|     //     Sign Hour : MinuteSecond
 | |
|     //     Sign Hour MinuteSecond
 | |
|     //     Sign Hour : MinuteSecond : MinuteSecond Fraction[opt]
 | |
|     //     Sign Hour MinuteSecond MinuteSecond Fraction[opt]
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_sign())
 | |
|         return false;
 | |
|     if (!parse_hour())
 | |
|         return false;
 | |
|     if (m_state.lexer.consume_specific(':')) {
 | |
|         if (!parse_minute_second())
 | |
|             return false;
 | |
|         if (m_state.lexer.consume_specific(':')) {
 | |
|             if (!parse_minute_second())
 | |
|                 return false;
 | |
|             (void)parse_fraction();
 | |
|         }
 | |
|     } else if (parse_minute_second()) {
 | |
|         if (parse_minute_second())
 | |
|             (void)parse_fraction();
 | |
|     }
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneIANAName
 | |
| bool ISO8601Parser::parse_time_zone_iana_name()
 | |
| {
 | |
|     // TZLeadingChar :
 | |
|     //     Alpha
 | |
|     //     .
 | |
|     //     _
 | |
|     // TZChar :
 | |
|     //     Alpha
 | |
|     //     .
 | |
|     //     -
 | |
|     //     _
 | |
|     // TimeZoneIANANameComponent :
 | |
|     //     TZLeadingChar TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] but not one of . or ..
 | |
|     // TimeZoneIANANameTail :
 | |
|     //     TimeZoneIANANameComponent
 | |
|     //     TimeZoneIANANameComponent / TimeZoneIANANameTail
 | |
|     // TimeZoneIANAName :
 | |
|     //     TimeZoneIANANameTail
 | |
|     StateTransaction transaction { *this };
 | |
|     // TODO: Implement the full production. Currently, anything other than "UTC" would get rejected as unknown anyway.
 | |
|     auto success = (m_state.lexer.consume_specific('U') || m_state.lexer.consume_specific('u'))
 | |
|         && (m_state.lexer.consume_specific('T') || m_state.lexer.consume_specific('t'))
 | |
|         && (m_state.lexer.consume_specific('C') || m_state.lexer.consume_specific('c'));
 | |
|     if (!success)
 | |
|         return false;
 | |
|     m_state.parse_result.time_zone_iana_name = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneBracketedName
 | |
| bool ISO8601Parser::parse_time_zone_bracketed_name()
 | |
| {
 | |
|     // TimeZoneBracketedName :
 | |
|     //     TimeZoneIANAName
 | |
|     //     Etc/GMT ASCIISign Hour
 | |
|     //     TimeZoneUTCOffsetName
 | |
|     StateTransaction transaction { *this };
 | |
|     if (parse_time_zone_iana_name()) {
 | |
|         // no-op.
 | |
|     } else if (m_state.lexer.consume_specific("Etc/GMT"sv)) {
 | |
|         if (!parse_ascii_sign())
 | |
|             return false;
 | |
|         if (!parse_hour())
 | |
|             return false;
 | |
|     } else if (!parse_time_zone_utc_offset_name()) {
 | |
|         return false;
 | |
|     }
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneBracketedAnnotation
 | |
| bool ISO8601Parser::parse_time_zone_bracketed_annotation()
 | |
| {
 | |
|     // TimeZoneBracketedAnnotation :
 | |
|     //     [ TimeZoneBracketedName ]
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!m_state.lexer.consume_specific('['))
 | |
|         return false;
 | |
|     if (!parse_time_zone_bracketed_name())
 | |
|         return false;
 | |
|     if (!m_state.lexer.consume_specific(']'))
 | |
|         return false;
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneOffsetRequired
 | |
| bool ISO8601Parser::parse_time_zone_offset_required()
 | |
| {
 | |
|     // TimeZoneOffsetRequired :
 | |
|     //     TimeZoneUTCOffset TimeZoneBracketedAnnotation[opt]
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_time_zone_utc_offset())
 | |
|         return false;
 | |
|     (void)parse_time_zone_bracketed_annotation();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZoneNameRequired
 | |
| bool ISO8601Parser::parse_time_zone_name_required()
 | |
| {
 | |
|     // TimeZoneNameRequired :
 | |
|     //     TimeZoneUTCOffset[opt] TimeZoneBracketedAnnotation
 | |
|     StateTransaction transaction { *this };
 | |
|     (void)parse_time_zone_utc_offset();
 | |
|     if (!parse_time_zone_bracketed_annotation())
 | |
|         return false;
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeZone
 | |
| bool ISO8601Parser::parse_time_zone()
 | |
| {
 | |
|     // TimeZone :
 | |
|     //     TimeZoneOffsetRequired
 | |
|     //     TimeZoneNameRequired
 | |
|     return parse_time_zone_offset_required()
 | |
|         || parse_time_zone_name_required();
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-CalendarName
 | |
| bool ISO8601Parser::parse_calendar_name()
 | |
| {
 | |
|     // CalChar :
 | |
|     //     Alpha
 | |
|     //     DecimalDigit
 | |
|     // CalendarNameComponent :
 | |
|     //     CalChar CalChar CalChar CalChar[opt] CalChar[opt] CalChar[opt] CalChar[opt] CalChar[opt]
 | |
|     // CalendarNameTail :
 | |
|     //     CalendarNameComponent
 | |
|     //     CalendarNameComponent - CalendarNameTail
 | |
|     // CalendarName :
 | |
|     //     CalendarNameTail
 | |
|     auto parse_calendar_name_component = [&] {
 | |
|         for (size_t i = 0; i < 8; ++i) {
 | |
|             if (!m_state.lexer.next_is(is_ascii_alphanumeric))
 | |
|                 return i > 2;
 | |
|             m_state.lexer.consume();
 | |
|         }
 | |
|         return true;
 | |
|     };
 | |
|     StateTransaction transaction { *this };
 | |
|     do {
 | |
|         if (!parse_calendar_name_component())
 | |
|             return false;
 | |
|     } while (m_state.lexer.consume_specific('-'));
 | |
|     m_state.parse_result.calendar_name = transaction.parsed_string_view();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-Calendar
 | |
| bool ISO8601Parser::parse_calendar()
 | |
| {
 | |
|     // Calendar :
 | |
|     //     [u-ca= CalendarName ]
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!m_state.lexer.consume_specific("[u-ca="sv))
 | |
|         return false;
 | |
|     if (!parse_calendar_name())
 | |
|         return false;
 | |
|     if (!m_state.lexer.consume_specific(']'))
 | |
|         return false;
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeSpec
 | |
| bool ISO8601Parser::parse_time_spec()
 | |
| {
 | |
|     // TimeSpec :
 | |
|     //     TimeHour
 | |
|     //     TimeHour : TimeMinute
 | |
|     //     TimeHour TimeMinute
 | |
|     //     TimeHour : TimeMinute : TimeSecond TimeFraction[opt]
 | |
|     //     TimeHour TimeMinute TimeSecond TimeFraction[opt]
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_time_hour())
 | |
|         return false;
 | |
|     if (m_state.lexer.consume_specific(':')) {
 | |
|         if (!parse_time_minute())
 | |
|             return false;
 | |
|         if (m_state.lexer.consume_specific(':')) {
 | |
|             if (!parse_time_second())
 | |
|                 return false;
 | |
|             (void)parse_time_fraction();
 | |
|         }
 | |
|     } else if (parse_time_minute()) {
 | |
|         if (parse_time_second())
 | |
|             (void)parse_time_fraction();
 | |
|     }
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-Time
 | |
| bool ISO8601Parser::parse_time()
 | |
| {
 | |
|     // Time :
 | |
|     //     TimeSpec TimeZone[opt]
 | |
|     if (!parse_time_spec())
 | |
|         return false;
 | |
|     (void)parse_time_zone();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TimeSpecSeparator
 | |
| bool ISO8601Parser::parse_time_spec_separator()
 | |
| {
 | |
|     // TimeSpecSeparator :
 | |
|     //     DateTimeSeparator TimeSpec
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_date_time_separator())
 | |
|         return false;
 | |
|     if (!parse_time_spec())
 | |
|         return false;
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-DateTime
 | |
| bool ISO8601Parser::parse_date_time()
 | |
| {
 | |
|     // DateTime :
 | |
|     //     Date TimeSpecSeparator[opt] TimeZone[opt]
 | |
|     if (!parse_date())
 | |
|         return false;
 | |
|     (void)parse_time_spec_separator();
 | |
|     (void)parse_time_zone();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-CalendarDateTime
 | |
| bool ISO8601Parser::parse_calendar_date_time()
 | |
| {
 | |
|     // CalendarDateTime :
 | |
|     //     DateTime Calendar[opt]
 | |
|     if (!parse_date_time())
 | |
|         return false;
 | |
|     (void)parse_calendar();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TemporalInstantString
 | |
| bool ISO8601Parser::parse_temporal_instant_string()
 | |
| {
 | |
|     // TemporalInstantString :
 | |
|     //     Date TimeZoneOffsetRequired
 | |
|     //     Date DateTimeSeparator TimeSpec TimeZoneOffsetRequired
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_date())
 | |
|         return false;
 | |
|     if (!parse_time_zone_offset_required()) {
 | |
|         if (!parse_date_time_separator())
 | |
|             return false;
 | |
|         if (!parse_time_spec())
 | |
|             return false;
 | |
|         if (!parse_time_zone_offset_required())
 | |
|             return false;
 | |
|     }
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TemporalDateString
 | |
| bool ISO8601Parser::parse_temporal_date_string()
 | |
| {
 | |
|     // TemporalDateString :
 | |
|     //     CalendarDateTime
 | |
|     return parse_calendar_date_time();
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TemporalDateTimeString
 | |
| bool ISO8601Parser::parse_temporal_date_time_string()
 | |
| {
 | |
|     // TemporalDateTimeString :
 | |
|     //     CalendarDateTime
 | |
|     return parse_calendar_date_time();
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TemporalMonthDayString
 | |
| bool ISO8601Parser::parse_temporal_month_day_string()
 | |
| {
 | |
|     // TemporalMonthDayString :
 | |
|     //     DateSpecMonthDay
 | |
|     //     DateTime
 | |
|     // NOTE: Reverse order here because `DateSpecMonthDay` can be a subset of `DateTime`,
 | |
|     // so we'd not attempt to parse that but may not exhaust the input string.
 | |
|     return parse_date_time()
 | |
|         || parse_date_spec_month_day();
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TemporalTimeString
 | |
| bool ISO8601Parser::parse_temporal_time_string()
 | |
| {
 | |
|     // TemporalTimeString :
 | |
|     //     Time
 | |
|     //     DateTime
 | |
|     // NOTE: Reverse order here because `Time` can be a subset of `DateTime`,
 | |
|     // so we'd not attempt to parse that but may not exhaust the input string.
 | |
|     return parse_date_time()
 | |
|         || parse_time();
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TemporalTimeZoneIdentifier
 | |
| bool ISO8601Parser::parse_temporal_time_zone_identifier()
 | |
| {
 | |
|     // TemporalTimeZoneIdentifier :
 | |
|     //     TimeZoneNumericUTCOffset
 | |
|     //     TimeZoneIANAName
 | |
|     return parse_time_zone_numeric_utc_offset()
 | |
|         || parse_time_zone_iana_name();
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TemporalTimeZoneString
 | |
| bool ISO8601Parser::parse_temporal_time_zone_string()
 | |
| {
 | |
|     // TemporalTimeZoneString :
 | |
|     //     TemporalTimeZoneIdentifier
 | |
|     //     Date TimeSpecSeparator[opt] TimeZone Calendar[opt]
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_temporal_time_zone_identifier()) {
 | |
|         if (!parse_date())
 | |
|             return false;
 | |
|         (void)parse_time_spec_separator();
 | |
|         if (!parse_time_zone())
 | |
|             return false;
 | |
|         (void)parse_calendar();
 | |
|     }
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TemporalYearMonthString
 | |
| bool ISO8601Parser::parse_temporal_year_month_string()
 | |
| {
 | |
|     // TemporalYearMonthString :
 | |
|     //     DateSpecYearMonth
 | |
|     //     DateTime
 | |
|     // NOTE: Reverse order here because `DateSpecYearMonth` can be a subset of `DateTime`,
 | |
|     // so we'd not attempt to parse that but may not exhaust the input string.
 | |
|     return parse_date_time()
 | |
|         || parse_date_spec_year_month();
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TemporalZonedDateTimeString
 | |
| bool ISO8601Parser::parse_temporal_zoned_date_time_string()
 | |
| {
 | |
|     // TemporalZonedDateTimeString :
 | |
|     //     Date TimeSpecSeparator[opt] TimeZoneNameRequired Calendar[opt]
 | |
|     StateTransaction transaction { *this };
 | |
|     if (!parse_date())
 | |
|         return false;
 | |
|     (void)parse_time_spec_separator();
 | |
|     if (!parse_time_zone_name_required())
 | |
|         return false;
 | |
|     (void)parse_calendar();
 | |
|     transaction.commit();
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TemporalCalendarString
 | |
| bool ISO8601Parser::parse_temporal_calendar_string()
 | |
| {
 | |
|     // TemporalCalendarString :
 | |
|     //     CalendarName
 | |
|     //     TemporalInstantString
 | |
|     //     CalendarDateTime
 | |
|     //     Time
 | |
|     //     DateSpecYearMonth
 | |
|     //     DateSpecMonthDay
 | |
|     return parse_calendar_name()
 | |
|         // TODO: || parse_temporal_instant_string()
 | |
|         || parse_calendar_date_time()
 | |
|         || parse_date_spec_year_month()
 | |
|         || parse_date_spec_month_day()
 | |
|         || parse_time();
 | |
| }
 | |
| 
 | |
| // https://tc39.es/proposal-temporal/#prod-TemporalRelativeToString
 | |
| bool ISO8601Parser::parse_temporal_relative_to_string()
 | |
| {
 | |
|     // TemporalRelativeToString :
 | |
|     //     TemporalDateTimeString
 | |
|     //     TemporalZonedDateTimeString
 | |
|     return parse_temporal_date_time_string()
 | |
|         || parse_temporal_zoned_date_time_string();
 | |
| }
 | |
| 
 | |
| }
 | |
| 
 | |
| #define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS                                        \
 | |
|     __JS_ENUMERATE(TemporalInstantString, parse_temporal_instant_string)               \
 | |
|     __JS_ENUMERATE(TemporalDateString, parse_temporal_date_string)                     \
 | |
|     __JS_ENUMERATE(TemporalDateTimeString, parse_temporal_date_time_string)            \
 | |
|     __JS_ENUMERATE(TemporalMonthDayString, parse_temporal_month_day_string)            \
 | |
|     __JS_ENUMERATE(TemporalTimeString, parse_temporal_time_string)                     \
 | |
|     __JS_ENUMERATE(TemporalTimeZoneString, parse_temporal_time_zone_string)            \
 | |
|     __JS_ENUMERATE(TemporalYearMonthString, parse_temporal_year_month_string)          \
 | |
|     __JS_ENUMERATE(TemporalZonedDateTimeString, parse_temporal_zoned_date_time_string) \
 | |
|     __JS_ENUMERATE(TemporalCalendarString, parse_temporal_calendar_string)             \
 | |
|     __JS_ENUMERATE(TemporalRelativeToString, parse_temporal_relative_to_string)
 | |
| 
 | |
| Optional<ParseResult> parse_iso8601(Production production, StringView input)
 | |
| {
 | |
|     auto parser = Detail::ISO8601Parser { input };
 | |
| 
 | |
|     switch (production) {
 | |
| #define __JS_ENUMERATE(ProductionName, parse_production) \
 | |
|     case Production::ProductionName:                     \
 | |
|         if (!parser.parse_production())                  \
 | |
|             return {};                                   \
 | |
|         break;
 | |
|         JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS
 | |
| #undef __JS_ENUMERATE
 | |
|     default:
 | |
|         VERIFY_NOT_REACHED();
 | |
|     }
 | |
| 
 | |
|     // If we parsed successfully but didn't reach the end, the string doesn't match the given production.
 | |
|     if (!parser.lexer().is_eof())
 | |
|         return {};
 | |
| 
 | |
|     return parser.parse_result();
 | |
| }
 | |
| }
 | 
