diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSMathFunctions.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSMathFunctions.cpp index d481a5692ef..b324d278494 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSMathFunctions.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSMathFunctions.cpp @@ -118,7 +118,7 @@ static Optional parse_rounding_strategy(Vector OwnPtr Parser::parse_math_function(PropertyID property_id, Function const& function) { - TokenStream stream { function.values() }; + TokenStream stream { function.value }; auto arguments = parse_a_comma_separated_list_of_component_values(stream); )~~~"); @@ -129,7 +129,7 @@ OwnPtr Parser::parse_math_function(PropertyID property_id, Func auto function_generator = generator.fork(); function_generator.set("name:lowercase", name); function_generator.set("name:titlecase", title_casify(name)); - function_generator.appendln(" if (function.name().equals_ignoring_ascii_case(\"@name:lowercase@\"sv)) {"); + function_generator.appendln(" if (function.name.equals_ignoring_ascii_case(\"@name:lowercase@\"sv)) {"); if (function_data.get_bool("is-variadic"sv).value_or(false)) { // Variadic function function_generator.append(R"~~~( diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/Parser/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/Parser/BUILD.gn index 17511001411..4d861afc388 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/Parser/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/Parser/BUILD.gn @@ -2,19 +2,15 @@ source_set("Parser") { configs += [ "//Userland/Libraries/LibWeb:configs" ] deps = [ "//Userland/Libraries/LibWeb:all_generated" ] sources = [ - "Block.cpp", "ComponentValue.cpp", - "Declaration.cpp", - "DeclarationOrAtRule.cpp", - "Function.cpp", "GradientParsing.cpp", "Helpers.cpp", "MediaParsing.cpp", "Parser.cpp", "ParsingContext.cpp", - "Rule.cpp", "SelectorParsing.cpp", "Token.cpp", "Tokenizer.cpp", + "Types.cpp", ] } diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 98f0a91c33a..39febaba6a2 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -72,20 +72,16 @@ set(SOURCES CSS/MediaQuery.cpp CSS/MediaQueryList.cpp CSS/MediaQueryListEvent.cpp - CSS/Parser/Block.cpp CSS/Parser/ComponentValue.cpp - CSS/Parser/Declaration.cpp - CSS/Parser/DeclarationOrAtRule.cpp - CSS/Parser/Function.cpp CSS/Parser/GradientParsing.cpp CSS/Parser/Helpers.cpp CSS/Parser/MediaParsing.cpp CSS/Parser/Parser.cpp CSS/Parser/ParsingContext.cpp - CSS/Parser/Rule.cpp CSS/Parser/SelectorParsing.cpp CSS/Parser/Token.cpp CSS/Parser/Tokenizer.cpp + CSS/Parser/Types.cpp CSS/ParsedFontFace.cpp CSS/PercentageOr.cpp CSS/PreferredColorScheme.cpp diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Block.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Block.cpp deleted file mode 100644 index 31b1708de93..00000000000 --- a/Userland/Libraries/LibWeb/CSS/Parser/Block.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2020-2021, the SerenityOS developers. - * Copyright (c) 2021-2023, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include - -namespace Web::CSS::Parser { - -Block::Block(Token token, Vector&& values) - : m_token(move(token)) - , m_values(move(values)) -{ -} - -Block::~Block() = default; - -String Block::to_string() const -{ - StringBuilder builder; - - builder.append(m_token.bracket_string()); - builder.join(' ', m_values); - builder.append(m_token.bracket_mirror_string()); - - return MUST(builder.to_string()); -} - -} diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Block.h b/Userland/Libraries/LibWeb/CSS/Parser/Block.h deleted file mode 100644 index 46edd9aaebe..00000000000 --- a/Userland/Libraries/LibWeb/CSS/Parser/Block.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2020-2021, the SerenityOS developers. - * Copyright (c) 2021-2023, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace Web::CSS::Parser { - -class Block : public RefCounted { -public: - static NonnullRefPtr create(Token token, Vector&& values) - { - return adopt_ref(*new Block(move(token), move(values))); - } - - ~Block(); - - bool is_curly() const { return m_token.is(Token::Type::OpenCurly); } - bool is_paren() const { return m_token.is(Token::Type::OpenParen); } - bool is_square() const { return m_token.is(Token::Type::OpenSquare); } - - Token const& token() const { return m_token; } - - Vector const& values() const { return m_values; } - - String to_string() const; - -private: - Block(Token, Vector&&); - Token m_token; - Vector m_values; -}; -} diff --git a/Userland/Libraries/LibWeb/CSS/Parser/ComponentValue.cpp b/Userland/Libraries/LibWeb/CSS/Parser/ComponentValue.cpp index b8d30eed8b9..17ced75f54a 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/ComponentValue.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/ComponentValue.cpp @@ -5,9 +5,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include -#include namespace Web::CSS::Parser { @@ -15,11 +13,11 @@ ComponentValue::ComponentValue(Token token) : m_value(token) { } -ComponentValue::ComponentValue(NonnullRefPtr function) +ComponentValue::ComponentValue(Function&& function) : m_value(function) { } -ComponentValue::ComponentValue(NonnullRefPtr block) +ComponentValue::ComponentValue(SimpleBlock&& block) : m_value(block) { } @@ -28,7 +26,7 @@ ComponentValue::~ComponentValue() = default; bool ComponentValue::is_function(StringView name) const { - return is_function() && function().name().equals_ignoring_ascii_case(name); + return is_function() && function().name.equals_ignoring_ascii_case(name); } bool ComponentValue::is_ident(StringView ident) const @@ -40,8 +38,8 @@ String ComponentValue::to_string() const { return m_value.visit( [](Token const& token) { return token.to_string(); }, - [](NonnullRefPtr const& block) { return block->to_string(); }, - [](NonnullRefPtr const& function) { return function->to_string(); }); + [](SimpleBlock const& block) { return block.to_string(); }, + [](Function const& function) { return function.to_string(); }); } String ComponentValue::to_debug_string() const @@ -50,11 +48,11 @@ String ComponentValue::to_debug_string() const [](Token const& token) { return MUST(String::formatted("Token: {}", token.to_debug_string())); }, - [](NonnullRefPtr const& block) { - return MUST(String::formatted("Block: {}", block->to_string())); + [](SimpleBlock const& block) { + return MUST(String::formatted("Block: {}", block.to_string())); }, - [](NonnullRefPtr const& function) { - return MUST(String::formatted("Function: {}", function->to_string())); + [](Function const& function) { + return MUST(String::formatted("Function: {}", function.to_string())); }); } diff --git a/Userland/Libraries/LibWeb/CSS/Parser/ComponentValue.h b/Userland/Libraries/LibWeb/CSS/Parser/ComponentValue.h index 726570e352b..4ee121dc755 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/ComponentValue.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/ComponentValue.h @@ -11,24 +11,26 @@ #include #include #include -#include +#include namespace Web::CSS::Parser { -// https://www.w3.org/TR/css-syntax-3/#component-value +// https://drafts.csswg.org/css-syntax/#component-value class ComponentValue { public: ComponentValue(Token); - explicit ComponentValue(NonnullRefPtr); - explicit ComponentValue(NonnullRefPtr); + explicit ComponentValue(Function&&); + explicit ComponentValue(SimpleBlock&&); ~ComponentValue(); - bool is_block() const { return m_value.has>(); } - Block& block() const { return m_value.get>(); } + bool is_block() const { return m_value.has(); } + SimpleBlock& block() { return m_value.get(); } + SimpleBlock const& block() const { return m_value.get(); } - bool is_function() const { return m_value.has>(); } + bool is_function() const { return m_value.has(); } bool is_function(StringView name) const; - Function& function() const { return m_value.get>(); } + Function& function() { return m_value.get(); } + Function const& function() const { return m_value.get(); } bool is_token() const { return m_value.has(); } bool is(Token::Type type) const { return is_token() && token().is(type); } @@ -41,7 +43,7 @@ public: String to_debug_string() const; private: - Variant, NonnullRefPtr> m_value; + Variant m_value; }; } diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Declaration.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Declaration.cpp deleted file mode 100644 index 5e5b4162984..00000000000 --- a/Userland/Libraries/LibWeb/CSS/Parser/Declaration.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2020-2021, the SerenityOS developers. - * Copyright (c) 2021-2023, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include - -namespace Web::CSS::Parser { - -Declaration::Declaration(FlyString name, Vector values, Important important) - : m_name(move(name)) - , m_values(move(values)) - , m_important(move(important)) -{ -} - -Declaration::~Declaration() = default; - -String Declaration::to_string() const -{ - StringBuilder builder; - - serialize_an_identifier(builder, m_name); - builder.append(": "sv); - builder.join(' ', m_values); - - if (m_important == Important::Yes) - builder.append(" !important"sv); - - return MUST(builder.to_string()); -} - -} diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Declaration.h b/Userland/Libraries/LibWeb/CSS/Parser/Declaration.h deleted file mode 100644 index 8f0c04e3964..00000000000 --- a/Userland/Libraries/LibWeb/CSS/Parser/Declaration.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2020-2021, the SerenityOS developers. - * Copyright (c) 2022-2023, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace Web::CSS::Parser { - -class Declaration { -public: - Declaration(FlyString name, Vector values, Important); - ~Declaration(); - - FlyString const& name() const { return m_name; } - Vector const& values() const { return m_values; } - Important importance() const { return m_important; } - - String to_string() const; - -private: - FlyString m_name; - Vector m_values; - Important m_important { Important::No }; -}; - -} diff --git a/Userland/Libraries/LibWeb/CSS/Parser/DeclarationOrAtRule.cpp b/Userland/Libraries/LibWeb/CSS/Parser/DeclarationOrAtRule.cpp deleted file mode 100644 index 3c429787c24..00000000000 --- a/Userland/Libraries/LibWeb/CSS/Parser/DeclarationOrAtRule.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2020-2021, the SerenityOS developers. - * Copyright (c) 2021-2023, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include - -namespace Web::CSS::Parser { - -DeclarationOrAtRule::DeclarationOrAtRule(RefPtr at) - : m_type(DeclarationType::At) - , m_at(move(at)) -{ -} - -DeclarationOrAtRule::DeclarationOrAtRule(Declaration declaration) - : m_type(DeclarationType::Declaration) - , m_declaration(move(declaration)) -{ -} - -DeclarationOrAtRule::~DeclarationOrAtRule() = default; - -} diff --git a/Userland/Libraries/LibWeb/CSS/Parser/DeclarationOrAtRule.h b/Userland/Libraries/LibWeb/CSS/Parser/DeclarationOrAtRule.h deleted file mode 100644 index 2a839fc7671..00000000000 --- a/Userland/Libraries/LibWeb/CSS/Parser/DeclarationOrAtRule.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2020-2021, the SerenityOS developers. - * Copyright (c) 2021-2023, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include - -namespace Web::CSS::Parser { - -class DeclarationOrAtRule { -public: - explicit DeclarationOrAtRule(RefPtr at); - explicit DeclarationOrAtRule(Declaration declaration); - ~DeclarationOrAtRule(); - - enum class DeclarationType { - At, - Declaration, - }; - - bool is_at_rule() const { return m_type == DeclarationType::At; } - bool is_declaration() const { return m_type == DeclarationType::Declaration; } - - Rule const& at_rule() const - { - VERIFY(is_at_rule()); - return *m_at; - } - - Declaration const& declaration() const - { - VERIFY(is_declaration()); - return *m_declaration; - } - -private: - DeclarationType m_type; - RefPtr m_at; - Optional m_declaration; -}; - -} diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Function.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Function.cpp deleted file mode 100644 index 8705bb69234..00000000000 --- a/Userland/Libraries/LibWeb/CSS/Parser/Function.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2020-2021, the SerenityOS developers. - * Copyright (c) 2021-2023, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include - -namespace Web::CSS::Parser { - -Function::Function(FlyString name, Vector&& values) - : m_name(move(name)) - , m_values(move(values)) -{ -} - -Function::~Function() = default; - -String Function::to_string() const -{ - StringBuilder builder; - - serialize_an_identifier(builder, m_name); - builder.append('('); - for (auto& item : m_values) - builder.append(item.to_string()); - builder.append(')'); - - return MUST(builder.to_string()); -} - -} diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Function.h b/Userland/Libraries/LibWeb/CSS/Parser/Function.h deleted file mode 100644 index 81c3f7d9162..00000000000 --- a/Userland/Libraries/LibWeb/CSS/Parser/Function.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2020-2021, the SerenityOS developers. - * Copyright (c) 2021-2023, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace Web::CSS::Parser { - -class Function : public RefCounted { -public: - static NonnullRefPtr create(FlyString name, Vector&& values) - { - return adopt_ref(*new Function(move(name), move(values))); - } - - ~Function(); - - FlyString const& name() const { return m_name; } - Vector const& values() const { return m_values; } - - String to_string() const; - -private: - Function(FlyString name, Vector&& values); - - FlyString m_name; - Vector m_values; -}; -} diff --git a/Userland/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp b/Userland/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp index 978a5f3d33d..77babc001a1 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp @@ -148,7 +148,7 @@ RefPtr Parser::parse_linear_gradient_function(TokenStream Parser::parse_linear_gradient_function(TokenStream | to ]?, ) - TokenStream tokens { component_value.function().values() }; + TokenStream tokens { component_value.function().value }; tokens.discard_whitespace(); if (!tokens.has_next_token()) @@ -277,7 +277,7 @@ RefPtr Parser::parse_conic_gradient_function(TokenStream Parser::parse_conic_gradient_function(TokenStream Parser::parse_radial_gradient_function(TokenStream Parser::parse_radial_gradient_function(TokenStream Parser::parse_media_in_parens(TokenStream // `( ) | ( )` auto const& first_token = tokens.next_token(); if (first_token.is_block() && first_token.block().is_paren()) { - TokenStream inner_token_stream { first_token.block().values() }; + TokenStream inner_token_stream { first_token.block().value }; if (auto maybe_media_condition = parse_media_condition(inner_token_stream, MediaCondition::AllowOr::Yes)) { tokens.discard_a_token(); transaction.commit(); @@ -620,24 +620,17 @@ Optional Parser::parse_media_feature_value(MediaFeatureID med return {}; } -JS::GCPtr Parser::convert_to_media_rule(Rule& rule) +JS::GCPtr Parser::convert_to_media_rule(AtRule const& rule) { - if (!rule.block()) { - dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @media rule: No block."); - return {}; - } - - auto media_query_tokens = TokenStream { rule.prelude() }; + auto media_query_tokens = TokenStream { rule.prelude }; auto media_query_list = parse_a_media_query_list(media_query_tokens); - - auto child_tokens = TokenStream { rule.block()->values() }; - auto parser_rules = parse_a_list_of_rules(child_tokens); - JS::MarkedVector child_rules(m_context.realm().heap()); - for (auto& raw_rule : parser_rules) { - if (auto child_rule = convert_to_rule(raw_rule)) - child_rules.append(child_rule); - } auto media_list = MediaList::create(m_context.realm(), move(media_query_list)); + + JS::MarkedVector child_rules { m_context.realm().heap() }; + rule.for_each_as_rule_list([&](auto& rule) { + if (auto child_rule = convert_to_rule(rule)) + child_rules.append(child_rule); + }); auto rule_list = CSSRuleList::create(m_context.realm(), child_rules); return CSSMediaRule::create(m_context.realm(), media_list, rule_list); } diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 0367ec4e160..f36cb01f34a 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -33,12 +33,8 @@ #include #include #include -#include -#include -#include -#include #include -#include +#include #include #include #include @@ -125,14 +121,13 @@ Parser::Parser(Parser&& other) m_token_stream.copy_state({}, other.m_token_stream); } -// 5.3.3. Parse a stylesheet -// https://www.w3.org/TR/css-syntax-3/#parse-stylesheet +// https://drafts.csswg.org/css-syntax/#parse-stylesheet template -Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream& tokens, Optional location) +Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream& input, Optional location) { // To parse a stylesheet from an input given an optional url location: - // 1. If input is a byte stream for stylesheet, decode bytes from input, and set input to the result. + // 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result. // 2. Normalize input, and set input to the result. // NOTE: These are done automatically when creating the Parser. @@ -140,14 +135,27 @@ Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream& tokens, Opti ParsedStyleSheet style_sheet; style_sheet.location = move(location); - // 4. Consume a list of rules from input, with the top-level flag set, and set the stylesheet’s value to the result. - style_sheet.rules = consume_a_list_of_rules(tokens, TopLevel::Yes); + // 4. Consume a stylesheet’s contents from input, and set the stylesheet’s rules to the result. + style_sheet.rules = consume_a_stylesheets_contents(input); // 5. Return the stylesheet. return style_sheet; } -// https://www.w3.org/TR/css-syntax-3/#parse-a-css-stylesheet +// https://drafts.csswg.org/css-syntax/#parse-a-stylesheets-contents +template +Vector Parser::parse_a_stylesheets_contents(TokenStream& input) +{ + // To parse a stylesheet’s contents from input: + + // 1. Normalize input, and set input to the result. + // NOTE: This is done automatically when creating the Parser. + + // 2. Consume a stylesheet’s contents from input, and return the result. + return consume_a_stylesheets_contents(input); +} + +// https://drafts.csswg.org/css-syntax/#parse-a-css-stylesheet CSSStyleSheet* Parser::parse_as_css_stylesheet(Optional location) { // To parse a CSS stylesheet, first parse a stylesheet. @@ -155,11 +163,15 @@ CSSStyleSheet* Parser::parse_as_css_stylesheet(Optional location) // Interpret all of the resulting top-level qualified rules as style rules, defined below. JS::MarkedVector rules(m_context.realm().heap()); - for (auto& raw_rule : style_sheet.rules) { + for (auto const& raw_rule : style_sheet.rules) { auto rule = convert_to_rule(raw_rule); - // If any style rule is invalid, or any at-rule is not recognized or is invalid according to its grammar or context, it’s a parse error. Discard that rule. - if (rule) - rules.append(rule); + // If any style rule is invalid, or any at-rule is not recognized or is invalid according to its grammar or context, it’s a parse error. + // Discard that rule. + if (!rule) { + log_parse_error(); + continue; + } + rules.append(rule); } auto rule_list = CSSRuleList::create(m_context.realm(), rules); @@ -264,7 +276,7 @@ Optional Parser::parse_supports_in_parens(TokenStream Parser::parse_supports_feature(TokenStream` if (first_token.is_block() && first_token.block().is_paren()) { - TokenStream block_tokens { first_token.block().values() }; + TokenStream block_tokens { first_token.block().value }; // FIXME: Parsing and then converting back to a string is weird. if (auto declaration = consume_a_declaration(block_tokens); declaration.has_value()) { transaction.commit(); @@ -314,7 +326,7 @@ Optional Parser::parse_supports_feature(TokenStream Parser::parse_general_enclosed(TokenStream -Vector> Parser::consume_a_list_of_rules(TokenStream& tokens, TopLevel top_level) +Vector Parser::consume_a_stylesheets_contents(TokenStream& input) { - // To consume a list of rules, given a top-level flag: + // To consume a stylesheet’s contents from a token stream input: - // Create an initially empty list of rules. - Vector> rules; + // Let rules be an initially empty list of rules. + Vector rules; - // Repeatedly consume the next input token: + // Process input: for (;;) { - auto& token = tokens.consume_a_token(); + auto& token = input.next_token(); // if (token.is(Token::Type::Whitespace)) { - // Do nothing. + // Discard a token from input. + input.discard_a_token(); continue; } // if (token.is(Token::Type::EndOfFile)) { - // Return the list of rules. + // Return rules. return rules; } // // if (token.is(Token::Type::CDO) || token.is(Token::Type::CDC)) { - // If the top-level flag is set, do nothing. - if (top_level == TopLevel::Yes) - continue; - - // Otherwise, reconsume the current input token. - tokens.reconsume_current_input_token(); - - // Consume a qualified rule. If anything is returned, append it to the list of rules. - if (auto maybe_qualified = consume_a_qualified_rule(tokens)) - rules.append(maybe_qualified.release_nonnull()); - + // Discard a token from input. + input.discard_a_token(); continue; } // if (token.is(Token::Type::AtKeyword)) { - // Reconsume the current input token. - tokens.reconsume_current_input_token(); - - // Consume an at-rule, and append the returned value to the list of rules. - rules.append(consume_an_at_rule(tokens)); - + // Consume an at-rule from input. If anything is returned, append it to rules. + if (auto maybe_at_rule = consume_an_at_rule(input); maybe_at_rule.has_value()) + rules.append(*maybe_at_rule); continue; } // anything else { - // Reconsume the current input token. - tokens.reconsume_current_input_token(); - - // Consume a qualified rule. If anything is returned, append it to the list of rules. - if (auto maybe_qualified = consume_a_qualified_rule(tokens)) - rules.append(maybe_qualified.release_nonnull()); - - continue; + // Consume a qualified rule from input. If a rule is returned, append it to rules. + consume_a_qualified_rule(input).visit( + [&](QualifiedRule qualified_rule) { rules.append(move(qualified_rule)); }, + [](auto&) {}); } } } -// 5.4.2. Consume an at-rule -// https://www.w3.org/TR/css-syntax-3/#consume-at-rule +// https://drafts.csswg.org/css-syntax/#consume-at-rule template -NonnullRefPtr Parser::consume_an_at_rule(TokenStream& tokens) +Optional Parser::consume_an_at_rule(TokenStream& input, Nested nested) { - // To consume an at-rule: + // To consume an at-rule from a token stream input, given an optional bool nested (default false): - // Consume the next input token. - auto& name_ident = tokens.consume_a_token(); - VERIFY(name_ident.is(Token::Type::AtKeyword)); + // Assert: The next token is an . + VERIFY(input.next_token().is(Token::Type::AtKeyword)); - // Create a new at-rule with its name set to the value of the current input token, its prelude initially set to an empty list, and its value initially set to nothing. - // NOTE: We create the Rule fully initialized when we return it instead. - auto at_rule_name = ((Token)name_ident).at_keyword(); - Vector prelude; - RefPtr block; + // Consume a token from input, and let rule be a new at-rule with its name set to the returned token’s value, + // its prelude initially set to an empty list, and no declarations or child rules. + AtRule rule { + .name = ((Token)input.consume_a_token()).at_keyword(), + .prelude = {}, + .child_rules_and_lists_of_declarations = {}, + }; - // Repeatedly consume the next input token: + // Process input: for (;;) { - auto& token = tokens.consume_a_token(); + auto& token = input.next_token(); // - if (token.is(Token::Type::Semicolon)) { - // Return the at-rule. - return Rule::make_at_rule(move(at_rule_name), move(prelude), move(block)); + // + if (token.is(Token::Type::Semicolon) || token.is(Token::Type::EndOfFile)) { + // Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing. + input.discard_a_token(); + if (is_valid_in_the_current_context(rule)) + return rule; + return {}; } - // - if (token.is(Token::Type::EndOfFile)) { - // This is a parse error. Return the at-rule. - log_parse_error(); - return Rule::make_at_rule(move(at_rule_name), move(prelude), move(block)); + // <}-token> + if (token.is(Token::Type::CloseCurly)) { + // If nested is true: + if (nested == Nested::Yes) { + // If rule is valid in the current context, return it. + if (is_valid_in_the_current_context(rule)) + return rule; + // Otherwise, return nothing. + return {}; + } + // Otherwise, consume a token and append the result to rule’s prelude. + else { + rule.prelude.append(input.consume_a_token()); + } + continue; } // <{-token> if (token.is(Token::Type::OpenCurly)) { - // Consume a simple block and assign it to the at-rule’s block. Return the at-rule. - block = consume_a_simple_block(tokens); - return Rule::make_at_rule(move(at_rule_name), move(prelude), move(block)); - } + // Consume a block from input, and assign the result to rule’s child rules. + rule.child_rules_and_lists_of_declarations = consume_a_block(input); - // simple block with an associated token of <{-token> - if constexpr (IsSame) { - ComponentValue const& component_value = token; - if (component_value.is_block() && component_value.block().is_curly()) { - // Assign the block to the at-rule’s block. Return the at-rule. - block = component_value.block(); - return Rule::make_at_rule(move(at_rule_name), move(prelude), move(block)); - } + // If rule is valid in the current context, return it. Otherwise, return nothing. + if (is_valid_in_the_current_context(rule)) + return rule; + return {}; } // anything else { - // Reconsume the current input token. - tokens.reconsume_current_input_token(); - // Consume a component value. Append the returned value to the at-rule’s prelude. - prelude.append(consume_a_component_value(tokens)); + // Consume a component value from input and append the returned value to rule’s prelude. + rule.prelude.append(consume_a_component_value(input)); } } } -// 5.4.3. Consume a qualified rule -// https://www.w3.org/TR/css-syntax-3/#consume-qualified-rule +// https://drafts.csswg.org/css-syntax/#consume-qualified-rule template -RefPtr Parser::consume_a_qualified_rule(TokenStream& tokens) +Variant Parser::consume_a_qualified_rule(TokenStream& input, Optional stop_token, Nested nested) { - // To consume a qualified rule: + // To consume a qualified rule, from a token stream input, given an optional token stop token and an optional bool nested (default false): - // Create a new qualified rule with its prelude initially set to an empty list, and its value initially set to nothing. - // NOTE: We create the Rule fully initialized when we return it instead. - Vector prelude; - RefPtr block; + // Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists. + QualifiedRule rule { + .prelude = {}, + .declarations = {}, + .child_rules = {}, + }; - // Repeatedly consume the next input token: + // Process input: for (;;) { - auto& token = tokens.consume_a_token(); + auto& token = input.next_token(); // - if (token.is(Token::Type::EndOfFile)) { + // stop token (if passed) + if (token.is(Token::Type::EndOfFile) || (stop_token.has_value() && token.is(*stop_token))) { // This is a parse error. Return nothing. log_parse_error(); return {}; } - // <{-token> - if (token.is(Token::Type::OpenCurly)) { - // Consume a simple block and assign it to the qualified rule’s block. Return the qualified rule. - block = consume_a_simple_block(tokens); - return Rule::make_qualified_rule(move(prelude), move(block)); + // <}-token> + if (token.is(Token::Type::CloseCurly)) { + // This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude. + log_parse_error(); + if (nested == Nested::Yes) + return {}; + rule.prelude.append(input.consume_a_token()); + continue; } - // simple block with an associated token of <{-token> - if constexpr (IsSame) { - ComponentValue const& component_value = token; - if (component_value.is_block() && component_value.block().is_curly()) { - // Assign the block to the qualified rule’s block. Return the qualified rule. - block = component_value.block(); - return Rule::make_qualified_rule(move(prelude), move(block)); + // <{-token> + if (token.is(Token::Type::OpenCurly)) { + // If the first two non- values of rule’s prelude are an whose value starts with "--" + // followed by a , then: + TokenStream prelude_tokens { rule.prelude }; + prelude_tokens.discard_whitespace(); + auto& first_non_whitespace = prelude_tokens.consume_a_token(); + prelude_tokens.discard_whitespace(); + auto& second_non_whitespace = prelude_tokens.consume_a_token(); + if (first_non_whitespace.is(Token::Type::Ident) && first_non_whitespace.token().ident().starts_with_bytes("--"sv) + && second_non_whitespace.is(Token::Type::Colon)) { + // If nested is true, consume the remnants of a bad declaration from input, with nested set to true, and return nothing. + if (nested == Nested::Yes) { + consume_the_remnants_of_a_bad_declaration(input, Nested::Yes); + return {}; + } + + // If nested is false, consume a block from input, and return nothing. + (void)consume_a_block(input); + return {}; } + + // Otherwise, consume a block from input, and let child rules be the result. + rule.child_rules = consume_a_block(input); + + // If the first item of child rules is a list of declarations, remove it from child rules and assign it to rule’s declarations. + if (!rule.child_rules.is_empty() && rule.child_rules.first().has>()) { + auto first = rule.child_rules.take_first(); + rule.declarations = move(first.get>()); + } + + // FIXME: If any remaining items of child rules are lists of declarations, replace them with nested declarations rules + // containing the list as its sole child. Assign child rules to rule’s child rules. + + // If rule is valid in the current context, return it; otherwise return an invalid rule error. + if (is_valid_in_the_current_context(rule)) + return rule; + return InvalidRuleError {}; } // anything else { - // Reconsume the current input token. - tokens.reconsume_current_input_token(); - - // Consume a component value. Append the returned value to the qualified rule’s prelude. - prelude.append(consume_a_component_value(tokens)); + // Consume a component value from input and append the result to rule’s prelude. + rule.prelude.append(consume_a_component_value(input)); } } } -// 5.4.4. Consume a style block’s contents -// https://www.w3.org/TR/css-syntax-3/#consume-a-style-blocks-contents +// https://drafts.csswg.org/css-syntax/#consume-block template -Vector Parser::consume_a_style_blocks_contents(TokenStream& tokens) +Vector Parser::consume_a_block(TokenStream& input) { - // To consume a style block’s contents: - // Create an initially empty list of declarations decls, and an initially empty list of rules rules. - Vector declarations; - Vector rules; + // To consume a block, from a token stream input: - // Repeatedly consume the next input token: + // Assert: The next token is a <{-token>. + VERIFY(input.next_token().is(Token::Type::OpenCurly)); + + // Discard a token from input. + input.discard_a_token(); + // Consume a block’s contents from input and let rules be the result. + auto rules = consume_a_blocks_contents(input); + // Discard a token from input. + input.discard_a_token(); + + // Return rules. + return rules; +} + +// https://drafts.csswg.org/css-syntax/#consume-block-contents +template +Vector Parser::consume_a_blocks_contents(TokenStream& input) +{ + // To consume a block’s contents from a token stream input: + + // Let rules be an empty list, containing either rules or lists of declarations. + Vector rules; + + // Let decls be an empty list of declarations. + Vector declarations; + + // Process input: for (;;) { - auto& token = tokens.consume_a_token(); + auto& token = input.next_token(); // // if (token.is(Token::Type::Whitespace) || token.is(Token::Type::Semicolon)) { - // Do nothing. + // Discard a token from input. + input.discard_a_token(); continue; } // - if (token.is(Token::Type::EndOfFile)) { - // Extend decls with rules, then return decls. - declarations.extend(move(rules)); - return declarations; + // <}-token> + if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseCurly)) { + // AD-HOC: If decls is not empty, append it to rules. + // Spec issue: https://github.com/w3c/csswg-drafts/issues/11017 + if (!declarations.is_empty()) + rules.append(move(declarations)); + // Return rules. + return rules; } // if (token.is(Token::Type::AtKeyword)) { - // Reconsume the current input token. - tokens.reconsume_current_input_token(); - - // Consume an at-rule, and append the result to rules. - rules.empend(consume_an_at_rule(tokens)); - continue; - } - - // - if (token.is(Token::Type::Ident)) { - // Initialize a temporary list initially filled with the current input token. - Vector temporary_list; - temporary_list.append(token); - - // As long as the next input token is anything other than a or , - // consume a component value and append it to the temporary list. - for (;;) { - auto& next_input_token = tokens.next_token(); - if (next_input_token.is(Token::Type::Semicolon) || next_input_token.is(Token::Type::EndOfFile)) - break; - temporary_list.append(consume_a_component_value(tokens)); + // If decls is not empty, append it to rules, and set decls to a fresh empty list of declarations. + if (!declarations.is_empty()) { + rules.append(move(declarations)); + declarations = {}; } - // Consume a declaration from the temporary list. If anything was returned, append it to decls. - auto token_stream = TokenStream(temporary_list); - if (auto maybe_declaration = consume_a_declaration(token_stream); maybe_declaration.has_value()) - declarations.empend(maybe_declaration.release_value()); - - continue; - } - - // with a value of "&" (U+0026 AMPERSAND) - if (token.is_delim('&')) { - // Reconsume the current input token. - tokens.reconsume_current_input_token(); - - // Consume a qualified rule. If anything was returned, append it to rules. - if (auto qualified_rule = consume_a_qualified_rule(tokens)) - rules.empend(qualified_rule); + // Consume an at-rule from input, with nested set to true. + // If a rule was returned, append it to rules. + if (auto at_rule = consume_an_at_rule(input, Nested::Yes); at_rule.has_value()) + rules.append({ at_rule.release_value() }); continue; } // anything else { - // This is a parse error. - log_parse_error(); + // Mark input. + input.mark(); - // Reconsume the current input token. - tokens.reconsume_current_input_token(); + // Consume a declaration from input, with nested set to true. + // If a declaration was returned, append it to decls, and discard a mark from input. + if (auto declaration = consume_a_declaration(input, Nested::Yes); declaration.has_value()) { + declarations.append(declaration.release_value()); + input.discard_a_mark(); + } - // As long as the next input token is anything other than a or , - // consume a component value and throw away the returned value. - for (;;) { - auto& peek = tokens.next_token(); - if (peek.is(Token::Type::Semicolon) || peek.is(Token::Type::EndOfFile)) - break; - (void)consume_a_component_value(tokens); + // Otherwise, restore a mark from input, then consume a qualified rule from input, + // with nested set to true, and as the stop token. + else { + input.restore_a_mark(); + consume_a_qualified_rule(input, Token::Type::Semicolon, Nested::Yes).visit( + // -> If nothing was returned + [](Empty&) { + // Do nothing + }, + // -> If an invalid rule error was returned + [&](InvalidRuleError&) { + // If decls is not empty, append decls to rules, and set decls to a fresh empty list of declarations. (Otherwise, do nothing.) + if (!declarations.is_empty()) { + rules.append(move(declarations)); + declarations = {}; + } + }, + // -> If a rule was returned + [&](QualifiedRule rule) { + // If decls is not empty, append decls to rules, and set decls to a fresh empty list of declarations. + if (!declarations.is_empty()) { + rules.append(move(declarations)); + declarations = {}; + } + // Append the rule to rules. + rules.append({ move(rule) }); + }); } } } @@ -629,195 +685,223 @@ ComponentValue Parser::consume_a_component_value(TokenStream& to } // 5.4.7. Consume a component value -// https://www.w3.org/TR/css-syntax-3/#consume-component-value +// https://drafts.csswg.org/css-syntax/#consume-component-value template -ComponentValue Parser::consume_a_component_value(TokenStream& tokens) +ComponentValue Parser::consume_a_component_value(TokenStream& input) { - // To consume a component value: + // To consume a component value from a token stream input: - // Consume the next input token. - auto& token = tokens.consume_a_token(); + // Process input: + for (;;) { + auto& token = input.next_token(); - // If the current input token is a <{-token>, <[-token>, or <(-token>, consume a simple block and return it. - if (token.is(Token::Type::OpenCurly) || token.is(Token::Type::OpenSquare) || token.is(Token::Type::OpenParen)) - return ComponentValue(consume_a_simple_block(tokens)); + // <{-token> + // <[-token> + // <(-token> + if (token.is(Token::Type::OpenCurly) || token.is(Token::Type::OpenSquare) || token.is(Token::Type::OpenParen)) { + // Consume a simple block from input and return the result. + return ComponentValue { consume_a_simple_block(input) }; + } - // Otherwise, if the current input token is a , consume a function and return it. - if (token.is(Token::Type::Function)) - return ComponentValue(consume_a_function(tokens)); + // + if (token.is(Token::Type::Function)) { + // Consume a function from input and return the result. + return ComponentValue { consume_a_function(input) }; + } - // Otherwise, return the current input token. - return ComponentValue(token); + // anything else + { + // Consume a token from input and return the result. + return ComponentValue { input.consume_a_token() }; + } + } } -// 5.4.8. Consume a simple block -// https://www.w3.org/TR/css-syntax-3/#consume-simple-block template -NonnullRefPtr Parser::consume_a_simple_block(TokenStream& tokens) +Vector Parser::consume_a_list_of_component_values(TokenStream& input, Optional stop_token, Nested nested) { - // Note: This algorithm assumes that the current input token has already been checked - // to be an <{-token>, <[-token>, or <(-token>. + // To consume a list of component values from a token stream input, given an optional token stop token + // and an optional boolean nested (default false): - // To consume a simple block: + // Let values be an empty list of component values. + Vector values; - // The ending token is the mirror variant of the current input token. - // (E.g. if it was called with <[-token>, the ending token is <]-token>.) - auto ending_token = ((Token)tokens.current_token()).mirror_variant(); - - // Create a simple block with its associated token set to the current input token - // and with its value initially set to an empty list. - // NOTE: We create the Block fully initialized when we return it instead. - Token block_token = tokens.current_token(); - Vector block_values; - - // Repeatedly consume the next input token and process it as follows: + // Process input: for (;;) { - auto& token = tokens.consume_a_token(); + auto& token = input.next_token(); + // + // stop token (if passed) + if (token.is(Token::Type::EndOfFile) || (stop_token.has_value() && token.is(*stop_token))) { + // Return values. + return values; + } + + // <}-token> + if (token.is(Token::Type::CloseCurly)) { + // If nested is true, return values. + if (nested == Nested::Yes) { + return values; + } + // Otherwise, this is a parse error. Consume a token from input and append the result to values. + else { + log_parse_error(); + values.append(input.consume_a_token()); + } + } + + // anything else + { + // Consume a component value from input, and append the result to values. + values.append(consume_a_component_value(input)); + } + } +} + +// https://drafts.csswg.org/css-syntax/#consume-simple-block +template +SimpleBlock Parser::consume_a_simple_block(TokenStream& input) +{ + // To consume a simple block from a token stream input: + + // Assert: the next token of input is <{-token>, <[-token>, or <(-token>. + auto& next = input.next_token(); + VERIFY(next.is(Token::Type::OpenCurly) || next.is(Token::Type::OpenSquare) || next.is(Token::Type::OpenParen)); + + // Let ending token be the mirror variant of the next token. (E.g. if it was called with <[-token>, the ending token is <]-token>.) + auto ending_token = ((Token)input.next_token()).mirror_variant(); + + // Let block be a new simple block with its associated token set to the next token and with its value initially set to an empty list. + SimpleBlock block { + .token = input.next_token(), + .value = {}, + }; + + // Discard a token from input. + input.discard_a_token(); + + // Process input: + for (;;) { + auto& token = input.next_token(); + + // // ending token - if (token.is(ending_token)) { - // Return the block. - return Block::create(move(block_token), move(block_values)); - } - // - if (token.is(Token::Type::EndOfFile)) { - // This is a parse error. Return the block. - log_parse_error(); - return Block::create(move(block_token), move(block_values)); + if (token.is(Token::Type::EndOfFile) || token.is(ending_token)) { + // Discard a token from input. Return block. + input.discard_a_token(); + return block; } // anything else { - // Reconsume the current input token. - tokens.reconsume_current_input_token(); - - // Consume a component value and append it to the value of the block. - block_values.empend(consume_a_component_value(tokens)); + // Consume a component value from input and append the result to block’s value. + block.value.empend(consume_a_component_value(input)); } } } -// 5.4.9. Consume a function -// https://www.w3.org/TR/css-syntax-3/#consume-function +// https://drafts.csswg.org/css-syntax/#consume-function template -NonnullRefPtr Parser::consume_a_function(TokenStream& tokens) +Function Parser::consume_a_function(TokenStream& input) { - // Note: This algorithm assumes that the current input token has already been checked to be a . - auto name_ident = tokens.current_token(); - VERIFY(name_ident.is(Token::Type::Function)); + // To consume a function from a token stream input: - // To consume a function: + // Assert: The next token is a . + VERIFY(input.next_token().is(Token::Type::Function)); - // Create a function with its name equal to the value of the current input token - // and with its value initially set to an empty list. - // NOTE: We create the Function fully initialized when we return it instead. - auto function_name = ((Token)name_ident).function(); - Vector function_values; + // Consume a token from input, and let function be a new function with its name equal the returned token’s value, + // and a value set to an empty list. + Function function { + .name = ((Token)input.consume_a_token()).function(), + .value = {}, + }; - // Repeatedly consume the next input token and process it as follows: + // Process input: for (;;) { - auto& token = tokens.consume_a_token(); + auto& token = input.next_token(); + // // <)-token> - if (token.is(Token::Type::CloseParen)) { - // Return the function. - return Function::create(move(function_name), move(function_values)); - } - - // - if (token.is(Token::Type::EndOfFile)) { - // This is a parse error. Return the function. - log_parse_error(); - return Function::create(move(function_name), move(function_values)); + if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseParen)) { + // Discard a token from input. Return function. + input.discard_a_token(); + return function; } // anything else { - // Reconsume the current input token. - tokens.reconsume_current_input_token(); - - // Consume a component value and append the returned value to the function’s value. - function_values.append(consume_a_component_value(tokens)); + // Consume a component value from input and append the result to function’s value. + function.value.append(consume_a_component_value(input)); } } } -// 5.4.6. Consume a declaration -// https://www.w3.org/TR/css-syntax-3/#consume-declaration +// https://drafts.csswg.org/css-syntax/#consume-declaration template -Optional Parser::consume_a_declaration(TokenStream& tokens) +Optional Parser::consume_a_declaration(TokenStream& input, Nested nested) { - // Note: This algorithm assumes that the next input token has already been checked to - // be an . - // NOTE: This is not true in our implementation! For convenience, we both skip whitespace - // and gracefully handle the first token not being an . + // To consume a declaration from a token stream input, given an optional bool nested (default false): - // To consume a declaration: + // TODO: As noted in the "Implementation note" below https://drafts.csswg.org/css-syntax/#consume-block-contents + // there are ways we can optimise this by early-exiting. - // Consume the next input token. - auto transaction = tokens.begin_transaction(); - tokens.discard_whitespace(); - auto& token = tokens.consume_a_token(); + // Let decl be a new declaration, with an initially empty name and a value set to an empty list. + Declaration declaration { + .name {}, + .value {}, + }; - // NOTE: Not to spec, handle the case where the input token *isn't* an . - if (!token.is(Token::Type::Ident)) - return {}; - - // Create a new declaration with its name set to the value of the current input token - // and its value initially set to the empty list. - // NOTE: We create a fully-initialized Declaration just before returning it instead. - auto declaration_name = ((Token)token).ident(); - Vector declaration_values; - Important declaration_important = Important::No; - - // 1. While the next input token is a , consume the next input token. - tokens.discard_whitespace(); - - // 2. If the next input token is anything other than a , this is a parse error. - // Return nothing. - auto& maybe_colon = tokens.next_token(); - if (!maybe_colon.is(Token::Type::Colon)) { - log_parse_error(); + // 1. If the next token is an , consume a token from input and set decl’s name to the token’s value. + if (input.next_token().is(Token::Type::Ident)) { + declaration.name = ((Token)input.consume_a_token()).ident(); + } + // Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing. + else { + consume_the_remnants_of_a_bad_declaration(input, nested); return {}; } - // Otherwise, consume the next input token. - tokens.discard_a_token(); - // 3. While the next input token is a , consume the next input token. - tokens.discard_whitespace(); + // 2. Discard whitespace from input. + input.discard_whitespace(); - // 4. As long as the next input token is anything other than an , consume a - // component value and append it to the declaration’s value. - for (;;) { - if (tokens.next_token().is(Token::Type::EndOfFile)) { - break; - } - declaration_values.append(consume_a_component_value(tokens)); + // 3. If the next token is a , discard a token from input. + if (input.next_token().is(Token::Type::Colon)) { + input.discard_a_token(); + } + // Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing. + else { + consume_the_remnants_of_a_bad_declaration(input, nested); + return {}; } - // 5. If the last two non-s in the declaration’s value are a - // with the value "!" followed by an with a value that is an ASCII case-insensitive - // match for "important", remove them from the declaration’s value and set the declaration’s - // important flag to true. - if (declaration_values.size() >= 2) { - // Walk backwards from the end until we find "important" + // 4. Discard whitespace from input. + input.discard_whitespace(); + + // 5. Consume a list of component values from input, with nested, and with as the stop token, + // and set decl’s value to the result. + declaration.value = consume_a_list_of_component_values(input, Token::Type::Semicolon, nested); + + // 6. If the last two non-s in decl’s value are a with the value "!" + // followed by an with a value that is an ASCII case-insensitive match for "important", + // remove them from decl’s value and set decl’s important flag. + if (declaration.value.size() >= 2) { + // NOTE: Walk backwards from the end until we find "important" Optional important_index; - for (size_t i = declaration_values.size() - 1; i > 0; i--) { - auto value = declaration_values[i]; + for (size_t i = declaration.value.size() - 1; i > 0; i--) { + auto const& value = declaration.value[i]; if (value.is_ident("important"sv)) { important_index = i; break; } - if (value.is(Token::Type::Whitespace)) - continue; - break; + if (!value.is(Token::Type::Whitespace)) + break; } - // Walk backwards from important until we find "!" + // NOTE: Walk backwards from important until we find "!" if (important_index.has_value()) { Optional bang_index; for (size_t i = important_index.value() - 1; i > 0; i--) { - auto value = declaration_values[i]; + auto const& value = declaration.value[i]; if (value.is_delim('!')) { bang_index = i; break; @@ -828,175 +912,161 @@ Optional Parser::consume_a_declaration(TokenStream& tokens) } if (bang_index.has_value()) { - declaration_values.remove(important_index.value()); - declaration_values.remove(bang_index.value()); - declaration_important = Important::Yes; + declaration.value.remove(important_index.value()); + declaration.value.remove(bang_index.value()); + declaration.important = Important::Yes; } } } - // 6. While the last token in the declaration’s value is a , remove that token. - while (!declaration_values.is_empty()) { - auto maybe_whitespace = declaration_values.last(); - if (!(maybe_whitespace.is(Token::Type::Whitespace))) { - break; - } - declaration_values.take_last(); + // 7. While the last item in decl’s value is a , remove that token. + while (!declaration.value.is_empty() && declaration.value.last().is(Token::Type::Whitespace)) { + declaration.value.take_last(); } - // 7. Return the declaration. - transaction.commit(); - return Declaration { move(declaration_name), move(declaration_values), declaration_important }; + // See second clause of step 8. + auto contains_a_curly_block_and_non_whitespace = [](Vector const& declaration_value) { + bool contains_curly_block = false; + bool contains_non_whitespace = false; + for (auto const& value : declaration_value) { + if (value.is_block() && value.block().is_curly()) { + if (contains_non_whitespace) + return true; + contains_curly_block = true; + continue; + } + + if (!value.is(Token::Type::Whitespace)) { + if (contains_curly_block) + return true; + contains_non_whitespace = true; + continue; + } + } + return false; + }; + + // 8. If decl’s name is a custom property name string, then set decl’s original text to the segment + // of the original source text string corresponding to the tokens of decl’s value. + if (is_a_custom_property_name_string(declaration.name)) { + // FIXME: Set the original source text + } + // Otherwise, if decl’s value contains a top-level simple block with an associated token of <{-token>, + // and also contains any other non- value, return nothing. + // (That is, a top-level {}-block is only allowed as the entire value of a non-custom property.) + else if (contains_a_curly_block_and_non_whitespace(declaration.value)) { + return {}; + } + // Otherwise, if decl’s name is an ASCII case-insensitive match for "unicode-range", consume the value of + // a unicode-range descriptor from the segment of the original source text string corresponding to the + // tokens returned by the consume a list of component values call, and replace decl’s value with the result. + else if (declaration.name.equals_ignoring_ascii_case("unicode-range"sv)) { + // FIXME: Special unicode-range handling + } + + // 9. If decl is valid in the current context, return it; otherwise return nothing. + if (is_valid_in_the_current_context(declaration)) + return declaration; + return {}; } -// 5.4.5. Consume a list of declarations -// https://www.w3.org/TR/css-syntax-3/#consume-list-of-declarations +// https://drafts.csswg.org/css-syntax/#consume-the-remnants-of-a-bad-declaration template -Vector Parser::consume_a_list_of_declarations(TokenStream& tokens) +void Parser::consume_the_remnants_of_a_bad_declaration(TokenStream& input, Nested nested) { - // To consume a list of declarations: + // To consume the remnants of a bad declaration from a token stream input, given a bool nested: - // Create an initially empty list of declarations. - Vector list_of_declarations; - - // Repeatedly consume the next input token: + // Process input: for (;;) { - auto& token = tokens.consume_a_token(); + auto& token = input.next_token(); - // + // // - if (token.is(Token::Type::Whitespace) || token.is(Token::Type::Semicolon)) { - // Do nothing. - continue; + if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::Semicolon)) { + // Discard a token from input, and return nothing. + input.discard_a_token(); + return; } - // - if (token.is(Token::Type::EndOfFile)) { - // Return the list of declarations. - return list_of_declarations; - } - - // - if (token.is(Token::Type::AtKeyword)) { - // Reconsume the current input token. - tokens.reconsume_current_input_token(); - - // Consume an at-rule. Append the returned rule to the list of declarations. - list_of_declarations.empend(consume_an_at_rule(tokens)); - continue; - } - - // - if (token.is(Token::Type::Ident)) { - // Initialize a temporary list initially filled with the current input token. - Vector temporary_list; - temporary_list.append(token); - - // As long as the next input token is anything other than a or , - // consume a component value and append it to the temporary list. - for (;;) { - auto& peek = tokens.next_token(); - if (peek.is(Token::Type::Semicolon) || peek.is(Token::Type::EndOfFile)) - break; - temporary_list.append(consume_a_component_value(tokens)); - } - - // Consume a declaration from the temporary list. If anything was returned, append it to the list of declarations. - auto token_stream = TokenStream(temporary_list); - if (auto maybe_declaration = consume_a_declaration(token_stream); maybe_declaration.has_value()) - list_of_declarations.empend(maybe_declaration.value()); - + // <}-token> + if (token.is(Token::Type::CloseCurly)) { + // If nested is true, return nothing. Otherwise, discard a token. + if (nested == Nested::Yes) + return; + input.discard_a_token(); continue; } // anything else { - // This is a parse error. - log_parse_error(); - - // Reconsume the current input token. - tokens.reconsume_current_input_token(); - - // As long as the next input token is anything other than a or , - // consume a component value and throw away the returned value. - for (;;) { - auto& peek = tokens.next_token(); - if (peek.is(Token::Type::Semicolon) || peek.is(Token::Type::EndOfFile)) - break; - dbgln_if(CSS_PARSER_DEBUG, "Discarding token: '{}'", peek.to_debug_string()); - (void)consume_a_component_value(tokens); - } + // Consume a component value from input, and do nothing. + (void)consume_a_component_value(input); + continue; } } } CSSRule* Parser::parse_as_css_rule() { - auto maybe_rule = parse_a_rule(m_token_stream); - if (maybe_rule) - return convert_to_rule(maybe_rule.release_nonnull()); + if (auto maybe_rule = parse_a_rule(m_token_stream); maybe_rule.has_value()) + return convert_to_rule(maybe_rule.value()); return {}; } -// 5.3.5. Parse a rule -// https://www.w3.org/TR/css-syntax-3/#parse-rule +// https://drafts.csswg.org/css-syntax/#parse-rule template -RefPtr Parser::parse_a_rule(TokenStream& tokens) +Optional Parser::parse_a_rule(TokenStream& input) { // To parse a rule from input: - RefPtr rule; + Optional rule; // 1. Normalize input, and set input to the result. - // Note: This is done when initializing the Parser. + // NOTE: This is done when initializing the Parser. - // 2. While the next input token from input is a , consume the next input token from input. - tokens.discard_whitespace(); + // 2. Discard whitespace from input. + input.discard_whitespace(); - // 3. If the next input token from input is an , return a syntax error. - auto& token = tokens.next_token(); - if (token.is(Token::Type::EndOfFile)) { + // 3. If the next token from input is an , return a syntax error. + if (input.next_token().is(Token::Type::EndOfFile)) { return {}; } - // Otherwise, if the next input token from input is an , consume an at-rule from input, and let rule be the return value. - else if (token.is(Token::Type::AtKeyword)) { - rule = consume_an_at_rule(m_token_stream); + // Otherwise, if the next token from input is an , + // consume an at-rule from input, and let rule be the return value. + else if (input.next_token().is(Token::Type::AtKeyword)) { + rule = consume_an_at_rule(m_token_stream).map([](auto& it) { return Rule { it }; }); } - // Otherwise, consume a qualified rule from input and let rule be the return value. If nothing was returned, return a syntax error. + // Otherwise, consume a qualified rule from input and let rule be the return value. + // If nothing or an invalid rule error was returned, return a syntax error. else { - auto qualified_rule = consume_a_qualified_rule(tokens); - if (!qualified_rule) - return {}; + consume_a_qualified_rule(input).visit( + [&](QualifiedRule qualified_rule) { rule = move(qualified_rule); }, + [](auto&) {}); - rule = qualified_rule; + if (!rule.has_value()) + return {}; } - // 4. While the next input token from input is a , consume the next input token from input. - tokens.discard_whitespace(); + // 4. Discard whitespace from input. + input.discard_whitespace(); - // 5. If the next input token from input is an , return rule. Otherwise, return a syntax error. - if (tokens.next_token().is(Token::Type::EndOfFile)) + // 5. If the next token from input is an , return rule. Otherwise, return a syntax error. + if (input.next_token().is(Token::Type::EndOfFile)) return rule; return {}; } -// 5.3.4. Parse a list of rules -// https://www.w3.org/TR/css-syntax-3/#parse-list-of-rules +// https://drafts.csswg.org/css-syntax/#parse-block-contents template -Vector> Parser::parse_a_list_of_rules(TokenStream& tokens) +Vector Parser::parse_a_blocks_contents(TokenStream& input) { - // To parse a list of rules from input: + // To parse a block’s contents from input: // 1. Normalize input, and set input to the result. - // Note: This is done when initializing the Parser. + // NOTE: Done by constructing the Parser. - // 2. Consume a list of rules from the input, with the top-level flag unset. - auto list_of_rules = consume_a_list_of_rules(tokens, TopLevel::No); - - // 3. Return the returned list. - return list_of_rules; + // 2. Consume a block’s contents from input, and return the result. + return consume_a_blocks_contents(input); } -template Vector> Parser::parse_a_list_of_rules(TokenStream& tokens); -template Vector> Parser::parse_a_list_of_rules(TokenStream& tokens); Optional Parser::parse_as_supports_condition() { @@ -1006,160 +1076,104 @@ Optional Parser::parse_as_supports_condition() return {}; } -// 5.3.6. Parse a declaration -// https://www.w3.org/TR/css-syntax-3/#parse-a-declaration +// https://drafts.csswg.org/css-syntax/#parse-declaration template -Optional Parser::parse_a_declaration(TokenStream& tokens) +Optional Parser::parse_a_declaration(TokenStream& input) { // To parse a declaration from input: // 1. Normalize input, and set input to the result. // Note: This is done when initializing the Parser. - // 2. While the next input token from input is a , consume the next input token. - tokens.discard_whitespace(); + // 2. Discard whitespace from input. + input.discard_whitespace(); - // 3. If the next input token from input is not an , return a syntax error. - auto& token = tokens.next_token(); - if (!token.is(Token::Type::Ident)) { - return {}; - } - - // 4. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error. - if (auto declaration = consume_a_declaration(tokens); declaration.has_value()) + // 3. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error. + if (auto declaration = consume_a_declaration(input); declaration.has_value()) return declaration.release_value(); + // FIXME: Syntax error return {}; } -// 5.3.7. Parse a style block’s contents -// https://www.w3.org/TR/css-syntax-3/#parse-style-blocks-contents -template -Vector Parser::parse_a_style_blocks_contents(TokenStream& tokens) -{ - // To parse a style block’s contents from input: - - // 1. Normalize input, and set input to the result. - // Note: This is done when initializing the Parser. - - // 2. Consume a style block’s contents from input, and return the result. - return consume_a_style_blocks_contents(tokens); -} - -// 5.3.8. Parse a list of declarations -// https://www.w3.org/TR/css-syntax-3/#parse-list-of-declarations -template -Vector Parser::parse_a_list_of_declarations(TokenStream& tokens) -{ - // To parse a list of declarations from input: - - // 1. Normalize input, and set input to the result. - // Note: This is done when initializing the Parser. - - // 2. Consume a list of declarations from input, and return the result. - return consume_a_list_of_declarations(tokens); -} - Optional Parser::parse_as_component_value() { return parse_a_component_value(m_token_stream); } -// 5.3.9. Parse a component value -// https://www.w3.org/TR/css-syntax-3/#parse-component-value +// https://drafts.csswg.org/css-syntax/#parse-component-value template -Optional Parser::parse_a_component_value(TokenStream& tokens) +Optional Parser::parse_a_component_value(TokenStream& input) { // To parse a component value from input: // 1. Normalize input, and set input to the result. // Note: This is done when initializing the Parser. - // 2. While the next input token from input is a , consume the next input token from input. - tokens.discard_whitespace(); + // 2. Discard whitespace from input. + input.discard_whitespace(); - // 3. If the next input token from input is an , return a syntax error. - if (tokens.next_token().is(Token::Type::EndOfFile)) + // 3. If input is empty, return a syntax error. + // FIXME: Syntax error + if (input.is_empty()) return {}; // 4. Consume a component value from input and let value be the return value. - auto value = consume_a_component_value(tokens); + auto value = consume_a_component_value(input); - // 5. While the next input token from input is a , consume the next input token. - tokens.discard_whitespace(); + // 5. Discard whitespace from input. + input.discard_whitespace(); - // 6. If the next input token from input is an , return value. Otherwise, return a syntax error. - if (tokens.next_token().is(Token::Type::EndOfFile)) + // 6. If input is empty, return value. Otherwise, return a syntax error. + if (input.is_empty()) return value; + // FIXME: Syntax error return {}; } -// 5.3.10. Parse a list of component values -// https://www.w3.org/TR/css-syntax-3/#parse-list-of-component-values +// https://drafts.csswg.org/css-syntax/#parse-list-of-component-values template -Vector Parser::parse_a_list_of_component_values(TokenStream& tokens) +Vector Parser::parse_a_list_of_component_values(TokenStream& input) { // To parse a list of component values from input: // 1. Normalize input, and set input to the result. // Note: This is done when initializing the Parser. - // 2. Repeatedly consume a component value from input until an is returned, appending the returned values (except the final ) into a list. Return the list. - Vector component_values; - - for (;;) { - if (tokens.next_token().is(Token::Type::EndOfFile)) { - break; - } - - component_values.append(consume_a_component_value(tokens)); - } - - return component_values; + // 2. Consume a list of component values from input, and return the result. + return consume_a_list_of_component_values(input); } -// 5.3.11. Parse a comma-separated list of component values -// https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values +// https://drafts.csswg.org/css-syntax/#parse-comma-separated-list-of-component-values template -Vector> Parser::parse_a_comma_separated_list_of_component_values(TokenStream& tokens) +Vector> Parser::parse_a_comma_separated_list_of_component_values(TokenStream& input) { // To parse a comma-separated list of component values from input: // 1. Normalize input, and set input to the result. // Note: This is done when initializing the Parser. - // 2. Let list of cvls be an initially empty list of component value lists. - Vector> list_of_component_value_lists; + // 2. Let groups be an empty list. + Vector> groups; - // 3. Repeatedly consume a component value from input until an or is returned, - // appending the returned values (except the final or ) into a list. - // Append the list to list of cvls. - // If it was a that was returned, repeat this step. - Vector current_list; - for (;;) { - auto component_value = consume_a_component_value(tokens); + // 3. While input is not empty: + while (!input.is_empty()) { - if (component_value.is(Token::Type::EndOfFile)) { - list_of_component_value_lists.append(move(current_list)); - break; - } - if (component_value.is(Token::Type::Comma)) { - list_of_component_value_lists.append(move(current_list)); - current_list = {}; - continue; - } + // 1. Consume a list of component values from input, with as the stop token, and append the result to groups. + groups.append(consume_a_list_of_component_values(input, Token::Type::Comma)); - current_list.append(component_value); + // 2. Discard a token from input. + input.discard_a_token(); } - // 4. Return list of cvls. - return list_of_component_value_lists; + // 4. Return groups. + return groups; } template Vector> Parser::parse_a_comma_separated_list_of_component_values(TokenStream&); template Vector> Parser::parse_a_comma_separated_list_of_component_values(TokenStream&); ElementInlineCSSStyleDeclaration* Parser::parse_as_style_attribute(DOM::Element& element) { - auto declarations_and_at_rules = parse_a_list_of_declarations(m_token_stream); + auto declarations_and_at_rules = parse_a_blocks_contents(m_token_stream); auto [properties, custom_properties] = extract_properties(declarations_and_at_rules); return ElementInlineCSSStyleDeclaration::create(element, move(properties), move(custom_properties)); } @@ -1183,7 +1197,7 @@ Optional Parser::parse_url_function(TokenStream& token return convert_string_to_url(url_string); } if (component_value.is_function("url"sv)) { - auto const& function_values = component_value.function().values(); + auto const& function_values = component_value.function().value; // FIXME: Handle url-modifiers. https://www.w3.org/TR/css-values-4/#url-modifiers for (size_t i = 0; i < function_values.size(); ++i) { auto const& value = function_values[i]; @@ -1215,7 +1229,7 @@ RefPtr Parser::parse_basic_shape_value(TokenStream Parser::parse_basic_shape_value(TokenStream? , [ ]# ) // FIXME: Parse the fill-rule. - auto arguments_tokens = TokenStream { component_value.function().values() }; + auto arguments_tokens = TokenStream { component_value.function().value }; auto arguments = parse_a_comma_separated_list_of_component_values(arguments_tokens); Vector points; @@ -1291,80 +1305,89 @@ Optional Parser::parse_layer_name(TokenStream& tokens return builder.to_fly_string_without_validation(); } -JS::GCPtr Parser::convert_to_rule(NonnullRefPtr rule) +bool Parser::is_valid_in_the_current_context(Declaration&) { - if (rule->is_at_rule()) { - if (has_ignored_vendor_prefix(rule->at_rule_name())) - return {}; - if (rule->at_rule_name().equals_ignoring_ascii_case("font-face"sv)) { - if (!rule->block() || !rule->block()->is_curly()) { - dbgln_if(CSS_PARSER_DEBUG, "@font-face rule is malformed."); - return {}; - } - TokenStream tokens { rule->block()->values() }; - return parse_font_face_rule(tokens); - } - - if (rule->at_rule_name().equals_ignoring_ascii_case("import"sv)) - return convert_to_import_rule(rule); - - if (rule->at_rule_name().equals_ignoring_ascii_case("keyframes"sv)) - return convert_to_keyframes_rule(rule); - - if (rule->at_rule_name().equals_ignoring_ascii_case("layer"sv)) - return convert_to_layer_rule(rule); - - if (rule->at_rule_name().equals_ignoring_ascii_case("media"sv)) - return convert_to_media_rule(rule); - - if (rule->at_rule_name().equals_ignoring_ascii_case("namespace"sv)) - return convert_to_namespace_rule(rule); - - if (rule->at_rule_name().equals_ignoring_ascii_case("supports"sv)) - return convert_to_supports_rule(rule); - - // FIXME: More at rules! - dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", rule->at_rule_name()); - return {}; - } - - auto prelude_stream = TokenStream(rule->prelude()); - auto selectors = parse_a_selector_list(prelude_stream, SelectorType::Standalone); - - if (selectors.is_error()) { - if (selectors.error() == ParseError::SyntaxError) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule selectors invalid; discarding."); - if constexpr (CSS_PARSER_DEBUG) { - prelude_stream.dump_all_tokens(); - } - } - return {}; - } - - if (selectors.value().is_empty()) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: empty selector; discarding."); - return {}; - } - - if (!rule->block()->is_curly()) - return {}; - - auto stream = TokenStream(rule->block()->values()); - auto declarations_and_at_rules = parse_a_style_blocks_contents(stream); - - auto* declaration = convert_to_style_declaration(declarations_and_at_rules); - if (!declaration) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule declaration invalid; discarding."); - return {}; - } - - // TODO: Implement this properly - JS::MarkedVector child_rules(m_context.realm().heap()); - auto nested_rules = CSSRuleList::create(m_context.realm(), move(child_rules)); - return CSSStyleRule::create(m_context.realm(), move(selectors.value()), *declaration, *nested_rules); + // FIXME: Implement this check + return true; } -JS::GCPtr Parser::convert_to_import_rule(Rule& rule) +bool Parser::is_valid_in_the_current_context(AtRule&) +{ + // FIXME: Implement this check + return true; +} + +bool Parser::is_valid_in_the_current_context(QualifiedRule&) +{ + // FIXME: Implement this check + return true; +} + +JS::GCPtr Parser::convert_to_rule(Rule const& rule) +{ + return rule.visit( + [this](AtRule const& at_rule) -> JS::GCPtr { + if (has_ignored_vendor_prefix(at_rule.name)) + return {}; + + if (at_rule.name.equals_ignoring_ascii_case("font-face"sv)) + return convert_to_font_face_rule(at_rule); + + if (at_rule.name.equals_ignoring_ascii_case("import"sv)) + return convert_to_import_rule(at_rule); + + if (at_rule.name.equals_ignoring_ascii_case("keyframes"sv)) + return convert_to_keyframes_rule(at_rule); + + if (at_rule.name.equals_ignoring_ascii_case("layer"sv)) + return convert_to_layer_rule(at_rule); + + if (at_rule.name.equals_ignoring_ascii_case("media"sv)) + return convert_to_media_rule(at_rule); + + if (at_rule.name.equals_ignoring_ascii_case("namespace"sv)) + return convert_to_namespace_rule(at_rule); + + if (at_rule.name.equals_ignoring_ascii_case("supports"sv)) + return convert_to_supports_rule(at_rule); + + // FIXME: More at rules! + dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", at_rule.name); + return {}; + }, + [this](QualifiedRule const& qualified_rule) -> JS::GCPtr { + TokenStream prelude_stream { qualified_rule.prelude }; + auto selectors = parse_a_selector_list(prelude_stream, SelectorType::Standalone); + + if (selectors.is_error()) { + if (selectors.error() == ParseError::SyntaxError) { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule selectors invalid; discarding."); + if constexpr (CSS_PARSER_DEBUG) { + prelude_stream.dump_all_tokens(); + } + } + return {}; + } + + if (selectors.value().is_empty()) { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: empty selector; discarding."); + return {}; + } + + auto* declaration = convert_to_style_declaration(qualified_rule.declarations); + if (!declaration) { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule declaration invalid; discarding."); + return {}; + } + + // TODO: Implement this properly + JS::MarkedVector child_rules { m_context.realm().heap() }; + auto nested_rules = CSSRuleList::create(m_context.realm(), move(child_rules)); + return CSSStyleRule::create(m_context.realm(), move(selectors.value()), *declaration, *nested_rules); + }); +} + +JS::GCPtr Parser::convert_to_import_rule(AtRule const& rule) { // https://drafts.csswg.org/css-cascade-5/#at-import // @import [ | ] @@ -1374,17 +1397,17 @@ JS::GCPtr Parser::convert_to_import_rule(Rule& rule) // = [ supports( [ | ] ) ]? // ? - if (rule.prelude().is_empty()) { + if (rule.prelude.is_empty()) { dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Empty prelude."); return {}; } - if (rule.block()) { + if (!rule.child_rules_and_lists_of_declarations.is_empty()) { dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Block is not allowed."); return {}; } - TokenStream tokens { rule.prelude() }; + TokenStream tokens { rule.prelude }; tokens.discard_whitespace(); Optional url = parse_url_function(tokens); @@ -1409,10 +1432,10 @@ JS::GCPtr Parser::convert_to_import_rule(Rule& rule) return CSSImportRule::create(url.value(), const_cast(*m_context.document())); } -JS::GCPtr Parser::convert_to_layer_rule(Rule& rule) +JS::GCPtr Parser::convert_to_layer_rule(AtRule const& rule) { // https://drafts.csswg.org/css-cascade-5/#at-layer - if (rule.block()) { + if (!rule.child_rules_and_lists_of_declarations.is_empty()) { // CSSLayerBlockRule // @layer ? { // @@ -1420,42 +1443,40 @@ JS::GCPtr Parser::convert_to_layer_rule(Rule& rule) // First, the name FlyString layer_name = {}; - auto prelude_tokens = TokenStream { rule.prelude() }; + auto prelude_tokens = TokenStream { rule.prelude }; if (auto maybe_name = parse_layer_name(prelude_tokens, AllowBlankLayerName::Yes); maybe_name.has_value()) { layer_name = maybe_name.release_value(); } else { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer has invalid prelude, (not a valid layer name) prelude = {}; discarding.", rule.prelude()); + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer has invalid prelude, (not a valid layer name) prelude = {}; discarding.", rule.prelude); return {}; } prelude_tokens.discard_whitespace(); if (prelude_tokens.has_next_token()) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer has invalid prelude, (tokens after layer name) prelude = {}; discarding.", rule.prelude()); + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer has invalid prelude, (tokens after layer name) prelude = {}; discarding.", rule.prelude); return {}; } // Then the rules - auto child_tokens = TokenStream { rule.block()->values() }; - auto parser_rules = parse_a_list_of_rules(child_tokens); - JS::MarkedVector child_rules(m_context.realm().heap()); - for (auto& raw_rule : parser_rules) { - if (auto child_rule = convert_to_rule(raw_rule)) + JS::MarkedVector child_rules { m_context.realm().heap() }; + rule.for_each_as_rule_list([&](auto& rule) { + if (auto child_rule = convert_to_rule(rule)) child_rules.append(child_rule); - } + }); auto rule_list = CSSRuleList::create(m_context.realm(), child_rules); return CSSLayerBlockRule::create(m_context.realm(), layer_name, rule_list); } // CSSLayerStatementRule // @layer #; - auto tokens = TokenStream { rule.prelude() }; + auto tokens = TokenStream { rule.prelude }; tokens.discard_whitespace(); Vector layer_names; while (tokens.has_next_token()) { // Comma if (!layer_names.is_empty()) { if (auto comma = tokens.consume_a_token(); !comma.is(Token::Type::Comma)) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer missing separating comma, ({}) prelude = {}; discarding.", comma.to_debug_string(), rule.prelude()); + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer missing separating comma, ({}) prelude = {}; discarding.", comma.to_debug_string(), rule.prelude); return {}; } tokens.discard_whitespace(); @@ -1464,39 +1485,40 @@ JS::GCPtr Parser::convert_to_layer_rule(Rule& rule) if (auto name = parse_layer_name(tokens, AllowBlankLayerName::No); name.has_value()) { layer_names.append(name.release_value()); } else { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer contains invalid name, prelude = {}; discarding.", rule.prelude()); + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer contains invalid name, prelude = {}; discarding.", rule.prelude); return {}; } tokens.discard_whitespace(); } if (layer_names.is_empty()) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer statement has no layer names, prelude = {}; discarding.", rule.prelude()); + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer statement has no layer names, prelude = {}; discarding.", rule.prelude); return {}; } return CSSLayerStatementRule::create(m_context.realm(), move(layer_names)); } -JS::GCPtr Parser::convert_to_keyframes_rule(Rule& rule) +JS::GCPtr Parser::convert_to_keyframes_rule(AtRule const& rule) { - // https://www.w3.org/TR/css-animations-1/#keyframes + // https://drafts.csswg.org/css-animations/#keyframes + // @keyframes = @keyframes { } + // = | + // = # { } + // = from | to | - if (rule.prelude().is_empty()) { + if (rule.prelude.is_empty()) { dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @keyframes rule: Empty prelude."); return {}; } - if (!rule.block()) { - dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @keyframes rule: No block."); - return {}; - } + // FIXME: Is there some way of detecting if there is a block or not? - auto prelude_stream = TokenStream { rule.prelude() }; + auto prelude_stream = TokenStream { rule.prelude }; prelude_stream.discard_whitespace(); auto& token = prelude_stream.consume_a_token(); if (!token.is_token()) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule.prelude()); + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule.prelude); return {}; } @@ -1504,7 +1526,7 @@ JS::GCPtr Parser::convert_to_keyframes_rule(Rule& rule) prelude_stream.discard_whitespace(); if (prelude_stream.has_next_token()) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule.prelude()); + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule.prelude); return {}; } @@ -1520,18 +1542,14 @@ JS::GCPtr Parser::convert_to_keyframes_rule(Rule& rule) auto name = name_token.to_string(); - auto child_tokens = TokenStream { rule.block()->values() }; - JS::MarkedVector keyframes(m_context.realm().heap()); - while (child_tokens.has_next_token()) { - child_tokens.discard_whitespace(); - // keyframe-selector = | - // keyframe-keyword = "from" | "to" - // selector = # - // keyframes-block = "{" ? "}" - // keyframe-rule = + rule.for_each_as_qualified_rule_list([&](auto& qualified_rule) { + if (!qualified_rule.child_rules.is_empty()) { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes keyframe rule contains at-rules; discarding them."); + } auto selectors = Vector {}; + TokenStream child_tokens { qualified_rule.prelude }; while (child_tokens.has_next_token()) { child_tokens.discard_whitespace(); if (!child_tokens.has_next_token()) @@ -1568,46 +1586,37 @@ JS::GCPtr Parser::convert_to_keyframes_rule(Rule& rule) break; } - if (!child_tokens.has_next_token()) - break; - - child_tokens.discard_whitespace(); - auto token = child_tokens.consume_a_token(); - if (token.is_block()) { - auto block_tokens = token.block().values(); - auto block_stream = TokenStream { block_tokens }; - - auto block_declarations = parse_a_list_of_declarations(block_stream); - auto style = convert_to_style_declaration(block_declarations); - for (auto& selector : selectors) { - auto keyframe_rule = CSSKeyframeRule::create(m_context.realm(), selector, *style); - keyframes.append(keyframe_rule); - } - } else { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule has invalid block: {}; discarding.", token.to_debug_string()); + PropertiesAndCustomProperties properties; + qualified_rule.for_each_as_declaration_list([&](auto const& declaration) { + extract_property(declaration, properties); + }); + auto style = PropertyOwningCSSStyleDeclaration::create(m_context.realm(), move(properties.properties), move(properties.custom_properties)); + for (auto& selector : selectors) { + auto keyframe_rule = CSSKeyframeRule::create(m_context.realm(), selector, *style); + keyframes.append(keyframe_rule); } - } + }); return CSSKeyframesRule::create(m_context.realm(), name, CSSRuleList::create(m_context.realm(), move(keyframes))); } -JS::GCPtr Parser::convert_to_namespace_rule(Rule& rule) +JS::GCPtr Parser::convert_to_namespace_rule(AtRule const& rule) { // https://drafts.csswg.org/css-namespaces/#syntax // @namespace ? [ | ] ; // = - if (rule.prelude().is_empty()) { + if (rule.prelude.is_empty()) { dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Empty prelude."); return {}; } - if (rule.block()) { + if (!rule.child_rules_and_lists_of_declarations.is_empty()) { dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Block is not allowed."); return {}; } - auto tokens = TokenStream { rule.prelude() }; + auto tokens = TokenStream { rule.prelude }; tokens.discard_whitespace(); Optional prefix = {}; @@ -1638,24 +1647,19 @@ JS::GCPtr Parser::convert_to_namespace_rule(Rule& rule) return CSSNamespaceRule::create(m_context.realm(), prefix, namespace_uri); } -JS::GCPtr Parser::convert_to_supports_rule(Rule& rule) +JS::GCPtr Parser::convert_to_supports_rule(AtRule const& rule) { // https://drafts.csswg.org/css-conditional-3/#at-supports // @supports { // // } - if (rule.prelude().is_empty()) { + if (rule.prelude.is_empty()) { dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @supports rule: Empty prelude."); return {}; } - if (!rule.block()) { - dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @supports rule: No block."); - return {}; - } - - auto supports_tokens = TokenStream { rule.prelude() }; + auto supports_tokens = TokenStream { rule.prelude }; auto supports = parse_a_supports(supports_tokens); if (!supports) { if constexpr (CSS_PARSER_DEBUG) { @@ -1665,50 +1669,57 @@ JS::GCPtr Parser::convert_to_supports_rule(Rule& rule) return {}; } - auto child_tokens = TokenStream { rule.block()->values() }; - auto parser_rules = parse_a_list_of_rules(child_tokens); JS::MarkedVector child_rules { m_context.realm().heap() }; - for (auto& raw_rule : parser_rules) { - if (auto child_rule = convert_to_rule(raw_rule)) + rule.for_each_as_rule_list([&](auto& rule) { + if (auto child_rule = convert_to_rule(rule)) child_rules.append(child_rule); - } + }); auto rule_list = CSSRuleList::create(m_context.realm(), child_rules); return CSSSupportsRule::create(m_context.realm(), supports.release_nonnull(), rule_list); } -auto Parser::extract_properties(Vector const& declarations_and_at_rules) -> PropertiesAndCustomProperties +Parser::PropertiesAndCustomProperties Parser::extract_properties(Vector const& rules_and_lists_of_declarations) { PropertiesAndCustomProperties result; - for (auto const& declaration_or_at_rule : declarations_and_at_rules) { - if (declaration_or_at_rule.is_at_rule()) { - dbgln_if(CSS_PARSER_DEBUG, "!!! CSS at-rule is not allowed here!"); + for (auto const& rule_or_list : rules_and_lists_of_declarations) { + if (rule_or_list.has()) continue; - } - auto const& declaration = declaration_or_at_rule.declaration(); - - if (auto maybe_property = convert_to_style_property(declaration); maybe_property.has_value()) { - auto property = maybe_property.release_value(); - if (property.property_id == PropertyID::Custom) { - result.custom_properties.set(property.custom_name, property); - } else { - result.properties.append(move(property)); - } + auto& declarations = rule_or_list.get>(); + PropertiesAndCustomProperties& dest = result; + for (auto const& declaration : declarations) { + extract_property(declaration, dest); } } return result; } -PropertyOwningCSSStyleDeclaration* Parser::convert_to_style_declaration(Vector const& declarations_and_at_rules) +void Parser::extract_property(Declaration const& declaration, PropertiesAndCustomProperties& dest) { - auto [properties, custom_properties] = extract_properties(declarations_and_at_rules); - return PropertyOwningCSSStyleDeclaration::create(m_context.realm(), move(properties), move(custom_properties)); + if (auto maybe_property = convert_to_style_property(declaration); maybe_property.has_value()) { + auto property = maybe_property.release_value(); + if (property.property_id == PropertyID::Custom) { + dest.custom_properties.set(property.custom_name, property); + } else { + dest.properties.append(move(property)); + } + } +} + +PropertyOwningCSSStyleDeclaration* Parser::convert_to_style_declaration(Vector const& declarations) +{ + PropertiesAndCustomProperties properties; + PropertiesAndCustomProperties& dest = properties; + for (auto const& declaration : declarations) { + extract_property(declaration, dest); + } + return PropertyOwningCSSStyleDeclaration::create(m_context.realm(), move(properties.properties), move(properties.custom_properties)); } Optional Parser::convert_to_style_property(Declaration const& declaration) { - auto const& property_name = declaration.name(); + auto const& property_name = declaration.name; auto property_id = property_id_from_string(property_name); if (!property_id.has_value()) { @@ -1722,7 +1733,7 @@ Optional Parser::convert_to_style_property(Declaration const& dec } } - auto value_token_stream = TokenStream(declaration.values()); + auto value_token_stream = TokenStream(declaration.value); auto value = parse_css_value(property_id.value(), value_token_stream); if (value.is_error()) { if (value.error() == ParseError::SyntaxError) { @@ -1735,9 +1746,9 @@ Optional Parser::convert_to_style_property(Declaration const& dec } if (property_id.value() == PropertyID::Custom) - return StyleProperty { declaration.importance(), property_id.value(), value.release_value(), declaration.name() }; + return StyleProperty { declaration.important, property_id.value(), value.release_value(), declaration.name }; - return StyleProperty { declaration.importance(), property_id.value(), value.release_value(), {} }; + return StyleProperty { declaration.important, property_id.value(), value.release_value(), {} }; } RefPtr Parser::parse_builtin_value(TokenStream& tokens) @@ -1822,8 +1833,8 @@ RefPtr Parser::parse_calculated_value(ComponentValue const& compon OwnPtr Parser::parse_a_calc_function_node(Function const& function) { - if (function.name().equals_ignoring_ascii_case("calc"sv)) - return parse_a_calculation(function.values()); + if (function.name.equals_ignoring_ascii_case("calc"sv)) + return parse_a_calculation(function.value); if (auto maybe_function = parse_math_function(m_context.current_property_id(), function)) return maybe_function; @@ -2682,7 +2693,7 @@ RefPtr Parser::parse_rect_value(TokenStream& toke return nullptr; Vector params; - auto argument_tokens = TokenStream { function_token.function().values() }; + auto argument_tokens = TokenStream { function_token.function().value }; enum class CommaRequirement { Unknown, @@ -2815,7 +2826,7 @@ RefPtr Parser::parse_rgb_color_value(TokenStream& RefPtr blue; RefPtr alpha; - auto inner_tokens = TokenStream { function_token.function().values() }; + auto inner_tokens = TokenStream { function_token.function().value }; inner_tokens.discard_whitespace(); red = parse_number_percentage_value(inner_tokens); @@ -2934,7 +2945,7 @@ RefPtr Parser::parse_hsl_color_value(TokenStream& RefPtr l; RefPtr alpha; - auto inner_tokens = TokenStream { function_token.function().values() }; + auto inner_tokens = TokenStream { function_token.function().value }; inner_tokens.discard_whitespace(); h = parse_hue_value(inner_tokens); @@ -3027,7 +3038,7 @@ RefPtr Parser::parse_hwb_color_value(TokenStream& RefPtr b; RefPtr alpha; - auto inner_tokens = TokenStream { function_token.function().values() }; + auto inner_tokens = TokenStream { function_token.function().value }; inner_tokens.discard_whitespace(); h = parse_hue_value(inner_tokens); @@ -3078,7 +3089,7 @@ RefPtr Parser::parse_oklab_color_value(TokenStream b; RefPtr alpha; - auto inner_tokens = TokenStream { function_token.function().values() }; + auto inner_tokens = TokenStream { function_token.function().value }; inner_tokens.discard_whitespace(); l = parse_number_percentage_value(inner_tokens); @@ -3129,7 +3140,7 @@ RefPtr Parser::parse_oklch_color_value(TokenStream h; RefPtr alpha; - auto inner_tokens = TokenStream { function_token.function().values() }; + auto inner_tokens = TokenStream { function_token.function().value }; inner_tokens.discard_whitespace(); l = parse_number_percentage_value(inner_tokens); @@ -3324,7 +3335,7 @@ RefPtr Parser::parse_counter_value(TokenStream& t if (token.is_function("counter"sv)) { // counter() = counter( , ? ) auto& function = token.function(); - TokenStream function_tokens { function.values() }; + TokenStream function_tokens { function.value }; auto function_values = parse_a_comma_separated_list_of_component_values(function_tokens); if (function_values.is_empty() || function_values.size() > 2) return nullptr; @@ -3352,7 +3363,7 @@ RefPtr Parser::parse_counter_value(TokenStream& t if (token.is_function("counters"sv)) { // counters() = counters( , , ? ) auto& function = token.function(); - TokenStream function_tokens { function.values() }; + TokenStream function_tokens { function.value }; auto function_values = parse_a_comma_separated_list_of_component_values(function_tokens); if (function_values.size() < 2 || function_values.size() > 3) return nullptr; @@ -3411,7 +3422,7 @@ RefPtr Parser::parse_counter_definitions_value(TokenStream Parser::parse_filter_value_list_value(TokenStream Parser::parse_font_variation_settings_value(TokenStream Parser::parse_font_face_rule(TokenStream& tokens) +JS::GCPtr Parser::convert_to_font_face_rule(AtRule const& rule) { - auto declarations_and_at_rules = parse_a_list_of_declarations(tokens); + // https://drafts.csswg.org/css-fonts/#font-face-rule Optional font_family; Optional font_named_instance; @@ -5661,33 +5672,27 @@ JS::GCPtr Parser::parse_font_face_rule(TokenStream Parser::parse_font_face_rule(TokenStream font_family_parts; bool had_syntax_error = false; - for (size_t i = 0; i < declaration.values().size(); ++i) { - auto const& part = declaration.values()[i]; + for (size_t i = 0; i < declaration.value.size(); ++i) { + auto const& part = declaration.value[i]; if (part.is(Token::Type::Whitespace)) continue; if (part.is(Token::Type::String)) { @@ -5742,13 +5747,13 @@ JS::GCPtr Parser::parse_font_face_rule(TokenStreamto_keyword() == Keyword::Normal) { font_feature_settings.clear(); @@ -5779,10 +5784,10 @@ JS::GCPtr Parser::parse_font_face_rule(TokenStreamto_string()); } } - continue; + return; } - if (declaration.name().equals_ignoring_ascii_case("font-language-override"sv)) { - TokenStream token_stream { declaration.values() }; + if (declaration.name.equals_ignoring_ascii_case("font-language-override"sv)) { + TokenStream token_stream { declaration.value }; if (auto maybe_value = parse_css_value(CSS::PropertyID::FontLanguageOverride, token_stream); !maybe_value.is_error()) { auto& value = maybe_value.value(); if (value->is_string()) { @@ -5791,17 +5796,17 @@ JS::GCPtr Parser::parse_font_face_rule(TokenStream - TokenStream token_stream { declaration.values() }; + TokenStream token_stream { declaration.value }; token_stream.discard_whitespace(); auto& token = token_stream.consume_a_token(); token_stream.discard_whitespace(); if (token_stream.has_next_token()) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in font-named-instance"); - continue; + return; } if (token.is_ident("auto"sv)) { @@ -5812,17 +5817,17 @@ JS::GCPtr Parser::parse_font_face_rule(TokenStreamto_font_slope(); } - continue; + return; } - if (declaration.name().equals_ignoring_ascii_case("font-variation-settings"sv)) { - TokenStream token_stream { declaration.values() }; + if (declaration.name.equals_ignoring_ascii_case("font-variation-settings"sv)) { + TokenStream token_stream { declaration.value }; if (auto value = parse_css_value(CSS::PropertyID::FontVariationSettings, token_stream); !value.is_error()) { if (value.value()->to_keyword() == Keyword::Normal) { font_variation_settings.clear(); @@ -5853,51 +5858,51 @@ JS::GCPtr Parser::parse_font_face_rule(TokenStreamto_string()); } } - continue; + return; } - if (declaration.name().equals_ignoring_ascii_case("font-weight"sv)) { - TokenStream token_stream { declaration.values() }; + if (declaration.name.equals_ignoring_ascii_case("font-weight"sv)) { + TokenStream token_stream { declaration.value }; if (auto value = parse_css_value(CSS::PropertyID::FontWeight, token_stream); !value.is_error()) { weight = value.value()->to_font_weight(); } - continue; + return; } - if (declaration.name().equals_ignoring_ascii_case("font-width"sv) - || declaration.name().equals_ignoring_ascii_case("font-stretch"sv)) { - TokenStream token_stream { declaration.values() }; + if (declaration.name.equals_ignoring_ascii_case("font-width"sv) + || declaration.name.equals_ignoring_ascii_case("font-stretch"sv)) { + TokenStream token_stream { declaration.value }; if (auto value = parse_css_value(CSS::PropertyID::FontWidth, token_stream); !value.is_error()) { width = value.value()->to_font_width(); } - continue; + return; } - if (declaration.name().equals_ignoring_ascii_case("line-gap-override"sv)) { - auto value = parse_as_percentage_or_normal(declaration.values()); + if (declaration.name.equals_ignoring_ascii_case("line-gap-override"sv)) { + auto value = parse_as_percentage_or_normal(declaration.value); if (value.is_error()) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face line-gap-override: {}", value.error()); } else { line_gap_override = value.release_value(); } - continue; + return; } - if (declaration.name().equals_ignoring_ascii_case("src"sv)) { - TokenStream token_stream { declaration.values() }; + if (declaration.name.equals_ignoring_ascii_case("src"sv)) { + TokenStream token_stream { declaration.value }; Vector supported_sources = parse_font_face_src(token_stream); if (!supported_sources.is_empty()) src = move(supported_sources); - continue; + return; } - if (declaration.name().equals_ignoring_ascii_case("unicode-range"sv)) { - TokenStream token_stream { declaration.values() }; + if (declaration.name.equals_ignoring_ascii_case("unicode-range"sv)) { + TokenStream token_stream { declaration.value }; auto unicode_ranges = parse_unicode_ranges(token_stream); if (unicode_ranges.is_empty()) - continue; + return; unicode_range = move(unicode_ranges); - continue; + return; } - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unrecognized descriptor '{}' in @font-face; discarding.", declaration.name()); - } + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unrecognized descriptor '{}' in @font-face; discarding.", declaration.name); + }); if (!font_family.has_value()) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face: no font-family!"); @@ -5958,8 +5963,8 @@ Vector Parser::parse_font_face_src(TokenStream& compo } auto const& function = maybe_function.function(); - if (function.name().equals_ignoring_ascii_case("format"sv)) { - TokenStream format_tokens { function.values() }; + if (function.name.equals_ignoring_ascii_case("format"sv)) { + TokenStream format_tokens { function.value }; format_tokens.discard_whitespace(); auto const& format_name_token = format_tokens.consume_a_token(); StringView format_name; @@ -5979,7 +5984,7 @@ Vector Parser::parse_font_face_src(TokenStream& compo format = FlyString::from_utf8(format_name).release_value_but_fixme_should_propagate_errors(); } else { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (unrecognized function token `{}`); discarding.", function.name()); + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (unrecognized function token `{}`); discarding.", function.name); return {}; } @@ -5995,10 +6000,10 @@ Vector Parser::parse_font_face_src(TokenStream& compo auto const& first = source_tokens.consume_a_token(); if (first.is_function("local"sv)) { - if (first.function().values().is_empty()) { + if (first.function().value.is_empty()) { continue; } - supported_sources.empend(first.function().values().first().to_string(), Optional {}); + supported_sources.empend(first.function().value.first().to_string(), Optional {}); continue; } @@ -6113,7 +6118,7 @@ RefPtr Parser::parse_math_depth_value(TokenStream // add() if (token.is_function("add"sv)) { - auto add_tokens = TokenStream { token.function().values() }; + auto add_tokens = TokenStream { token.function().value }; add_tokens.discard_whitespace(); auto integer_token = add_tokens.consume_a_token(); add_tokens.discard_whitespace(); @@ -6390,14 +6395,14 @@ RefPtr Parser::parse_easing_value(TokenStream& to if (!part.is_function()) return nullptr; - TokenStream argument_tokens { part.function().values() }; + TokenStream argument_tokens { part.function().value }; auto comma_separated_arguments = parse_a_comma_separated_list_of_component_values(argument_tokens); // Remove whitespace for (auto& argument : comma_separated_arguments) argument.remove_all_matching([](auto& value) { return value.is(Token::Type::Whitespace); }); - auto name = part.function().name(); + auto name = part.function().name; if (name == "linear"sv) { Vector stops; for (auto const& argument : comma_separated_arguments) { @@ -6540,17 +6545,17 @@ RefPtr Parser::parse_transform_value(TokenStream& auto const& part = tokens.consume_a_token(); if (!part.is_function()) return nullptr; - auto maybe_function = transform_function_from_string(part.function().name()); + auto maybe_function = transform_function_from_string(part.function().name); if (!maybe_function.has_value()) return nullptr; auto function = maybe_function.release_value(); auto function_metadata = transform_function_metadata(function); - auto function_tokens = TokenStream { part.function().values() }; + auto function_tokens = TokenStream { part.function().value }; auto arguments = parse_a_comma_separated_list_of_component_values(function_tokens); if (arguments.size() > function_metadata.parameters.size()) { - dbgln_if(CSS_PARSER_DEBUG, "Too many arguments to {}. max: {}", part.function().name(), function_metadata.parameters.size()); + dbgln_if(CSS_PARSER_DEBUG, "Too many arguments to {}. max: {}", part.function().name, function_metadata.parameters.size()); return nullptr; } @@ -6989,7 +6994,7 @@ Optional Parser::parse_repeat(Vector const& com last_object_was_line_names = true; if (!token.block().is_square()) return {}; - TokenStream block_tokens { token.block().values() }; + TokenStream block_tokens { token.block().value }; while (block_tokens.has_next_token()) { auto current_block_token = block_tokens.consume_a_token(); line_names.append(current_block_token.token().ident().to_string()); @@ -7054,20 +7059,20 @@ Optional Parser::parse_track_sizing_function(ComponentVa { if (token.is_function()) { auto const& function_token = token.function(); - if (function_token.name().equals_ignoring_ascii_case("repeat"sv)) { - auto maybe_repeat = parse_repeat(function_token.values()); + if (function_token.name.equals_ignoring_ascii_case("repeat"sv)) { + auto maybe_repeat = parse_repeat(function_token.value); if (maybe_repeat.has_value()) return CSS::ExplicitGridTrack(maybe_repeat.value()); else return {}; - } else if (function_token.name().equals_ignoring_ascii_case("minmax"sv)) { - auto maybe_min_max_value = parse_min_max(function_token.values()); + } else if (function_token.name.equals_ignoring_ascii_case("minmax"sv)) { + auto maybe_min_max_value = parse_min_max(function_token.value); if (maybe_min_max_value.has_value()) return CSS::ExplicitGridTrack(maybe_min_max_value.value()); else return {}; - } else if (function_token.name().equals_ignoring_ascii_case("fit-content"sv)) { - auto maybe_fit_content_value = parse_fit_content(function_token.values()); + } else if (function_token.name.equals_ignoring_ascii_case("fit-content"sv)) { + auto maybe_fit_content_value = parse_fit_content(function_token.value); if (maybe_fit_content_value.has_value()) return CSS::ExplicitGridTrack(maybe_fit_content_value.value()); return {}; @@ -7109,7 +7114,7 @@ RefPtr Parser::parse_grid_track_size_list(TokenStream Parser::parse_grid_template_areas_value(TokenStream Parser::parse_a_calculation(Vector const // 1. If leaf is a parenthesized simple block, replace leaf with the result of parsing a calculation from leaf’s contents. if (component_value.is_block() && component_value.block().is_paren()) { - auto leaf_calculation = parse_a_calculation(component_value.block().values()); + auto leaf_calculation = parse_a_calculation(component_value.block().value); if (!leaf_calculation) { parsing_failed_for_child_node = true; return; @@ -8730,29 +8735,27 @@ bool Parser::expand_variables(DOM::Element& element, Optional block_values; - TokenStream source_block_contents { source_block.values() }; + TokenStream source_block_contents { source_block.value }; if (!expand_variables(element, pseudo_element, property_name, dependencies, source_block_contents, block_values)) return false; - NonnullRefPtr block = Block::create(source_block.token(), move(block_values)); - dest.empend(block); + dest.empend(SimpleBlock { source_block.token, move(block_values) }); continue; } if (!value.is_function()) { dest.empend(value); continue; } - if (!value.function().name().equals_ignoring_ascii_case("var"sv)) { + if (!value.function().name.equals_ignoring_ascii_case("var"sv)) { auto const& source_function = value.function(); Vector function_values; - TokenStream source_function_contents { source_function.values() }; + TokenStream source_function_contents { source_function.value }; if (!expand_variables(element, pseudo_element, property_name, dependencies, source_function_contents, function_values)) return false; - NonnullRefPtr function = Function::create(source_function.name(), move(function_values)); - dest.empend(function); + dest.empend(Function { source_function.name, move(function_values) }); continue; } - TokenStream var_contents { value.function().values() }; + TokenStream var_contents { value.function().value }; var_contents.discard_whitespace(); if (!var_contents.has_next_token()) return false; @@ -8802,7 +8805,7 @@ bool Parser::expand_unresolved_values(DOM::Element& element, FlyString const& pr while (source.has_next_token()) { auto const& value = source.consume_a_token(); if (value.is_function()) { - if (value.function().name().equals_ignoring_ascii_case("attr"sv)) { + if (value.function().name.equals_ignoring_ascii_case("attr"sv)) { if (!substitute_attr_function(element, property_name, value.function(), dest)) return false; continue; @@ -8844,21 +8847,19 @@ bool Parser::expand_unresolved_values(DOM::Element& element, FlyString const& pr auto const& source_function = value.function(); Vector function_values; - TokenStream source_function_contents { source_function.values() }; + TokenStream source_function_contents { source_function.value }; if (!expand_unresolved_values(element, property_name, source_function_contents, function_values)) return false; - NonnullRefPtr function = Function::create(source_function.name(), move(function_values)); - dest.empend(function); + dest.empend(Function { source_function.name, move(function_values) }); continue; } if (value.is_block()) { auto const& source_block = value.block(); - TokenStream source_block_values { source_block.values() }; + TokenStream source_block_values { source_block.value }; Vector block_values; if (!expand_unresolved_values(element, property_name, source_block_values, block_values)) return false; - NonnullRefPtr block = Block::create(source_block.token(), move(block_values)); - dest.empend(move(block)); + dest.empend(SimpleBlock { source_block.token, move(block_values) }); continue; } dest.empend(value.token()); @@ -8873,7 +8874,7 @@ bool Parser::substitute_attr_function(DOM::Element& element, FlyString const& pr // First, parse the arguments to attr(): // attr() = attr( ? , ?) // = string | url | ident | color | number | percentage | length | angle | time | frequency | flex | - TokenStream attr_contents { attr_function.values() }; + TokenStream attr_contents { attr_function.value }; attr_contents.discard_whitespace(); if (!attr_contents.has_next_token()) return false; diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 80179d17699..53dc680b51b 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -16,16 +16,12 @@ #include #include #include -#include #include -#include -#include #include -#include #include -#include #include #include +#include #include #include #include @@ -91,35 +87,34 @@ private: // "Parse a stylesheet" is intended to be the normal parser entry point, for parsing stylesheets. struct ParsedStyleSheet { Optional location; - Vector> rules; + Vector rules; }; template ParsedStyleSheet parse_a_stylesheet(TokenStream&, Optional location); - // "Parse a list of rules" is intended for the content of at-rules such as @media. It differs from "Parse a stylesheet" in the handling of and . + // "Parse a stylesheet’s contents" is intended for use by the CSSStyleSheet replace() method, and similar, which parse text into the contents of an existing stylesheet. template - Vector> parse_a_list_of_rules(TokenStream&); + Vector parse_a_stylesheets_contents(TokenStream&); + + // "Parse a block’s contents" is intended for parsing the contents of any block in CSS (including things like the style attribute), + // and APIs such as the CSSStyleDeclaration cssText attribute. + template + Vector parse_a_blocks_contents(TokenStream&); // "Parse a rule" is intended for use by the CSSStyleSheet#insertRule method, and similar functions which might exist, which parse text into a single rule. template - RefPtr parse_a_rule(TokenStream&); + Optional parse_a_rule(TokenStream&); // "Parse a declaration" is used in @supports conditions. [CSS3-CONDITIONAL] template Optional parse_a_declaration(TokenStream&); - template - Vector parse_a_style_blocks_contents(TokenStream&); - - // "Parse a list of declarations" is for the contents of a style attribute, which parses text into the contents of a single style rule. - template - Vector parse_a_list_of_declarations(TokenStream&); - // "Parse a component value" is for things that need to consume a single value, like the parsing rules for attr(). template Optional parse_a_component_value(TokenStream&); - // "Parse a list of component values" is for the contents of presentational attributes, which parse text into a single declaration’s value, or for parsing a stand-alone selector [SELECT] or list of Media Queries [MEDIAQ], as in Selectors API or the media HTML attribute. + // "Parse a list of component values" is for the contents of presentational attributes, which parse text into a single declaration’s value, + // or for parsing a stand-alone selector [SELECT] or list of Media Queries [MEDIAQ], as in Selectors API or the media HTML attribute. template Vector parse_a_list_of_component_values(TokenStream&); @@ -140,33 +135,37 @@ private: Optional parse_a_n_plus_b_pattern(TokenStream&); - enum class TopLevel { + template + [[nodiscard]] Vector consume_a_stylesheets_contents(TokenStream&); + enum class Nested { No, - Yes + Yes, }; template - [[nodiscard]] Vector> consume_a_list_of_rules(TokenStream&, TopLevel); + Optional consume_an_at_rule(TokenStream&, Nested nested = Nested::No); + struct InvalidRuleError { }; template - [[nodiscard]] NonnullRefPtr consume_an_at_rule(TokenStream&); + Variant consume_a_qualified_rule(TokenStream&, Optional stop_token = {}, Nested = Nested::No); template - RefPtr consume_a_qualified_rule(TokenStream&); + Vector consume_a_block(TokenStream&); template - [[nodiscard]] Vector consume_a_style_blocks_contents(TokenStream&); + Vector consume_a_blocks_contents(TokenStream&); template - [[nodiscard]] Vector consume_a_list_of_declarations(TokenStream&); + Optional consume_a_declaration(TokenStream&, Nested = Nested::No); template - Optional consume_a_declaration(TokenStream&); + void consume_the_remnants_of_a_bad_declaration(TokenStream&, Nested); + template + [[nodiscard]] Vector consume_a_list_of_component_values(TokenStream&, Optional stop_token = {}, Nested = Nested::No); template [[nodiscard]] ComponentValue consume_a_component_value(TokenStream&); template - NonnullRefPtr consume_a_simple_block(TokenStream&); + SimpleBlock consume_a_simple_block(TokenStream&); template - NonnullRefPtr consume_a_function(TokenStream&); + Function consume_a_function(TokenStream&); + // TODO: consume_a_unicode_range_value() Optional parse_general_enclosed(TokenStream&); - JS::GCPtr parse_font_face_rule(TokenStream&); - template Vector parse_font_face_src(TokenStream&); @@ -176,15 +175,19 @@ private: }; Optional parse_layer_name(TokenStream&, AllowBlankLayerName); - JS::GCPtr convert_to_rule(NonnullRefPtr); - JS::GCPtr convert_to_keyframes_rule(Rule&); - JS::GCPtr convert_to_import_rule(Rule&); - JS::GCPtr convert_to_layer_rule(Rule&); - JS::GCPtr convert_to_media_rule(Rule&); - JS::GCPtr convert_to_namespace_rule(Rule&); - JS::GCPtr convert_to_supports_rule(Rule&); + bool is_valid_in_the_current_context(Declaration&); + bool is_valid_in_the_current_context(AtRule&); + bool is_valid_in_the_current_context(QualifiedRule&); + JS::GCPtr convert_to_rule(Rule const&); + JS::GCPtr convert_to_font_face_rule(AtRule const&); + JS::GCPtr convert_to_keyframes_rule(AtRule const&); + JS::GCPtr convert_to_import_rule(AtRule const&); + JS::GCPtr convert_to_layer_rule(AtRule const&); + JS::GCPtr convert_to_media_rule(AtRule const&); + JS::GCPtr convert_to_namespace_rule(AtRule const&); + JS::GCPtr convert_to_supports_rule(AtRule const&); - PropertyOwningCSSStyleDeclaration* convert_to_style_declaration(Vector const& declarations); + PropertyOwningCSSStyleDeclaration* convert_to_style_declaration(Vector const&); Optional convert_to_style_property(Declaration const&); Optional parse_dimension(ComponentValue const&); @@ -376,7 +379,8 @@ private: HashMap custom_properties; }; - PropertiesAndCustomProperties extract_properties(Vector const&); + PropertiesAndCustomProperties extract_properties(Vector const&); + void extract_property(Declaration const&, Parser::PropertiesAndCustomProperties&); ParsingContext m_context; diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Rule.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Rule.cpp deleted file mode 100644 index 55a07eeac4a..00000000000 --- a/Userland/Libraries/LibWeb/CSS/Parser/Rule.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2020-2021, the SerenityOS developers. - * Copyright (c) 2021-2023, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include - -namespace Web::CSS::Parser { - -Rule::Rule(Rule::Type type, FlyString name, Vector prelude, RefPtr block) - : m_type(type) - , m_at_rule_name(move(name)) - , m_prelude(move(prelude)) - , m_block(move(block)) -{ -} - -Rule::~Rule() = default; - -} diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Rule.h b/Userland/Libraries/LibWeb/CSS/Parser/Rule.h deleted file mode 100644 index 60647a7b419..00000000000 --- a/Userland/Libraries/LibWeb/CSS/Parser/Rule.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2020-2021, the SerenityOS developers. - * Copyright (c) 2021-2023, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace Web::CSS::Parser { - -class Rule : public RefCounted { -public: - enum class Type { - At, - Qualified, - }; - - static NonnullRefPtr make_at_rule(FlyString name, Vector prelude, RefPtr block) - { - return adopt_ref(*new Rule(Type::At, move(name), move(prelude), move(block))); - } - - static NonnullRefPtr make_qualified_rule(Vector prelude, RefPtr block) - { - return adopt_ref(*new Rule(Type::Qualified, {}, move(prelude), move(block))); - } - - ~Rule(); - - bool is_qualified_rule() const { return m_type == Type::Qualified; } - bool is_at_rule() const { return m_type == Type::At; } - - Vector const& prelude() const { return m_prelude; } - RefPtr block() const { return m_block; } - StringView at_rule_name() const { return m_at_rule_name; } - -private: - Rule(Type, FlyString name, Vector prelude, RefPtr); - - Type const m_type; - FlyString m_at_rule_name; - Vector m_prelude; - RefPtr m_block; -}; - -} diff --git a/Userland/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp b/Userland/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp index 8293a9c2f13..a06fb21e26c 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp @@ -240,7 +240,7 @@ Optional Parser::parse_selector_qualifi Parser::ParseErrorOr Parser::parse_attribute_simple_selector(ComponentValue const& first_value) { - auto attribute_tokens = TokenStream { first_value.block().values() }; + auto attribute_tokens = TokenStream { first_value.block().value }; attribute_tokens.discard_whitespace(); @@ -496,34 +496,34 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec }; auto const& pseudo_function = pseudo_class_token.function(); - auto maybe_pseudo_class = pseudo_class_from_string(pseudo_function.name()); + auto maybe_pseudo_class = pseudo_class_from_string(pseudo_function.name); if (!maybe_pseudo_class.has_value()) { - dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name()); + dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name); return ParseError::SyntaxError; } auto pseudo_class = maybe_pseudo_class.value(); auto metadata = pseudo_class_metadata(pseudo_class); if (!metadata.is_valid_as_function) { - dbgln_if(CSS_PARSER_DEBUG, "Pseudo-class ':{}' is not valid as a function", pseudo_function.name()); + dbgln_if(CSS_PARSER_DEBUG, "Pseudo-class ':{}' is not valid as a function", pseudo_function.name); return ParseError::SyntaxError; } - if (pseudo_function.values().is_empty()) { - dbgln_if(CSS_PARSER_DEBUG, "Empty :{}() selector", pseudo_function.name()); + if (pseudo_function.value.is_empty()) { + dbgln_if(CSS_PARSER_DEBUG, "Empty :{}() selector", pseudo_function.name); return ParseError::SyntaxError; } switch (metadata.parameter_type) { case PseudoClassMetadata::ParameterType::ANPlusB: - return parse_nth_child_selector(pseudo_class, pseudo_function.values(), false); + return parse_nth_child_selector(pseudo_class, pseudo_function.value, false); case PseudoClassMetadata::ParameterType::ANPlusBOf: - return parse_nth_child_selector(pseudo_class, pseudo_function.values(), true); + return parse_nth_child_selector(pseudo_class, pseudo_function.value, true); case PseudoClassMetadata::ParameterType::CompoundSelector: { - auto function_token_stream = TokenStream(pseudo_function.values()); + auto function_token_stream = TokenStream(pseudo_function.value); auto compound_selector_or_error = parse_compound_selector(function_token_stream); if (compound_selector_or_error.is_error() || !compound_selector_or_error.value().has_value()) { - dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a compound selector", pseudo_function.name()); + dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a compound selector", pseudo_function.name); return ParseError::SyntaxError; } @@ -542,7 +542,7 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec } case PseudoClassMetadata::ParameterType::ForgivingRelativeSelectorList: case PseudoClassMetadata::ParameterType::ForgivingSelectorList: { - auto function_token_stream = TokenStream(pseudo_function.values()); + auto function_token_stream = TokenStream(pseudo_function.value); auto selector_type = metadata.parameter_type == PseudoClassMetadata::ParameterType::ForgivingSelectorList ? SelectorType::Standalone : SelectorType::Relative; @@ -557,18 +557,18 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec }; } case PseudoClassMetadata::ParameterType::Ident: { - auto function_token_stream = TokenStream(pseudo_function.values()); + auto function_token_stream = TokenStream(pseudo_function.value); function_token_stream.discard_whitespace(); auto maybe_keyword_token = function_token_stream.consume_a_token(); function_token_stream.discard_whitespace(); if (!maybe_keyword_token.is(Token::Type::Ident) || function_token_stream.has_next_token()) { - dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a keyword: not an ident", pseudo_function.name()); + dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a keyword: not an ident", pseudo_function.name); return ParseError::SyntaxError; } auto maybe_keyword = keyword_from_string(maybe_keyword_token.token().ident()); if (!maybe_keyword.has_value()) { - dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a keyword: unrecognized keyword", pseudo_function.name()); + dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a keyword: unrecognized keyword", pseudo_function.name); return ParseError::SyntaxError; } @@ -581,7 +581,7 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec } case PseudoClassMetadata::ParameterType::LanguageRanges: { Vector languages; - auto function_token_stream = TokenStream(pseudo_function.values()); + auto function_token_stream = TokenStream(pseudo_function.value); auto language_token_lists = parse_a_comma_separated_list_of_component_values(function_token_stream); for (auto language_token_list : language_token_lists) { @@ -589,7 +589,7 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec language_token_stream.discard_whitespace(); auto language_token = language_token_stream.consume_a_token(); if (!(language_token.is(Token::Type::Ident) || language_token.is(Token::Type::String))) { - dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - not a string/ident", pseudo_function.name()); + dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - not a string/ident", pseudo_function.name); return ParseError::SyntaxError; } @@ -598,7 +598,7 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec language_token_stream.discard_whitespace(); if (language_token_stream.has_next_token()) { - dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - trailing tokens", pseudo_function.name()); + dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - trailing tokens", pseudo_function.name); return ParseError::SyntaxError; } } @@ -611,7 +611,7 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec }; } case PseudoClassMetadata::ParameterType::SelectorList: { - auto function_token_stream = TokenStream(pseudo_function.values()); + auto function_token_stream = TokenStream(pseudo_function.value); auto not_selector = TRY(parse_a_selector_list(function_token_stream, SelectorType::Standalone)); return Selector::SimpleSelector { diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Types.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Types.cpp new file mode 100644 index 00000000000..927e95e7fe7 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/Parser/Types.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2020-2021, the SerenityOS developers. + * Copyright (c) 2021-2024, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::CSS::Parser { + +String Declaration::to_string() const +{ + if (original_text.has_value()) + return original_text.value(); + + StringBuilder builder; + + serialize_an_identifier(builder, name); + builder.append(": "sv); + builder.join(' ', value); + + if (important == Important::Yes) + builder.append(" !important"sv); + + return MUST(builder.to_string()); +} + +String SimpleBlock::to_string() const +{ + StringBuilder builder; + + builder.append(token.bracket_string()); + builder.join(' ', value); + builder.append(token.bracket_mirror_string()); + + return builder.to_string_without_validation(); +} + +String Function::to_string() const +{ + StringBuilder builder; + + serialize_an_identifier(builder, name); + builder.append('('); + for (auto& item : value) + builder.append(item.to_string()); + builder.append(')'); + + return builder.to_string_without_validation(); +} + +void AtRule::for_each(AtRuleVisitor&& visit_at_rule, QualifiedRuleVisitor&& visit_qualified_rule, DeclarationVisitor&& visit_declaration) const +{ + for (auto const& child : child_rules_and_lists_of_declarations) { + child.visit( + [&](Rule const& rule) { + rule.visit( + [&](AtRule const& at_rule) { visit_at_rule(at_rule); }, + [&](QualifiedRule const& qualified_rule) { visit_qualified_rule(qualified_rule); }); + }, + [&](Vector const& declarations) { + for (auto const& declaration : declarations) + visit_declaration(declaration); + }); + } +} + +// https://drafts.csswg.org/css-syntax/#typedef-declaration-list +void AtRule::for_each_as_declaration_list(DeclarationVisitor&& visit) const +{ + // : only declarations are allowed; at-rules and qualified rules are automatically invalid. + for_each( + [](auto const& at_rule) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal @{} rule in ``; discarding.", at_rule.name); }, + [](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal qualified rule in ``; discarding."); }, + move(visit)); +} + +// https://drafts.csswg.org/css-syntax/#typedef-qualified-rule-list +void AtRule::for_each_as_qualified_rule_list(QualifiedRuleVisitor&& visit) const +{ + // : only qualified rules are allowed; declarations and at-rules are automatically invalid. + for_each( + [](auto const& at_rule) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal @{} rule in ``; discarding.", at_rule.name); }, + move(visit), + [](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal list of declarations in ``; discarding."); }); +} + +// https://drafts.csswg.org/css-syntax/#typedef-at-rule-list +void AtRule::for_each_as_at_rule_list(AtRuleVisitor&& visit) const +{ + // : only at-rules are allowed; declarations and qualified rules are automatically invalid. + for_each( + move(visit), + [](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal qualified rule in ``; discarding."); }, + [](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal list of declarations in ``; discarding."); }); +} + +// https://drafts.csswg.org/css-syntax/#typedef-declaration-rule-list +void AtRule::for_each_as_declaration_rule_list(AtRuleVisitor&& visit_at_rule, DeclarationVisitor&& visit_declaration) const +{ + // : declarations and at-rules are allowed; qualified rules are automatically invalid. + for_each( + move(visit_at_rule), + [](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal qualified rule in ``; discarding."); }, + move(visit_declaration)); +} + +// https://drafts.csswg.org/css-syntax/#typedef-rule-list +void AtRule::for_each_as_rule_list(RuleVisitor&& visit) const +{ + // : qualified rules and at-rules are allowed; declarations are automatically invalid. + for (auto const& child : child_rules_and_lists_of_declarations) { + child.visit( + [&](Rule const& rule) { visit(rule); }, + [&](Vector const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal list of declarations in ``; discarding."); }); + } +} + +// https://drafts.csswg.org/css-syntax/#typedef-declaration-list +void QualifiedRule::for_each_as_declaration_list(DeclarationVisitor&& visit) const +{ + // : only declarations are allowed; at-rules and qualified rules are automatically invalid. + for (auto const& declaration : declarations) + visit(declaration); + + for (auto const& child : child_rules) { + child.visit( + [&](Rule const&) { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal qualified rule in ``; discarding."); + }, + [&](Vector const& declarations) { + for (auto const& declaration : declarations) + visit(declaration); + }); + } +} + +} diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Types.h b/Userland/Libraries/LibWeb/CSS/Parser/Types.h new file mode 100644 index 00000000000..cb12e5284a7 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/Parser/Types.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Web::CSS::Parser { + +// https://drafts.csswg.org/css-syntax/#css-rule +using Rule = Variant; + +using RuleOrListOfDeclarations = Variant>; + +using AtRuleVisitor = AK::Function; +using QualifiedRuleVisitor = AK::Function; +using RuleVisitor = AK::Function; +using DeclarationVisitor = AK::Function; + +// https://drafts.csswg.org/css-syntax/#ref-for-at-rule%E2%91%A0%E2%91%A1 +struct AtRule { + FlyString name; + Vector prelude; + Vector child_rules_and_lists_of_declarations; + + void for_each(AtRuleVisitor&& visit_at_rule, QualifiedRuleVisitor&& visit_qualified_rule, DeclarationVisitor&& visit_declaration) const; + void for_each_as_declaration_list(DeclarationVisitor&& visit) const; + void for_each_as_qualified_rule_list(QualifiedRuleVisitor&& visit) const; + void for_each_as_at_rule_list(AtRuleVisitor&& visit) const; + void for_each_as_declaration_rule_list(AtRuleVisitor&& visit_at_rule, DeclarationVisitor&& visit_declaration) const; + void for_each_as_rule_list(RuleVisitor&& visit) const; +}; + +// https://drafts.csswg.org/css-syntax/#qualified-rule +struct QualifiedRule { + Vector prelude; + Vector declarations; + Vector child_rules; + + void for_each_as_declaration_list(DeclarationVisitor&& visit) const; +}; + +// https://drafts.csswg.org/css-syntax/#declaration +struct Declaration { + FlyString name; + Vector value; + Important important = Important::No; + Optional original_text = {}; + + // FIXME: Only needed by our janky @supports re-serialization-re-parse code. + String to_string() const; +}; + +// https://drafts.csswg.org/css-syntax/#simple-block +struct SimpleBlock { + Token token; + Vector value; + + bool is_curly() const { return token.is(Token::Type::OpenCurly); } + bool is_paren() const { return token.is(Token::Type::OpenParen); } + bool is_square() const { return token.is(Token::Type::OpenSquare); } + + String to_string() const; +}; + +// https://drafts.csswg.org/css-syntax/#function +struct Function { + FlyString name; + Vector value; + + String to_string() const; +}; + +} diff --git a/Userland/Libraries/LibWeb/CSS/Supports.h b/Userland/Libraries/LibWeb/CSS/Supports.h index 065c22d185c..07743f973f9 100644 --- a/Userland/Libraries/LibWeb/CSS/Supports.h +++ b/Userland/Libraries/LibWeb/CSS/Supports.h @@ -12,7 +12,6 @@ #include #include #include -#include namespace Web::CSS { diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 65c50bfc798..57d0235611e 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -227,13 +227,13 @@ struct BackgroundLayerData; } namespace Web::CSS::Parser { -class Block; +struct AtRule; class ComponentValue; -class Declaration; -class DeclarationOrAtRule; -class Function; +struct Declaration; +struct Function; class Parser; -class Rule; +struct QualifiedRule; +struct SimpleBlock; class Token; class Tokenizer; }