Meta: Handle juxtaposition in CSS value parsing code generation
This commit is contained in:
parent
30e58a48c6
commit
099c3fa590
4 changed files with 97 additions and 2 deletions
|
|
@ -19,6 +19,10 @@ class ComponentValueGrammarNode(GrammarNode):
|
||||||
|
|
||||||
|
|
||||||
class CombinatorType(Enum):
|
class CombinatorType(Enum):
|
||||||
|
# https://drafts.csswg.org/css-values-4/#component-combinators
|
||||||
|
# Juxtaposing components means that all of them must occur, in the given order.
|
||||||
|
JUXTAPOSITION = "Juxtaposition"
|
||||||
|
|
||||||
# https://drafts.csswg.org/css-values-4/#comb-one
|
# https://drafts.csswg.org/css-values-4/#comb-one
|
||||||
# A bar (|) separates two or more alternatives: exactly one of them must occur.
|
# A bar (|) separates two or more alternatives: exactly one of them must occur.
|
||||||
ALTERNATIVES = "Alternatives"
|
ALTERNATIVES = "Alternatives"
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,28 @@ class Parser:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def parse_alternatives(self) -> GrammarNode:
|
def parse_alternatives(self) -> GrammarNode:
|
||||||
children = [self.parse_component_value()]
|
children = [self.parse_juxtaposition()]
|
||||||
|
|
||||||
while self.peek().is_token_type(TokenType.SINGLE_BAR):
|
while self.peek().is_token_type(TokenType.SINGLE_BAR):
|
||||||
self.consume()
|
self.consume()
|
||||||
children.append(self.parse_component_value())
|
children.append(self.parse_juxtaposition())
|
||||||
|
|
||||||
if len(children) == 1:
|
if len(children) == 1:
|
||||||
return children[0]
|
return children[0]
|
||||||
|
|
||||||
return CombinatorGrammarNode(CombinatorType.ALTERNATIVES, children)
|
return CombinatorGrammarNode(CombinatorType.ALTERNATIVES, children)
|
||||||
|
|
||||||
|
def parse_juxtaposition(self) -> GrammarNode:
|
||||||
|
children = [self.parse_component_value()]
|
||||||
|
|
||||||
|
while self.next_token_starts_component_value():
|
||||||
|
children.append(self.parse_component_value())
|
||||||
|
|
||||||
|
if len(children) == 1:
|
||||||
|
return children[0]
|
||||||
|
|
||||||
|
return CombinatorGrammarNode(CombinatorType.JUXTAPOSITION, children)
|
||||||
|
|
||||||
def parse_component_value(self) -> GrammarNode:
|
def parse_component_value(self) -> GrammarNode:
|
||||||
# https://drafts.csswg.org/css-values-4/#component-multipliers
|
# https://drafts.csswg.org/css-values-4/#component-multipliers
|
||||||
# FIXME: Support component multipliers
|
# FIXME: Support component multipliers
|
||||||
|
|
@ -67,6 +78,12 @@ class Parser:
|
||||||
|
|
||||||
return component_value
|
return component_value
|
||||||
|
|
||||||
|
def next_token_starts_component_value(self) -> bool:
|
||||||
|
return self.peek().token_type in (
|
||||||
|
TokenType.OPEN_SQUARE_BRACKET,
|
||||||
|
TokenType.COMPONENT_VALUE,
|
||||||
|
)
|
||||||
|
|
||||||
def peek(self, offset: int = 0) -> Token:
|
def peek(self, offset: int = 0) -> Token:
|
||||||
index = min(self.index + offset, len(self.tokens) - 1)
|
index = min(self.index + offset, len(self.tokens) - 1)
|
||||||
return self.tokens[index]
|
return self.tokens[index]
|
||||||
|
|
|
||||||
|
|
@ -100,9 +100,38 @@ auto {cpp_name} = parse_{cpp_name}_alternatives();
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_css_parser_expression_for_juxtaposition(out: TextIO, cpp_name: str, children: list[GrammarNode]) -> None:
|
||||||
|
out.write(f"""auto const parse_{cpp_name}_juxtaposition = [&]() -> RefPtr<StyleValue const> {{
|
||||||
|
auto {cpp_name}_transaction = tokens.begin_transaction();
|
||||||
|
StyleValueVector {cpp_name}_values;
|
||||||
|
{cpp_name}_values.ensure_capacity({len(children)});
|
||||||
|
|
||||||
|
""")
|
||||||
|
|
||||||
|
for i, component in enumerate(children):
|
||||||
|
component_name = f"{cpp_name}_component_{i}"
|
||||||
|
generate_css_parser_expression_for_grammar_node(out, component_name, component)
|
||||||
|
out.write(f"""if (!{component_name})
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
{cpp_name}_values.append({component_name}.release_nonnull());
|
||||||
|
""")
|
||||||
|
|
||||||
|
out.write(f"""{cpp_name}_transaction.commit();
|
||||||
|
return StyleValueList::create(move({cpp_name}_values), StyleValueList::Separator::Space, StyleValueList::Collapsible::No);
|
||||||
|
}};
|
||||||
|
|
||||||
|
auto {cpp_name} = parse_{cpp_name}_juxtaposition();
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
def generate_css_parser_expression_for_combinator_grammar_node(
|
def generate_css_parser_expression_for_combinator_grammar_node(
|
||||||
out: TextIO, cpp_name: str, grammar_node: CombinatorGrammarNode
|
out: TextIO, cpp_name: str, grammar_node: CombinatorGrammarNode
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if grammar_node.combinator_type == CombinatorType.JUXTAPOSITION:
|
||||||
|
generate_css_parser_expression_for_juxtaposition(out, cpp_name, grammar_node.children)
|
||||||
|
return
|
||||||
|
|
||||||
if grammar_node.combinator_type == CombinatorType.ALTERNATIVES:
|
if grammar_node.combinator_type == CombinatorType.ALTERNATIVES:
|
||||||
generate_css_parser_expression_for_alternatives(out, cpp_name, grammar_node.children)
|
generate_css_parser_expression_for_alternatives(out, cpp_name, grammar_node.children)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,51 @@ class TestCSSGrammarParser(unittest.TestCase):
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_parse_juxtaposition(self) -> None:
|
||||||
|
syntax = parse_value_definition_grammar("<foo> <bar> baz")
|
||||||
|
self.assertEqual(
|
||||||
|
syntax.dump(),
|
||||||
|
"""Combinator(Juxtaposition):
|
||||||
|
ComponentValue
|
||||||
|
Type: foo
|
||||||
|
ComponentValue
|
||||||
|
Type: bar
|
||||||
|
ComponentValue
|
||||||
|
Keyword: baz
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_parse_juxtaposition_has_higher_precedence_than_alternatives(self) -> None:
|
||||||
|
syntax = parse_value_definition_grammar("<foo> <bar> | <baz>")
|
||||||
|
self.assertEqual(
|
||||||
|
syntax.dump(),
|
||||||
|
"""Combinator(Alternatives):
|
||||||
|
Combinator(Juxtaposition):
|
||||||
|
ComponentValue
|
||||||
|
Type: foo
|
||||||
|
ComponentValue
|
||||||
|
Type: bar
|
||||||
|
ComponentValue
|
||||||
|
Type: baz
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_parse_grouped_alternatives_in_juxtaposition(self) -> None:
|
||||||
|
syntax = parse_value_definition_grammar("[ <foo> | <bar> ] <baz>")
|
||||||
|
self.assertEqual(
|
||||||
|
syntax.dump(),
|
||||||
|
"""Combinator(Juxtaposition):
|
||||||
|
Group:
|
||||||
|
Combinator(Alternatives):
|
||||||
|
ComponentValue
|
||||||
|
Type: foo
|
||||||
|
ComponentValue
|
||||||
|
Type: bar
|
||||||
|
ComponentValue
|
||||||
|
Type: baz
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
def test_parse_group(self) -> None:
|
def test_parse_group(self) -> None:
|
||||||
syntax = parse_value_definition_grammar("[ <length> ]")
|
syntax = parse_value_definition_grammar("[ <length> ]")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue