Meta: Implement code generation for CSS custom ident value blacklists
This commit is contained in:
parent
09418e8c77
commit
dd9f0b8838
4 changed files with 70 additions and 5 deletions
|
|
@ -5,9 +5,15 @@ from typing import Union
|
|||
@dataclass(frozen=True)
|
||||
class Type:
|
||||
name: str
|
||||
custom_ident_blacklist: list[str] | None
|
||||
|
||||
def dump(self, indent: int) -> str:
|
||||
return f"{'': >{indent}}Type: {self.name}\n"
|
||||
output = f"{'': >{indent}}Type: {self.name}"
|
||||
|
||||
if self.custom_ident_blacklist:
|
||||
output += f" ![{', '.join(self.custom_ident_blacklist)}]"
|
||||
|
||||
return output + "\n"
|
||||
|
||||
|
||||
# https://drafts.csswg.org/css-values-4/#component-types
|
||||
|
|
|
|||
|
|
@ -43,6 +43,33 @@ class Tokenizer:
|
|||
|
||||
raise SyntaxError("CSSGrammar::Tokenizer: Unexpected character")
|
||||
|
||||
def consume_custom_ident_blacklist(self) -> list[str]:
|
||||
# NB: This notation isn't yet included in the spec but we use it internally and the CSSWG has resolved to add it in
|
||||
# https://github.com/w3c/csswg-drafts/issues/11924
|
||||
|
||||
self.discard_whitespace()
|
||||
|
||||
if not self.lexer.consume_specific_string("!["):
|
||||
return []
|
||||
|
||||
blacklist = []
|
||||
|
||||
while True:
|
||||
self.discard_whitespace()
|
||||
|
||||
ident = self.consume_an_identifier()
|
||||
if not ident:
|
||||
raise SyntaxError("Expected identifier in custom-ident blacklist")
|
||||
|
||||
blacklist.append(ident)
|
||||
|
||||
self.discard_whitespace()
|
||||
if self.lexer.consume_specific_char("]"):
|
||||
return blacklist
|
||||
|
||||
if not self.lexer.consume_specific_char(","):
|
||||
raise SyntaxError("Expected ',' in custom-ident blacklist")
|
||||
|
||||
def consume_a_non_terminal_token(self) -> Token:
|
||||
assert self.lexer.consume_specific_char("<")
|
||||
|
||||
|
|
@ -51,9 +78,12 @@ class Tokenizer:
|
|||
if not name:
|
||||
raise SyntaxError("CSSGrammar::Tokenizer: Expected a type name")
|
||||
|
||||
# FIXME: Support custom-ident blacklist notation (i.e. <custom-ident ![foo, bar]>)
|
||||
custom_ident_blacklist = None
|
||||
if name == "custom-ident":
|
||||
custom_ident_blacklist = self.consume_custom_ident_blacklist()
|
||||
|
||||
# FIXME: Support numeric data type bracketed range notations (i.e. <integer [0,10]>)
|
||||
if not self.lexer.consume_specific_char(">"):
|
||||
raise SyntaxError("CSSGrammar::Tokenizer: Expected '>'")
|
||||
|
||||
return Token.create_component_value(Type(name))
|
||||
return Token.create_component_value(Type(name, custom_ident_blacklist))
|
||||
|
|
|
|||
|
|
@ -11,7 +11,18 @@ from Utils.utils import snake_casify
|
|||
|
||||
def generate_css_parser_expression_for_type_component_value(out: TextIO, cpp_name: str, type: Type) -> None:
|
||||
type_name = snake_casify(type.name)
|
||||
out.write(f"auto {cpp_name} = parse_{type_name}_value(tokens);\n")
|
||||
|
||||
additional_arguments = ""
|
||||
if type.custom_ident_blacklist is not None:
|
||||
additional_arguments = ", ReadonlySpan<StringView> { "
|
||||
|
||||
if len(type.custom_ident_blacklist) > 0:
|
||||
disallowed_idents = "".join(f'"{disallowed_ident}"sv, ' for disallowed_ident in type.custom_ident_blacklist)
|
||||
additional_arguments += f"Array<StringView, {len(type.custom_ident_blacklist)}> {{{disallowed_idents}}}"
|
||||
|
||||
additional_arguments += "}"
|
||||
|
||||
out.write(f"auto {cpp_name} = parse_{type_name}_value(tokens{additional_arguments});\n")
|
||||
|
||||
|
||||
def generate_css_parser_expression_for_component_value_grammar_node(
|
||||
|
|
|
|||
|
|
@ -28,6 +28,15 @@ class TestCSSGrammarParser(unittest.TestCase):
|
|||
""",
|
||||
)
|
||||
|
||||
def test_parse_custom_ident_blacklist(self) -> None:
|
||||
syntax = parse_value_definition_grammar("<custom-ident ![foo, bar-baz]>")
|
||||
self.assertEqual(
|
||||
syntax.dump(),
|
||||
"""ComponentValue
|
||||
Type: custom-ident ![foo, bar-baz]
|
||||
""",
|
||||
)
|
||||
|
||||
def test_parse_alternatives(self) -> None:
|
||||
syntax = parse_value_definition_grammar("<foo> | <bar> | <baz>")
|
||||
self.assertEqual(
|
||||
|
|
@ -67,7 +76,16 @@ class TestCSSGrammarParser(unittest.TestCase):
|
|||
parse_value_definition_grammar("<foo> |")
|
||||
|
||||
def test_reject_invalid_type_reference(self) -> None:
|
||||
for value in ("<>", "<foo", "<foo/auto>"):
|
||||
for value in (
|
||||
"<>",
|
||||
"<foo",
|
||||
"<foo/auto>",
|
||||
"<length ![foo]>",
|
||||
"<custom-ident ![]>",
|
||||
"<custom-ident ![foo bar]>",
|
||||
"<custom-ident ![foo,]>",
|
||||
"<custom-ident ![foo>",
|
||||
):
|
||||
with self.subTest(value=value):
|
||||
with self.assertRaises(SyntaxError):
|
||||
parse_value_definition_grammar(value)
|
||||
|
|
|
|||
Loading…
Reference in a new issue