Source code for yadr.lex

"""
lex
~~~

A lexer for `yadr` dice notation.
"""
from typing import Optional

from yadr import maps, pools
from yadr.base import BaseLexer, ResultMethod, StateMethod
from yadr.model import CompoundResult, Result, Token, TokenInfo, symbols


# Lexers.
[docs] class Lexer(BaseLexer): """A state-machine to lex :ref:`YADN` dice notation.""" def __init__(self) -> None: state_map: dict[Token, StateMethod] = { Token.START: self._start, Token.AS_OPERATOR: self._as_operator, Token.BOOLEAN: self._boolean, Token.CHOICE_OPERATOR: self._choice_operator, Token.COMPARISON_OPERATOR: self._comparison_operator, Token.DICE_OPERATOR: self._dice_operator, Token.EX_OPERATOR: self._ex_operator, Token.GROUP_OPEN: self._group_open, Token.GROUP_CLOSE: self._group_close, Token.MAP: self._map, Token.MAP_END: self._map_end, Token.MAPPING_OPERATOR: self._mapping_operator, Token.MD_OPERATOR: self._md_operator, Token.NUMBER: self._number, Token.OPTIONS_OPERATOR: self._options_operator, Token.POOL: self._pool, Token.POOL_DEGEN_OPERATOR: self._pool_degen_operator, Token.POOL_END: self._pool_end, Token.POOL_GEN_OPERATOR: self._pool_gen_operator, Token.POOL_OPERATOR: self._pool_operator, Token.QUALIFIER: self._qualifier, Token.QUALIFIER_END: self._qualifier_end, Token.ROLL_DELIMITER: self._roll_delimiter, Token.U_POOL_DEGEN_OPERATOR: self._u_pool_degen_operator, Token.WHITESPACE: self._whitespace, Token.END: self._start, } bracket_states: dict[Token, Token] = { Token.MAP_OPEN: Token.MAP, Token.NEGATIVE_SIGN: Token.NUMBER, Token.QUALIFIER_DELIMITER: Token.QUALIFIER, Token.POOL_OPEN: Token.POOL, } bracket_ends: dict[Token, Token] = { Token.MAP: Token.MAP_END, Token.QUALIFIER: Token.QUALIFIER_END, Token.POOL: Token.POOL_END, } symbol_map: dict[Token, list[str]] = symbols result_map: dict[Token, ResultMethod] = { Token.BOOLEAN: self._tf_boolean, Token.MAP: self._tf_maps, Token.NUMBER: self._tf_number, Token.POOL: self._tf_pool, Token.QUALIFIER: self._tf_qualifier, } no_store: list[Token] = [ Token.WHITESPACE, Token.START, Token.MAP_END, Token.POOL_END, Token.QUALIFIER_END, ] super().__init__( state_map, symbol_map, bracket_states, bracket_ends, result_map, no_store, Token.START ) self.process = self._start # Value transforms. def _tf_boolean(self, value: str) -> bool: if value == 'T': return True return False def _tf_maps(self, value: str) -> tuple[str, dict]: mlexer = maps.Lexer() lexed = mlexer.lex(value) mparser = maps.Parser() parsed = mparser.parse(lexed) return parsed def _tf_number(self, value: str) -> int: return int(value) def _tf_pool(self, value: str) -> tuple[int, ...]: plexer = pools.Lexer() pparser = pools.Parser() lexed = plexer.lex(value) return pparser.parse(lexed) def _tf_qualifier(self, value: str) -> str: return value[1:-1] # Lexing rules. def _as_operator(self, char: str) -> None: """Processing an operator.""" can_follow = [ Token.NUMBER, Token.NEGATIVE_SIGN, Token.GROUP_OPEN, Token.U_POOL_DEGEN_OPERATOR, Token.WHITESPACE, ] self._check_char(char, can_follow) def _boolean(self, char: str) -> None: """Processing a boolean.""" can_follow = [ Token.CHOICE_OPERATOR, Token.WHITESPACE, ] self._check_char(char, can_follow) def _choice_operator(self, char: str) -> None: """Processing a choice operator.""" can_follow = [ Token.QUALIFIER, Token.QUALIFIER_DELIMITER, Token.CHOICE_OPTIONS, Token.WHITESPACE, ] self._check_char(char, can_follow) def _comparison_operator(self, char: str) -> None: """Processing a comparison operator.""" can_follow = [ Token.GROUP_OPEN, Token.NEGATIVE_SIGN, Token.NUMBER, Token.U_POOL_DEGEN_OPERATOR, Token.WHITESPACE, ] self._check_char(char, can_follow) def _dice_operator(self, char: str) -> None: """Processing an operator.""" can_follow = [ Token.NUMBER, Token.NEGATIVE_SIGN, Token.GROUP_OPEN, Token.U_POOL_DEGEN_OPERATOR, Token.WHITESPACE, ] self._check_char(char, can_follow) def _ex_operator(self, char: str) -> None: """Processing an operator.""" can_follow = [ Token.NUMBER, Token.NEGATIVE_SIGN, Token.GROUP_OPEN, Token.U_POOL_DEGEN_OPERATOR, Token.WHITESPACE, ] self._check_char(char, can_follow) def _group_close(self, char: str) -> None: """Processing a close group token.""" can_follow = [ Token.AS_OPERATOR, Token.MD_OPERATOR, Token.EX_OPERATOR, Token.DICE_OPERATOR, Token.GROUP_CLOSE, Token.POOL_OPERATOR, Token.POOL_GEN_OPERATOR, Token.ROLL_DELIMITER, Token.WHITESPACE, ] self._check_char(char, can_follow) def _group_open(self, char: str) -> None: """Processing an open group token.""" can_follow = [ Token.GROUP_OPEN, Token.NUMBER, Token.NEGATIVE_SIGN, Token.POOL_OPEN, Token.U_POOL_DEGEN_OPERATOR, Token.WHITESPACE, ] self._check_char(char, can_follow) def _map_end(self, char: str) -> None: """Processing a choice operator.""" can_follow = [ Token.ROLL_DELIMITER, Token.WHITESPACE, ] self._check_char(char, can_follow) def _map(self, char: str) -> None: """Processing a choice operator.""" self.buffer += char if self._is_token_start(Token.MAP_CLOSE, char): new_state = Token.MAP_END self._change_state(new_state, char) def _mapping_operator(self, char: str) -> None: can_follow = [ Token.QUALIFIER, Token.QUALIFIER_DELIMITER, Token.WHITESPACE, ] self._check_char(char, can_follow) def _md_operator(self, char: str) -> None: """Processing an operator.""" can_follow = [ Token.NUMBER, Token.NEGATIVE_SIGN, Token.GROUP_OPEN, Token.U_POOL_DEGEN_OPERATOR, Token.WHITESPACE, ] self._check_char(char, can_follow) def _number(self, char: str) -> None: """Processing a number.""" can_follow = [ Token.AS_OPERATOR, Token.COMPARISON_OPERATOR, Token.DICE_OPERATOR, Token.EX_OPERATOR, Token.GROUP_CLOSE, Token.MAPPING_OPERATOR, Token.MD_OPERATOR, Token.POOL_GEN_OPERATOR, Token.ROLL_DELIMITER, Token.WHITESPACE, ] # Check here if the character is a digit because the checks in # Char are currently limited to tokens that no longer than two # characters. Check if the state is a number because white # space also ends up here, and we want white space to separate # numbers. if char.isdigit() and self.state == Token.NUMBER: self.buffer += char else: self._check_char(char, can_follow) def _options_operator(self, char: str) -> None: """Processing an options operator.""" can_follow = [ Token.QUALIFIER_DELIMITER, Token.WHITESPACE, ] self._check_char(char, can_follow) def _pool(self, char: str) -> None: """Processing a pool.""" self.buffer += char if self._is_token_start(Token.POOL_CLOSE, char): new_state = Token.POOL_END self._change_state(new_state, char) def _pool_end(self, char: str) -> None: """Processing after a pool.""" can_follow = [ Token.GROUP_CLOSE, Token.POOL_DEGEN_OPERATOR, Token.POOL_OPERATOR, Token.ROLL_DELIMITER, Token.WHITESPACE, ] self._check_char(char, can_follow) def _pool_gen_operator(self, char: str) -> None: """Processing an pool generation operator.""" can_follow = [ Token.NUMBER, Token.NEGATIVE_SIGN, Token.GROUP_OPEN, Token.U_POOL_DEGEN_OPERATOR, Token.WHITESPACE, ] self._check_char(char, can_follow) def _qualifier(self, char: str) -> None: """Processing a qualifier.""" self.buffer += char if self._is_token_start(Token.QUALIFIER_DELIMITER, char): new_state = Token.QUALIFIER_END self._change_state(new_state, char) def _qualifier_end(self, char: str) -> None: """Process after a qualifier.""" can_follow = [ Token.OPTIONS_OPERATOR, Token.ROLL_DELIMITER, Token.WHITESPACE, ] self._check_char(char, can_follow) def _pool_degen_operator(self, char: str) -> None: """Processing a pool degeneration operator.""" can_follow = [ Token.NUMBER, Token.NEGATIVE_SIGN, Token.GROUP_OPEN, Token.U_POOL_DEGEN_OPERATOR, Token.WHITESPACE, ] self._check_char(char, can_follow) def _pool_operator(self, char: str) -> None: """Lex pool operators.""" can_follow = [ Token.GROUP_OPEN, Token.NUMBER, Token.NEGATIVE_SIGN, Token.U_POOL_DEGEN_OPERATOR, Token.WHITESPACE, ] self._check_char(char, can_follow) def _roll_delimiter(self, char: str) -> None: """Lex roll delimiters.""" can_follow = [ Token.MAP_OPEN, Token.NUMBER, Token.NEGATIVE_SIGN, Token.BOOLEAN, Token.GROUP_OPEN, Token.POOL_OPEN, Token.U_POOL_DEGEN_OPERATOR, Token.QUALIFIER_DELIMITER, Token.QUALIFIER, Token.BOOLEAN, Token.WHITESPACE, ] self._check_char(char, can_follow) def _start(self, char: str) -> None: """The starting state.""" if self.tokens: self.tokens = [] self._roll_delimiter(char) def _u_pool_degen_operator(self, char: str) -> None: """Processing a unary pool degeneration operator.""" can_follow = [ Token.GROUP_OPEN, Token.NUMBER, Token.NEGATIVE_SIGN, Token.POOL_OPEN, Token.U_POOL_DEGEN_OPERATOR, Token.WHITESPACE, ] self._check_char(char, can_follow)