arith-eval/main.py

118 lines
2.5 KiB
Python

import dataclasses
import enum
import logging
import string
from rich.logging import RichHandler
logging.basicConfig(
level="INFO", format="%(message)s", datefmt="[%X]", handlers=[RichHandler()]
)
log = logging.getLogger(__name__)
class TokenType(enum.Enum):
DIGIT = enum.auto()
OPERATOR = enum.auto()
@dataclasses.dataclass
class Token:
token_type: TokenType
literal: str
precedence: int
line: int
column: int
@classmethod
def from_char(cls, char: str, line: int, column: int) -> "Token | None":
if char.isdigit():
token_type = TokenType.DIGIT
elif char in "*/+-":
token_type = TokenType.OPERATOR
else:
return None
return cls(
token_type=token_type,
literal=char,
precedence=get_precedence(char),
line=line,
column=column,
)
def get_precedence(char: str):
match char:
case "*" | "/":
return 2
case "+" | "-":
return 1
case _:
return 0
def get_expr() -> str:
return "1 + 2\n * 6 / 2 * oarsetaortsn6 / 2"
def goto_address(line, col) -> str:
return f"({line}:{col})"
def tokenize_expr(expr: str) -> list[Token]:
stack = list()
line = 1
col = 0
for c in expr:
col += 1
if c in string.whitespace:
if c == "\n":
line += 1
col = 0
continue
current_token = Token.from_char(c, line, col)
if not current_token:
log.warning("Unexpected char %s: %s", goto_address(line, col), repr(c))
log.info("Ignoring char %s: %s", goto_address(line, col), repr(c))
continue
stack.append(current_token)
return stack
def main() -> int:
expr = get_expr()
log.info(f"{expr=}")
expr_tokenized = tokenize_expr(expr)
log.info(f"{expr_tokenized=}")
rpn: list[Token] = []
stack: list[Token] = []
for token in expr_tokenized:
log.info(
f"{goto_address(token.line, token.column)} {token.literal=} {token.precedence=}"
)
if token.literal.isdigit():
rpn.append(token)
elif token.literal in "*/-+":
while stack and stack[-1].precedence >= token.precedence:
rpn.append(stack.pop())
stack.append(token)
while stack:
rpn.append(stack.pop())
log.info("RPN: %s", [c.literal for c in rpn])
return 0
if __name__ == "__main__":
exit(main())