2026-04-24 08:29:57 -03:00
|
|
|
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
|
2026-05-05 01:06:10 -03:00
|
|
|
from Utils.CSSGrammar.Parser.grammar_node import GroupGrammarNode
|
2026-05-05 07:32:41 -03:00
|
|
|
from Utils.CSSGrammar.Parser.grammar_node import OptionalGrammarNode
|
2026-04-24 08:29:57 -03:00
|
|
|
from Utils.CSSGrammar.Parser.token import Token
|
|
|
|
|
from Utils.CSSGrammar.Parser.token import TokenType
|
|
|
|
|
from Utils.CSSGrammar.Parser.tokenizer import Tokenizer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Parser:
|
|
|
|
|
def __init__(self, tokens: list[Token]) -> None:
|
|
|
|
|
self.tokens = tokens
|
|
|
|
|
self.index = 0
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def parse_value_definition_grammar(cls, input: str) -> GrammarNode:
|
|
|
|
|
parser = cls(Tokenizer.tokenize(input))
|
|
|
|
|
|
|
|
|
|
value = parser.parse_alternatives()
|
|
|
|
|
|
|
|
|
|
if not parser.peek().is_token_type(TokenType.END_OF_FILE):
|
|
|
|
|
raise SyntaxError("CSSGrammar::Parser: Unexpected trailing input")
|
|
|
|
|
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
def parse_alternatives(self) -> GrammarNode:
|
2026-05-05 23:22:44 -03:00
|
|
|
children = [self.parse_juxtaposition()]
|
2026-04-24 08:29:57 -03:00
|
|
|
|
|
|
|
|
while self.peek().is_token_type(TokenType.SINGLE_BAR):
|
|
|
|
|
self.consume()
|
2026-05-05 23:22:44 -03:00
|
|
|
children.append(self.parse_juxtaposition())
|
2026-04-24 08:29:57 -03:00
|
|
|
|
|
|
|
|
if len(children) == 1:
|
|
|
|
|
return children[0]
|
|
|
|
|
|
|
|
|
|
return CombinatorGrammarNode(CombinatorType.ALTERNATIVES, children)
|
|
|
|
|
|
2026-05-05 23:22:44 -03:00
|
|
|
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)
|
|
|
|
|
|
2026-04-24 08:29:57 -03:00
|
|
|
def parse_component_value(self) -> GrammarNode:
|
|
|
|
|
# https://drafts.csswg.org/css-values-4/#component-multipliers
|
|
|
|
|
# FIXME: Support component multipliers
|
|
|
|
|
|
2026-05-05 07:32:41 -03:00
|
|
|
peeked = self.peek()
|
|
|
|
|
component_value = None
|
|
|
|
|
|
|
|
|
|
if peeked.is_token_type(TokenType.OPEN_SQUARE_BRACKET):
|
2026-05-05 01:06:10 -03:00
|
|
|
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()
|
2026-05-05 07:32:41 -03:00
|
|
|
component_value = GroupGrammarNode(group)
|
|
|
|
|
|
|
|
|
|
if peeked.is_token_type(TokenType.COMPONENT_VALUE):
|
|
|
|
|
component_value = ComponentValueGrammarNode(self.consume().component_value())
|
2026-05-05 01:06:10 -03:00
|
|
|
|
2026-05-05 07:32:41 -03:00
|
|
|
if component_value is None:
|
2026-04-24 08:29:57 -03:00
|
|
|
raise SyntaxError("CSSGrammar::Parser: Expected a component value")
|
|
|
|
|
|
2026-05-05 07:32:41 -03:00
|
|
|
if self.peek().is_token_type(TokenType.QUESTION_MARK):
|
|
|
|
|
self.consume()
|
|
|
|
|
component_value = OptionalGrammarNode(component_value)
|
|
|
|
|
|
|
|
|
|
return component_value
|
2026-04-24 08:29:57 -03:00
|
|
|
|
2026-05-05 23:22:44 -03:00
|
|
|
def next_token_starts_component_value(self) -> bool:
|
|
|
|
|
return self.peek().token_type in (
|
|
|
|
|
TokenType.OPEN_SQUARE_BRACKET,
|
|
|
|
|
TokenType.COMPONENT_VALUE,
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-24 08:29:57 -03:00
|
|
|
def peek(self, offset: int = 0) -> Token:
|
|
|
|
|
index = min(self.index + offset, len(self.tokens) - 1)
|
|
|
|
|
return self.tokens[index]
|
|
|
|
|
|
|
|
|
|
def consume(self) -> Token:
|
|
|
|
|
token = self.peek()
|
|
|
|
|
|
|
|
|
|
if not token.is_token_type(TokenType.END_OF_FILE):
|
|
|
|
|
self.index += 1
|
|
|
|
|
|
|
|
|
|
return token
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_value_definition_grammar(input: str) -> GrammarNode:
|
|
|
|
|
return Parser.parse_value_definition_grammar(input)
|