Meta: Handle optionals in CSS value parsing code generation
This commit is contained in:
parent
fe121d403d
commit
30e58a48c6
7 changed files with 79 additions and 4 deletions
|
|
@ -95,6 +95,7 @@ def generate_implementation_file(out: TextIO, value_type_data: dict[str, Any]) -
|
|||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
|
||||
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/EmptyOptionalStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
|
||||
|
||||
namespace Web::CSS::Parser {
|
||||
|
|
|
|||
|
|
@ -45,3 +45,11 @@ class GroupGrammarNode(GrammarNode):
|
|||
|
||||
def dump(self, indent: int = 0) -> str:
|
||||
return f"{'': >{indent}}Group:\n" + self.child.dump(indent + 2)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OptionalGrammarNode(GrammarNode):
|
||||
child: GrammarNode
|
||||
|
||||
def dump(self, indent: int = 0) -> str:
|
||||
return f"{'': >{indent}}Optional:\n" + self.child.dump(indent + 2)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from Utils.CSSGrammar.Parser.grammar_node import CombinatorType
|
|||
from Utils.CSSGrammar.Parser.grammar_node import ComponentValueGrammarNode
|
||||
from Utils.CSSGrammar.Parser.grammar_node import GrammarNode
|
||||
from Utils.CSSGrammar.Parser.grammar_node import GroupGrammarNode
|
||||
from Utils.CSSGrammar.Parser.grammar_node import OptionalGrammarNode
|
||||
from Utils.CSSGrammar.Parser.token import Token
|
||||
from Utils.CSSGrammar.Parser.token import TokenType
|
||||
from Utils.CSSGrammar.Parser.tokenizer import Tokenizer
|
||||
|
|
@ -40,7 +41,10 @@ class Parser:
|
|||
# https://drafts.csswg.org/css-values-4/#component-multipliers
|
||||
# FIXME: Support component multipliers
|
||||
|
||||
if self.peek().is_token_type(TokenType.OPEN_SQUARE_BRACKET):
|
||||
peeked = self.peek()
|
||||
component_value = None
|
||||
|
||||
if peeked.is_token_type(TokenType.OPEN_SQUARE_BRACKET):
|
||||
self.consume()
|
||||
group = self.parse_alternatives()
|
||||
|
||||
|
|
@ -49,12 +53,19 @@ class Parser:
|
|||
|
||||
# FIXME: Support required groups (e.g. [ <foo>? ]!)
|
||||
self.consume()
|
||||
return GroupGrammarNode(group)
|
||||
component_value = GroupGrammarNode(group)
|
||||
|
||||
if not self.peek().is_token_type(TokenType.COMPONENT_VALUE):
|
||||
if peeked.is_token_type(TokenType.COMPONENT_VALUE):
|
||||
component_value = ComponentValueGrammarNode(self.consume().component_value())
|
||||
|
||||
if component_value is None:
|
||||
raise SyntaxError("CSSGrammar::Parser: Expected a component value")
|
||||
|
||||
return ComponentValueGrammarNode(self.consume().component_value())
|
||||
if self.peek().is_token_type(TokenType.QUESTION_MARK):
|
||||
self.consume()
|
||||
component_value = OptionalGrammarNode(component_value)
|
||||
|
||||
return component_value
|
||||
|
||||
def peek(self, offset: int = 0) -> Token:
|
||||
index = min(self.index + offset, len(self.tokens) - 1)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ class TokenType(Enum):
|
|||
SINGLE_BAR = "single-bar"
|
||||
OPEN_SQUARE_BRACKET = "open-square-bracket"
|
||||
CLOSE_SQUARE_BRACKET = "close-square-bracket"
|
||||
QUESTION_MARK = "question-mark"
|
||||
COMPONENT_VALUE = "component-value"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ class Tokenizer:
|
|||
if peeked == "]":
|
||||
self.lexer.consume()
|
||||
return Token.create(TokenType.CLOSE_SQUARE_BRACKET)
|
||||
if peeked == "?":
|
||||
self.lexer.consume()
|
||||
return Token.create(TokenType.QUESTION_MARK)
|
||||
if peeked == "<":
|
||||
return self.consume_a_non_terminal_token()
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from Utils.CSSGrammar.Parser.grammar_node import CombinatorType
|
|||
from Utils.CSSGrammar.Parser.grammar_node import ComponentValueGrammarNode
|
||||
from Utils.CSSGrammar.Parser.grammar_node import GrammarNode
|
||||
from Utils.CSSGrammar.Parser.grammar_node import GroupGrammarNode
|
||||
from Utils.CSSGrammar.Parser.grammar_node import OptionalGrammarNode
|
||||
from Utils.CSSGrammar.Parser.parser import parse_value_definition_grammar
|
||||
from Utils.utils import snake_casify
|
||||
from Utils.utils import title_casify
|
||||
|
|
@ -115,6 +116,19 @@ def generate_css_parser_expression_for_group_grammar_node(
|
|||
generate_css_parser_expression_for_grammar_node(out, cpp_name, grammar_node.child)
|
||||
|
||||
|
||||
def generate_css_parser_expression_for_optional_grammar_node(
|
||||
out: TextIO, cpp_name: str, grammar_node: OptionalGrammarNode
|
||||
) -> None:
|
||||
out.write(f"""RefPtr<StyleValue const> {cpp_name} = EmptyOptionalStyleValue::create();
|
||||
""")
|
||||
|
||||
generate_css_parser_expression_for_grammar_node(out, f"maybe_{cpp_name}", grammar_node.child)
|
||||
|
||||
out.write(f"""if (maybe_{cpp_name})
|
||||
{cpp_name} = maybe_{cpp_name};
|
||||
""")
|
||||
|
||||
|
||||
def generate_css_parser_expression_for_grammar_node(out: TextIO, cpp_name: str, grammar_node: GrammarNode) -> None:
|
||||
if isinstance(grammar_node, ComponentValueGrammarNode):
|
||||
generate_css_parser_expression_for_component_value_grammar_node(out, cpp_name, grammar_node)
|
||||
|
|
@ -122,6 +136,9 @@ def generate_css_parser_expression_for_grammar_node(out: TextIO, cpp_name: str,
|
|||
if isinstance(grammar_node, GroupGrammarNode):
|
||||
generate_css_parser_expression_for_group_grammar_node(out, cpp_name, grammar_node)
|
||||
return
|
||||
if isinstance(grammar_node, OptionalGrammarNode):
|
||||
generate_css_parser_expression_for_optional_grammar_node(out, cpp_name, grammar_node)
|
||||
return
|
||||
if isinstance(grammar_node, CombinatorGrammarNode):
|
||||
generate_css_parser_expression_for_combinator_grammar_node(out, cpp_name, grammar_node)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -160,6 +160,30 @@ class TestCSSGrammarParser(unittest.TestCase):
|
|||
""",
|
||||
)
|
||||
|
||||
def test_parse_optional_type_reference(self) -> None:
|
||||
syntax = parse_value_definition_grammar("<foo>?")
|
||||
self.assertEqual(
|
||||
syntax.dump(),
|
||||
"""Optional:
|
||||
ComponentValue
|
||||
Type: foo
|
||||
""",
|
||||
)
|
||||
|
||||
def test_parse_optional_group(self) -> None:
|
||||
syntax = parse_value_definition_grammar("[ auto | none ]?")
|
||||
self.assertEqual(
|
||||
syntax.dump(),
|
||||
"""Optional:
|
||||
Group:
|
||||
Combinator(Alternatives):
|
||||
ComponentValue
|
||||
Keyword: auto
|
||||
ComponentValue
|
||||
Keyword: none
|
||||
""",
|
||||
)
|
||||
|
||||
def test_parse_ignores_whitespace_around_tokens(self) -> None:
|
||||
syntax = parse_value_definition_grammar(" <foo>\t|\n<bar> ")
|
||||
self.assertEqual(
|
||||
|
|
@ -184,6 +208,16 @@ class TestCSSGrammarParser(unittest.TestCase):
|
|||
with self.assertRaises(SyntaxError):
|
||||
parse_value_definition_grammar("<foo> |")
|
||||
|
||||
def test_reject_invalid_optional(self) -> None:
|
||||
for value in (
|
||||
"?",
|
||||
"<foo>??",
|
||||
"[ <foo> ]??",
|
||||
):
|
||||
with self.subTest(value=value):
|
||||
with self.assertRaises(SyntaxError):
|
||||
parse_value_definition_grammar(value)
|
||||
|
||||
def test_reject_invalid_group(self) -> None:
|
||||
for value in (
|
||||
"[]",
|
||||
|
|
|
|||
Loading…
Reference in a new issue