mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
b3b968bed9
This started with implementing TemporalMonthDayString. It turns out that the facilities needed to parse that production includes nearly all the helpers to parse each of: TemporalDateTimeString TemporalInstantString TemporalMonthDayString TemporalTimeString TemporalYearMonthString TemporalZonedDateTimeString As most of these invoke the same helpers with different options. So, all 6 of those productions are implemented here.
1341 lines
44 KiB
C++
1341 lines
44 KiB
C++
/*
|
||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||
* Copyright (c) 2024, 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.30.1 Static Semantics: IsValidMonthDay, https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar-static-semantics-isvalidmonthday
|
||
static bool is_valid_month_day(ParseResult const& result)
|
||
{
|
||
// 1. If DateDay is "31" and DateMonth is "02", "04", "06", "09", "11", return false.
|
||
if (result.date_day == "31"sv && result.date_month->is_one_of("02"sv, "04"sv, "06"sv, "09"sv, "11"sv))
|
||
return false;
|
||
|
||
// 2. If DateMonth is "02" and DateDay is "30", return false.
|
||
if (result.date_month == "02"sv && result.date_day == "30"sv)
|
||
return false;
|
||
|
||
// 3. Return true.
|
||
return true;
|
||
}
|
||
|
||
// 13.30.2 Static Semantics: IsValidDate, https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar-static-semantics-isvaliddate
|
||
static bool is_valid_date(ParseResult const& result)
|
||
{
|
||
// 1. If IsValidMonthDay of DateSpec is false, return false.
|
||
if (!is_valid_month_day(result))
|
||
return false;
|
||
|
||
// 2. Let year be ℝ(StringToNumber(CodePointsToString(DateYear))).
|
||
auto year = string_to_number(*result.date_year);
|
||
|
||
// 3. If DateMonth is "02" and DateDay is "29" and MathematicalInLeapYear(EpochTimeForYear(year)) = 0, return false.
|
||
if (result.date_month == "02"sv && result.date_day == "29"sv && mathematical_in_leap_year(epoch_time_for_year(year)) == 0)
|
||
return false;
|
||
|
||
// 4. Return true.
|
||
return true;
|
||
}
|
||
|
||
// 13.30 ISO 8601 grammar, https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar
|
||
class ISO8601Parser {
|
||
public:
|
||
explicit ISO8601Parser(StringView input)
|
||
: m_input(input)
|
||
, m_state({
|
||
.lexer = GenericLexer { input },
|
||
.parse_result = {},
|
||
})
|
||
{
|
||
}
|
||
|
||
[[nodiscard]] GenericLexer const& lexer() const { return m_state.lexer; }
|
||
[[nodiscard]] ParseResult const& parse_result() const { return m_state.parse_result; }
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TemporalDateTimeString
|
||
[[nodiscard]] bool parse_temporal_date_time_string()
|
||
{
|
||
// TemporalDateTimeString[Zoned] :::
|
||
// AnnotatedDateTime[?Zoned, ~TimeRequired]
|
||
return parse_annotated_date_time(Zoned::No, TimeRequired::No);
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TemporalDateTimeString
|
||
[[nodiscard]] bool parse_temporal_zoned_date_time_string()
|
||
{
|
||
// TemporalDateTimeString[Zoned] :::
|
||
// AnnotatedDateTime[?Zoned, ~TimeRequired]
|
||
return parse_annotated_date_time(Zoned::Yes, TimeRequired::No);
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TemporalDurationString
|
||
[[nodiscard]] bool parse_temporal_duration_string()
|
||
{
|
||
// TemporalDurationString :::
|
||
// Duration
|
||
return parse_duration();
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TemporalInstantString
|
||
[[nodiscard]] bool parse_temporal_instant_string()
|
||
{
|
||
// TemporalInstantString :::
|
||
// Date DateTimeSeparator Time DateTimeUTCOffset[+Z] TimeZoneAnnotation[opt] Annotations[opt]
|
||
if (!parse_date())
|
||
return false;
|
||
if (!parse_date_time_separator())
|
||
return false;
|
||
if (!parse_time())
|
||
return false;
|
||
if (!parse_date_time_utc_offset(ZDesignator::Yes))
|
||
return false;
|
||
|
||
(void)parse_time_zone_annotation();
|
||
(void)parse_annotations();
|
||
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TemporalMonthDayString
|
||
[[nodiscard]] bool parse_temporal_month_day_string()
|
||
{
|
||
// TemporalMonthDayString :::
|
||
// AnnotatedMonthDay
|
||
// AnnotatedDateTime[~Zoned, ~TimeRequired]
|
||
// NOTE: Reverse order here because `AnnotatedMonthDay` can be a subset of `AnnotatedDateTime`.
|
||
return parse_annotated_date_time(Zoned::No, TimeRequired::No) || parse_annotated_month_day();
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TemporalTimeString
|
||
[[nodiscard]] bool parse_temporal_time_string()
|
||
{
|
||
// TemporalTimeString :::
|
||
// AnnotatedTime
|
||
// AnnotatedDateTime[~Zoned, +TimeRequired]
|
||
// NOTE: Reverse order here because `AnnotatedTime` can be a subset of `AnnotatedDateTime`.
|
||
return parse_annotated_date_time(Zoned::No, TimeRequired::Yes) || parse_annotated_time();
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TemporalYearMonthString
|
||
[[nodiscard]] bool parse_temporal_year_month_string()
|
||
{
|
||
// TemporalYearMonthString :::
|
||
// AnnotatedYearMonth
|
||
// AnnotatedDateTime[~Zoned, ~TimeRequired]
|
||
// NOTE: Reverse order here because `AnnotatedYearMonth` can be a subset of `AnnotatedDateTime`.
|
||
return parse_annotated_date_time(Zoned::No, TimeRequired::No) || parse_annotated_year_month();
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-AnnotatedDateTime
|
||
[[nodiscard]] bool parse_annotated_date_time(Zoned zoned, TimeRequired time_required)
|
||
{
|
||
// AnnotatedDateTime[Zoned, TimeRequired] :::
|
||
// [~Zoned] DateTime[~Z, ?TimeRequired] TimeZoneAnnotation[opt] Annotations[opt]
|
||
// [+Zoned] DateTime[+Z, ?TimeRequired] TimeZoneAnnotation Annotations[opt]
|
||
if (!parse_date_time(zoned == Zoned::Yes ? ZDesignator::Yes : ZDesignator::No, time_required))
|
||
return false;
|
||
|
||
if (!parse_time_zone_annotation()) {
|
||
if (zoned == Zoned::Yes)
|
||
return false;
|
||
}
|
||
|
||
(void)parse_annotations();
|
||
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-AnnotatedMonthDay
|
||
[[nodiscard]] bool parse_annotated_month_day()
|
||
{
|
||
// AnnotatedMonthDay :::
|
||
// DateSpecMonthDay TimeZoneAnnotation[opt] Annotations[opt]
|
||
if (!parse_date_spec_month_day())
|
||
return false;
|
||
|
||
(void)parse_time_zone_annotation();
|
||
(void)parse_annotations();
|
||
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-AnnotatedTime
|
||
[[nodiscard]] bool parse_annotated_time()
|
||
{
|
||
// AnnotatedTime :::
|
||
// TimeDesignator Time DateTimeUTCOffset[~Z][opt] TimeZoneAnnotation[opt] Annotations[opt]
|
||
// Time DateTimeUTCOffset[~Z][opt] TimeZoneAnnotation[opt] Annotations[opt]
|
||
(void)parse_time_designator();
|
||
|
||
if (!parse_time())
|
||
return false;
|
||
|
||
(void)parse_date_time_utc_offset(ZDesignator::No);
|
||
(void)parse_time_zone_annotation();
|
||
(void)parse_annotations();
|
||
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-AnnotatedYearMonth
|
||
[[nodiscard]] bool parse_annotated_year_month()
|
||
{
|
||
// AnnotatedYearMonth :::
|
||
// DateSpecYearMonth TimeZoneAnnotation[opt] Annotations[opt]
|
||
if (!parse_date_spec_year_month())
|
||
return false;
|
||
|
||
(void)parse_time_zone_annotation();
|
||
(void)parse_annotations();
|
||
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-DateTime
|
||
[[nodiscard]] bool parse_date_time(ZDesignator z_designator, TimeRequired time_required)
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
// DateTime[Z, TimeRequired] :::
|
||
// [~TimeRequired] Date
|
||
// Date DateTimeSeparator Time DateTimeUTCOffset[?Z][opt]
|
||
if (!parse_date())
|
||
return false;
|
||
|
||
if (parse_date_time_separator()) {
|
||
if (!parse_time())
|
||
return false;
|
||
|
||
(void)parse_date_time_utc_offset(z_designator);
|
||
} else if (time_required == TimeRequired::Yes) {
|
||
return false;
|
||
}
|
||
|
||
transaction.commit();
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-Date
|
||
[[nodiscard]] bool parse_date()
|
||
{
|
||
// Date :::
|
||
// DateSpec[+Extended]
|
||
// DateSpec[~Extended]
|
||
return parse_date_spec(Extended::Yes) || parse_date_spec(Extended::No);
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-DateSpec
|
||
[[nodiscard]] bool parse_date_spec(Extended extended)
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
// DateSpec[Extended] :::
|
||
// DateYear DateSeparator[?Extended] DateMonth DateSeparator[?Extended] DateDay
|
||
if (!parse_date_year())
|
||
return false;
|
||
if (!parse_date_separator(extended))
|
||
return false;
|
||
if (!parse_date_month())
|
||
return false;
|
||
if (!parse_date_separator(extended))
|
||
return false;
|
||
if (!parse_date_day())
|
||
return false;
|
||
|
||
// It is a Syntax Error if IsValidDate of DateSpec is false.
|
||
if (!is_valid_date(m_state.parse_result))
|
||
return false;
|
||
|
||
transaction.commit();
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-DateSpecMonthDay
|
||
[[nodiscard]] bool parse_date_spec_month_day()
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
// DateSpecMonthDay :::
|
||
// --[opt] DateMonth DateSeparator[+Extended] DateDay
|
||
// --[opt] DateMonth DateSeparator[~Extended] DateDay
|
||
(void)m_state.lexer.consume_specific("--"sv);
|
||
|
||
if (!parse_date_month())
|
||
return false;
|
||
if (!parse_date_separator(Extended::Yes) && !parse_date_separator(Extended::No))
|
||
return false;
|
||
if (!parse_date_day())
|
||
return false;
|
||
|
||
// It is a Syntax Error if IsValidMonthDay of DateSpecMonthDay is false.
|
||
if (!is_valid_month_day(m_state.parse_result))
|
||
return false;
|
||
|
||
transaction.commit();
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-DateSpecYearMonth
|
||
[[nodiscard]] bool parse_date_spec_year_month()
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
// DateSpecYearMonth :::
|
||
// DateYear DateSeparator[+Extended] DateMonth
|
||
// DateYear DateSeparator[~Extended] DateMonth
|
||
if (!parse_date_year())
|
||
return false;
|
||
if (!parse_date_separator(Extended::Yes) && !parse_date_separator(Extended::No))
|
||
return false;
|
||
if (!parse_date_month())
|
||
return false;
|
||
|
||
transaction.commit();
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-DateYear
|
||
[[nodiscard]] bool parse_date_year()
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
// DateYear :::
|
||
// DecimalDigit DecimalDigit DecimalDigit DecimalDigit
|
||
// ASCIISign DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit
|
||
size_t digit_count = parse_ascii_sign() ? 6 : 4;
|
||
|
||
for (size_t i = 0; i < digit_count; ++i) {
|
||
if (!parse_decimal_digit())
|
||
return false;
|
||
}
|
||
|
||
// It is a Syntax Error if DateYear is "-000000".
|
||
if (transaction.parsed_string_view() == "-000000"sv)
|
||
return false;
|
||
|
||
m_state.parse_result.date_year = transaction.parsed_string_view();
|
||
transaction.commit();
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-DateMonth
|
||
[[nodiscard]] bool parse_date_month()
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
// DateMonth :::
|
||
// 0 NonZeroDigit
|
||
// 10
|
||
// 11
|
||
// 12
|
||
if (m_state.lexer.consume_specific('0')) {
|
||
if (!parse_non_zero_digit())
|
||
return false;
|
||
} else {
|
||
auto success = m_state.lexer.consume_specific("10"sv) || m_state.lexer.consume_specific("11"sv) || m_state.lexer.consume_specific("12"sv);
|
||
if (!success)
|
||
return false;
|
||
}
|
||
|
||
m_state.parse_result.date_month = transaction.parsed_string_view();
|
||
transaction.commit();
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-DateDay
|
||
[[nodiscard]] bool parse_date_day()
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
// DateDay :::
|
||
// 0 NonZeroDigit
|
||
// 1 DecimalDigit
|
||
// 2 DecimalDigit
|
||
// 30
|
||
// 31
|
||
if (m_state.lexer.consume_specific('0')) {
|
||
if (!parse_non_zero_digit())
|
||
return false;
|
||
} else if (m_state.lexer.consume_specific('1') || m_state.lexer.consume_specific('2')) {
|
||
if (!parse_decimal_digit())
|
||
return false;
|
||
} else {
|
||
auto success = m_state.lexer.consume_specific("30"sv) || m_state.lexer.consume_specific("31"sv);
|
||
if (!success)
|
||
return false;
|
||
}
|
||
|
||
m_state.parse_result.date_day = transaction.parsed_string_view();
|
||
transaction.commit();
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-DateTimeUTCOffset
|
||
[[nodiscard]] bool parse_date_time_utc_offset(ZDesignator z_designator)
|
||
{
|
||
// DateTimeUTCOffset[Z] :::
|
||
// [+Z] UTCDesignator
|
||
// UTCOffset[+SubMinutePrecision]
|
||
if (z_designator == ZDesignator::Yes) {
|
||
if (parse_utc_designator())
|
||
return true;
|
||
}
|
||
|
||
return parse_utc_offset(SubMinutePrecision::Yes, m_state.parse_result.date_time_offset);
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-Time
|
||
[[nodiscard]] bool parse_time()
|
||
{
|
||
// Time :::
|
||
// TimeSpec[+Extended]
|
||
// TimeSpec[~Extended]
|
||
return parse_time_spec();
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TimeSpec
|
||
[[nodiscard]] bool parse_time_spec()
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
auto parse_time_hour = [&]() {
|
||
return scoped_parse(m_state.parse_result.time_hour, [&]() { return parse_hour(); });
|
||
};
|
||
auto parse_time_minute = [&]() {
|
||
return scoped_parse(m_state.parse_result.time_minute, [&]() { return parse_minute_second(); });
|
||
};
|
||
auto parse_time_fraction = [&]() {
|
||
return scoped_parse(m_state.parse_result.time_fraction, [&]() { return parse_temporal_decimal_fraction(); });
|
||
};
|
||
|
||
// TimeSpec[Extended] :::
|
||
// Hour
|
||
// Hour TimeSeparator[?Extended] MinuteSecond
|
||
// Hour TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] TimeSecond TemporalDecimalFraction[opt]
|
||
if (!parse_time_hour())
|
||
return false;
|
||
|
||
if (parse_time_separator(Extended::Yes)) {
|
||
if (!parse_time_minute())
|
||
return false;
|
||
|
||
if (parse_time_separator(Extended::Yes)) {
|
||
if (!parse_time_second())
|
||
return false;
|
||
|
||
(void)parse_time_fraction();
|
||
}
|
||
} else if (parse_time_minute() && parse_time_second()) {
|
||
(void)parse_time_fraction();
|
||
}
|
||
|
||
transaction.commit();
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TimeSecond
|
||
[[nodiscard]] bool parse_time_second()
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
// TimeSecond :::
|
||
// MinuteSecond
|
||
// 60
|
||
auto success = parse_minute_second() || m_state.lexer.consume_specific("60"sv);
|
||
if (!success)
|
||
return false;
|
||
|
||
m_state.parse_result.time_second = transaction.parsed_string_view();
|
||
transaction.commit();
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TimeZoneAnnotation
|
||
[[nodiscard]] bool parse_time_zone_annotation()
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
// TimeZoneAnnotation :::
|
||
// [ AnnotationCriticalFlag[opt] TimeZoneIdentifier ]
|
||
if (!m_state.lexer.consume_specific('['))
|
||
return false;
|
||
|
||
(void)parse_annotation_critical_flag();
|
||
if (!parse_time_zone_identifier())
|
||
return false;
|
||
|
||
if (!m_state.lexer.consume_specific(']'))
|
||
return false;
|
||
|
||
transaction.commit();
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TimeZoneIdentifier
|
||
[[nodiscard]] bool parse_time_zone_identifier()
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
// TimeZoneIdentifier :::
|
||
// UTCOffset[~SubMinutePrecision]
|
||
// TimeZoneIANAName
|
||
auto success = parse_utc_offset(SubMinutePrecision::No, m_state.parse_result.time_zone_offset) || parse_time_zone_iana_name();
|
||
if (!success)
|
||
return false;
|
||
|
||
m_state.parse_result.time_zone_identifier = transaction.parsed_string_view();
|
||
transaction.commit();
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TimeZoneIANAName
|
||
[[nodiscard]] bool parse_time_zone_iana_name()
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
// TimeZoneIANAName :::
|
||
// TimeZoneIANANameComponent
|
||
// TimeZoneIANAName / TimeZoneIANANameComponent
|
||
if (!parse_time_zone_iana_name_component())
|
||
return false;
|
||
|
||
while (m_state.lexer.consume_specific('/')) {
|
||
if (!parse_time_zone_iana_name_component())
|
||
return false;
|
||
}
|
||
|
||
m_state.parse_result.time_zone_iana_name = transaction.parsed_string_view();
|
||
transaction.commit();
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TimeZoneIANANameComponent
|
||
[[nodiscard]] bool parse_time_zone_iana_name_component()
|
||
{
|
||
// TimeZoneIANANameComponent :::
|
||
// TZLeadingChar
|
||
// TimeZoneIANANameComponent TZChar
|
||
if (!parse_tz_leading_char())
|
||
return false;
|
||
while (parse_tz_leading_char())
|
||
;
|
||
while (parse_tz_char())
|
||
;
|
||
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TZLeadingChar
|
||
[[nodiscard]] bool parse_tz_leading_char()
|
||
{
|
||
// TZLeadingChar :::
|
||
// Alpha
|
||
// .
|
||
// _
|
||
return parse_alpha() || m_state.lexer.consume_specific('.') || m_state.lexer.consume_specific('_');
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-TZChar
|
||
[[nodiscard]] bool parse_tz_char()
|
||
{
|
||
// TZChar :::
|
||
// TZLeadingChar
|
||
// DecimalDigit
|
||
// -
|
||
// +
|
||
return parse_tz_leading_char() || parse_decimal_digit() || m_state.lexer.consume_specific('.') || m_state.lexer.consume_specific('+');
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-Annotations
|
||
[[nodiscard]] bool parse_annotations()
|
||
{
|
||
// Annotations :::
|
||
// Annotation Annotationsopt
|
||
if (!parse_annotation())
|
||
return false;
|
||
while (parse_annotation())
|
||
;
|
||
return true;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#prod-Annotation
|
||
[[nodiscard]] bool parse_annotation()
|
||
{
|
||
StateTransaction transaction { *this };
|
||
|
||
Optional<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(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;
|
||
}
|
||
|
||
}
|