ladybird/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp

1358 lines
45 KiB
C++
Raw Normal View History

/*
* 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;
}
}