LibWeb/CSS: Implement "parse with a <syntax>"

Uses the SyntaxNode tree to parse a list of ComponentValues into some
kind of StyleValue.
This commit is contained in:
Sam Atkins 2025-07-11 12:17:36 +01:00 committed by Tim Ledbetter
commit 0a5e8c2865
Notes: github-actions[bot] 2025-07-16 13:49:04 +00:00
6 changed files with 140 additions and 7 deletions

View file

@ -5,10 +5,16 @@
*/
#include <AK/GenericShorthands.h>
#include <LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/Parser/Syntax.h>
#include <LibWeb/CSS/Parser/SyntaxParsing.h>
#include <LibWeb/CSS/Parser/TokenStream.h>
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
#include <LibWeb/CSS/StyleValues/GuaranteedInvalidStyleValue.h>
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
#include <LibWeb/CSS/StyleValues/UnresolvedStyleValue.h>
namespace Web::CSS::Parser {
@ -207,4 +213,116 @@ OwnPtr<SyntaxNode> parse_as_syntax(Vector<ComponentValue> const& component_value
return AlternativesSyntaxNode::create(move(syntax_components));
}
NonnullRefPtr<CSSStyleValue const> parse_with_a_syntax(ParsingParams const& parsing_params, Vector<ComponentValue> const& input, SyntaxNode const& syntax, Optional<DOM::AbstractElement> const& element)
{
return Parser::create(parsing_params, ""sv).parse_with_a_syntax(input, syntax, element);
}
RefPtr<CSSStyleValue const> Parser::parse_according_to_syntax_node(TokenStream<ComponentValue>& tokens, SyntaxNode const& syntax_node, Optional<DOM::AbstractElement> const& element)
{
auto transaction = tokens.begin_transaction();
switch (syntax_node.type()) {
case SyntaxNode::NodeType::Universal:
if (auto declaration_value = parse_declaration_value(tokens); declaration_value.has_value()) {
transaction.commit();
return UnresolvedStyleValue::create(declaration_value.release_value());
}
return nullptr;
case SyntaxNode::NodeType::Ident: {
auto const& ident_node = as<IdentSyntaxNode>(syntax_node);
tokens.discard_whitespace();
if (tokens.consume_a_token().is_ident(ident_node.ident())) {
transaction.commit();
if (auto keyword = keyword_from_string(ident_node.ident()); keyword.has_value())
return CSSKeywordValue::create(keyword.release_value());
return CustomIdentStyleValue::create(ident_node.ident());
}
return nullptr;
}
case SyntaxNode::NodeType::Type: {
auto const& type_node = as<TypeSyntaxNode>(syntax_node);
auto const& type_name = type_node.type_name();
if (auto value_type = value_type_from_string(type_name); value_type.has_value()) {
if (auto result = parse_value(*value_type, tokens)) {
transaction.commit();
return result.release_nonnull();
}
return nullptr;
}
dbgln_if(CSS_PARSER_DEBUG, "Couldn't parse `<{}>` because we don't know what it is.", type_name);
return nullptr;
}
case SyntaxNode::NodeType::Multiplier: {
auto const& multiplier_node = as<MultiplierSyntaxNode>(syntax_node);
StyleValueVector values;
tokens.discard_whitespace();
while (tokens.has_next_token()) {
auto parsed_child = parse_according_to_syntax_node(tokens, multiplier_node.child(), element);
if (!parsed_child)
break;
values.append(parsed_child.release_nonnull());
tokens.discard_whitespace();
}
if (values.is_empty())
return nullptr;
transaction.commit();
return StyleValueList::create(move(values), StyleValueList::Separator::Space);
}
case SyntaxNode::NodeType::CommaSeparatedMultiplier: {
auto const& multiplier_node = as<CommaSeparatedMultiplierSyntaxNode>(syntax_node);
auto result = parse_comma_separated_value_list(tokens, [&](auto& tokens) {
return parse_according_to_syntax_node(tokens, multiplier_node.child(), element);
});
if (!result)
return nullptr;
transaction.commit();
return result.release_nonnull();
}
case SyntaxNode::NodeType::Alternatives: {
auto const& alternatives_node = as<AlternativesSyntaxNode>(syntax_node);
for (auto const& child : alternatives_node.children()) {
if (auto result = parse_according_to_syntax_node(tokens, *child, element)) {
transaction.commit();
return result.release_nonnull();
}
}
return nullptr;
}
}
VERIFY_NOT_REACHED();
}
// https://drafts.csswg.org/css-values-5/#parse-with-a-syntax
NonnullRefPtr<CSSStyleValue const> Parser::parse_with_a_syntax(Vector<ComponentValue> const& input, SyntaxNode const& syntax, Optional<DOM::AbstractElement> const& element)
{
// 1. Parse a list of component values from values, and let raw parse be the result.
// NB: Already done before this point.
// FIXME: 2. If el was given, substitute arbitrary substitution functions in raw parse, and set raw parse to that result.
// NB: This is currently a no-op because our only caller already substitutes ASFs in the input before calling us.
// FIXME: Move substitute_arbitrary_substitution_functions() into the Parser, and keep the guarded contexts there,
// so we don't have this awkward situation of needing to pass that to random other functions.
// 3. parse values according to syntax, with a * value treated as <declaration-value>?, and let parsed result be
// the result.
// If syntax used a | combinator, let parsed result be the parse result from the first matching clause.
TokenStream tokens { input };
auto parsed_result = parse_according_to_syntax_node(tokens, syntax, element);
tokens.discard_whitespace();
// 4. If parsed result is failure, return the guaranteed-invalid value.
if (!parsed_result || tokens.has_next_token())
return GuaranteedInvalidStyleValue::create();
// 5. Assert: parsed result is now a well-defined list of one or more CSS values, since each branch of a <syntax>
// defines an unambiguous parse result (or the * syntax is unambiguous on its own).
// NB: Nothing to do.
// 6. Return parsed result.
return parsed_result.release_nonnull();
}
}