Meta: Handle groups in CSS value parsing code generation
We don't yet support required groups so for the moment these just fall back to generating the parsing code of their child
This commit is contained in:
parent
cb14d574de
commit
6361dcaae5
7 changed files with 91 additions and 2 deletions
|
|
@ -1,8 +1,7 @@
|
|||
{
|
||||
"<font-weight-absolute>": {
|
||||
"spec": "https://drafts.csswg.org/css-fonts-4/#font-weight-absolute-values",
|
||||
"grammar": "normal | bold | <number [1,1000]>",
|
||||
"__comment": "FIXME: The spec defines this wrapped in a group i.e. '[ normal | bold | <number [1,1000]> ]' but our CSS grammar parser doesn't currently know how to handle that"
|
||||
"grammar": "[ normal | bold | <number [1,1000]> ]"
|
||||
},
|
||||
"<symbol>": {
|
||||
"spec": "https://drafts.csswg.org/css-counter-styles-3/#typedef-symbol",
|
||||
|
|
|
|||
|
|
@ -37,3 +37,11 @@ class CombinatorGrammarNode(GrammarNode):
|
|||
output += child.dump(indent + 2)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GroupGrammarNode(GrammarNode):
|
||||
child: GrammarNode
|
||||
|
||||
def dump(self, indent: int = 0) -> str:
|
||||
return f"{'': >{indent}}Group:\n" + self.child.dump(indent + 2)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from Utils.CSSGrammar.Parser.grammar_node import CombinatorGrammarNode
|
|||
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.token import Token
|
||||
from Utils.CSSGrammar.Parser.token import TokenType
|
||||
from Utils.CSSGrammar.Parser.tokenizer import Tokenizer
|
||||
|
|
@ -39,6 +40,17 @@ 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):
|
||||
self.consume()
|
||||
group = self.parse_alternatives()
|
||||
|
||||
if not self.peek().is_token_type(TokenType.CLOSE_SQUARE_BRACKET):
|
||||
raise SyntaxError("CSSGrammar::Parser: Expected ']'")
|
||||
|
||||
# FIXME: Support required groups (e.g. [ <foo>? ]!)
|
||||
self.consume()
|
||||
return GroupGrammarNode(group)
|
||||
|
||||
if not self.peek().is_token_type(TokenType.COMPONENT_VALUE):
|
||||
raise SyntaxError("CSSGrammar::Parser: Expected a component value")
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ from Utils.CSSGrammar.Parser.component_values import ComponentValue
|
|||
class TokenType(Enum):
|
||||
END_OF_FILE = "end-of-file"
|
||||
SINGLE_BAR = "single-bar"
|
||||
OPEN_SQUARE_BRACKET = "open-square-bracket"
|
||||
CLOSE_SQUARE_BRACKET = "close-square-bracket"
|
||||
COMPONENT_VALUE = "component-value"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,12 @@ class Tokenizer:
|
|||
if peeked == "|":
|
||||
self.lexer.consume()
|
||||
return Token.create(TokenType.SINGLE_BAR)
|
||||
if peeked == "[":
|
||||
self.lexer.consume()
|
||||
return Token.create(TokenType.OPEN_SQUARE_BRACKET)
|
||||
if peeked == "]":
|
||||
self.lexer.consume()
|
||||
return Token.create(TokenType.CLOSE_SQUARE_BRACKET)
|
||||
if peeked == "<":
|
||||
return self.consume_a_non_terminal_token()
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from Utils.CSSGrammar.Parser.grammar_node import CombinatorGrammarNode
|
|||
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.parser import parse_value_definition_grammar
|
||||
from Utils.utils import snake_casify
|
||||
from Utils.utils import title_casify
|
||||
|
|
@ -108,10 +109,19 @@ def generate_css_parser_expression_for_combinator_grammar_node(
|
|||
raise TypeError(f"Unhandled combinator type: {grammar_node.combinator_type}")
|
||||
|
||||
|
||||
def generate_css_parser_expression_for_group_grammar_node(
|
||||
out: TextIO, cpp_name: str, grammar_node: GroupGrammarNode
|
||||
) -> None:
|
||||
generate_css_parser_expression_for_grammar_node(out, cpp_name, grammar_node.child)
|
||||
|
||||
|
||||
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)
|
||||
return
|
||||
if isinstance(grammar_node, GroupGrammarNode):
|
||||
generate_css_parser_expression_for_group_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
|
||||
|
|
|
|||
|
|
@ -119,6 +119,47 @@ class TestCSSGrammarParser(unittest.TestCase):
|
|||
""",
|
||||
)
|
||||
|
||||
def test_parse_group(self) -> None:
|
||||
syntax = parse_value_definition_grammar("[ <length> ]")
|
||||
self.assertEqual(
|
||||
syntax.dump(),
|
||||
"""Group:
|
||||
ComponentValue
|
||||
Type: length [-∞,∞]
|
||||
""",
|
||||
)
|
||||
|
||||
def test_parse_grouped_alternatives(self) -> None:
|
||||
syntax = parse_value_definition_grammar("[ auto | none | <length> ]")
|
||||
self.assertEqual(
|
||||
syntax.dump(),
|
||||
"""Group:
|
||||
Combinator(Alternatives):
|
||||
ComponentValue
|
||||
Keyword: auto
|
||||
ComponentValue
|
||||
Keyword: none
|
||||
ComponentValue
|
||||
Type: length [-∞,∞]
|
||||
""",
|
||||
)
|
||||
|
||||
def test_parse_groups_as_alternative(self) -> None:
|
||||
syntax = parse_value_definition_grammar("[ auto | none ] | <length>")
|
||||
self.assertEqual(
|
||||
syntax.dump(),
|
||||
"""Combinator(Alternatives):
|
||||
Group:
|
||||
Combinator(Alternatives):
|
||||
ComponentValue
|
||||
Keyword: auto
|
||||
ComponentValue
|
||||
Keyword: none
|
||||
ComponentValue
|
||||
Type: length [-∞,∞]
|
||||
""",
|
||||
)
|
||||
|
||||
def test_parse_ignores_whitespace_around_tokens(self) -> None:
|
||||
syntax = parse_value_definition_grammar(" <foo>\t|\n<bar> ")
|
||||
self.assertEqual(
|
||||
|
|
@ -143,6 +184,17 @@ class TestCSSGrammarParser(unittest.TestCase):
|
|||
with self.assertRaises(SyntaxError):
|
||||
parse_value_definition_grammar("<foo> |")
|
||||
|
||||
def test_reject_invalid_group(self) -> None:
|
||||
for value in (
|
||||
"[]",
|
||||
"[ <foo>",
|
||||
"<foo> ]",
|
||||
"[ <foo> | ]",
|
||||
):
|
||||
with self.subTest(value=value):
|
||||
with self.assertRaises(SyntaxError):
|
||||
parse_value_definition_grammar(value)
|
||||
|
||||
def test_reject_invalid_type_reference(self) -> None:
|
||||
for value in (
|
||||
"<>",
|
||||
|
|
|
|||
Loading…
Reference in a new issue