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.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 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: children = [self.parse_juxtaposition()] while self.peek().is_token_type(TokenType.SINGLE_BAR): self.consume() children.append(self.parse_juxtaposition()) if len(children) == 1: return children[0] 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: # https://drafts.csswg.org/css-values-4/#component-multipliers # FIXME: Support component multipliers peeked = self.peek() component_value = None if peeked.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. [ ? ]!) self.consume() component_value = GroupGrammarNode(group) 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") if self.peek().is_token_type(TokenType.QUESTION_MARK): self.consume() component_value = OptionalGrammarNode(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: 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)