123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 |
- /*
- * 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/ISO8601.h>
- namespace JS::Temporal {
- // 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; }
- enum class Separator {
- No,
- Yes,
- };
- // https://tc39.es/proposal-temporal/#prod-TemporalDurationString
- [[nodiscard]] bool parse_temporal_duration_string()
- {
- // TemporalDurationString :
- // Duration
- return parse_duration();
- }
- // 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)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 = parse_temporal_decimal_fraction(m_state.parse_result.duration_hours_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 = parse_temporal_decimal_fraction(m_state.parse_result.duration_minutes_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)parse_temporal_decimal_fraction(m_state.parse_result.duration_seconds_fraction);
- if (!parse_seconds_designator())
- return false;
- transaction.commit();
- return true;
- }
- // 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-TemporalDecimalFraction
- [[nodiscard]] bool parse_temporal_decimal_fraction(Optional<StringView>& result)
- {
- StateTransaction transaction { *this };
- // 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;
- }
- result = transaction.parsed_string_view();
- transaction.commit();
- return true;
- }
- // https://tc39.es/ecma262/#prod-ASCIISign
- [[nodiscard]] bool parse_ascii_sign()
- {
- StateTransaction transaction { *this };
- // ASCIISign : one of
- // + -
- if (!m_state.lexer.next_is(is_any_of("+-"sv)))
- return false;
- m_state.parse_result.sign = m_state.lexer.consume();
- transaction.commit();
- return true;
- }
- // 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-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-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');
- }
- private:
- 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(TemporalDurationString, parse_temporal_duration_string)
- 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();
- }
- }
|