import re from enum import Enum from dataclasses import dataclass class TokenType(Enum): INTEGER = 1 FLOATING = 2 PLUS = 3, MINUS = 4, MULT = 5, DIV = 6, OPEN_PAREN = 7, CLOSE_PAREN = 8 @dataclass class Token: type: TokenType value: str | int def tokenize(expr): scanner = re.Scanner(( (r"\d+", lambda scanner,token: Token(TokenType.INTEGER, token)), (r"\d+\.\d", lambda scanner,token: Token(TokenType.FLOATING, token)), (r"\+", lambda scanner,token: Token(TokenType.PLUS, token)), (r"\-", lambda scanner,token: Token(TokenType.MINUS, token)), (r"\*", lambda scanner,token: Token(TokenType.MULT, token)), (r"\/", lambda scanner,token: Token(TokenType.DIV, token)), (r"\(", lambda scanner,token: Token(TokenType.OPEN_PAREN, token)), (r"\)", lambda scanner,token: Token(TokenType.CLOSE_PAREN, token)), (r"\s+", None) )) tokens, unknown = scanner.scan(expr) assert len(unknown) == 0, f"Unknown token: {unknown}" return tokens class Parser: def __init__(self, tokens): self.tokens = tokens self.pos = 0 def _accept(self, token_type) -> Token | None: if self.pos < len(self.tokens) and self.tokens[self.pos].type == token_type: token = self.tokens[self.pos] self.pos += 1 return token return None def parse_expr(self): tree = self.parse_expr_plus() assert self.pos == len(self.tokens), "Not all tokens where consumed!" return tree def parse_expr_plus(self): lhs = self.parse_expr_mult() while ((token := self._accept(TokenType.PLUS)) is not None or (token := self._accept(TokenType.MINUS)) is not None): rhs = self.parse_expr_mult() if token.type == TokenType.PLUS: lhs += rhs elif token.type == TokenType.MINUS: lhs -= rhs else: raise SyntaxError(f"Unexpected token {token[0]}") return lhs def parse_expr_mult(self): lhs = self.parse_primary() while ((token := self._accept(TokenType.MULT)) is not None or (token := self._accept(TokenType.DIV)) is not None): if token.type == TokenType.MULT: lhs *= self.parse_primary() elif token.type == TokenType.DIV: lhs /= self.parse_primary() else: raise SyntaxError(f"Unexpected token {token[0]}") return lhs def parse_primary(self): assert self.pos <= len(self.tokens) sign = 1 if self._accept(TokenType.MINUS) is not None: sign *= -1 if (token := self._accept(TokenType.INTEGER)) is not None: return sign * int(token.value) elif (token := self._accept(TokenType.OPEN_PAREN)) is not None: expr = self.parse_expr_plus() if self._accept(TokenType.CLOSE_PAREN) is None: raise SyntaxError(f"Expected token {TokenType.CLOSE_PAREN}") return sign * expr assert False, f"Unexpected token type for parse_primary: {self.tokens[self.pos][0]}" def calc(expression): p = Parser(tokenize(expression)) try: return p.parse_expr() except: print(expression) print(p.tokens) raise