Created
February 26, 2021 15:14
-
-
Save zdimension/986b66787cb93d56f8d21b5ecee58e82 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from dataclasses import dataclass | |
from typing import Any, Callable | |
from enum import Enum | |
import operator | |
import string | |
@dataclass | |
class BinOperator: | |
symbol: str | |
priority: int | |
perform: Callable | |
OPERATORS = [ | |
BinOperator("+", 0, operator.add), | |
BinOperator("-", 0, operator.sub), | |
BinOperator("*", 1, operator.mul), | |
BinOperator("/", 1, operator.truediv) | |
] | |
MAX_PRIORITY = max(op.priority for op in OPERATORS) | |
def ops_by_priority(priority): | |
return [op for op in OPERATORS if op.priority == priority] | |
ops_syms = [op.symbol for op in OPERATORS] | |
class TokenType(Enum): | |
NUMBER = 1 | |
PARENTHESIS = 2 | |
OPERATION = 3 | |
@dataclass | |
class Token: | |
type: TokenType | |
val: Any | |
def tokenize(inp: str): | |
tokens = [] | |
index = 0 | |
def skip_spaces(): | |
nonlocal index | |
while inp[index].isspace(): | |
index += 1 | |
def has(): | |
return index < len(inp) | |
def peek(): | |
return inp[index] | |
def read(): | |
nonlocal index | |
index += 1 | |
return inp[index - 1] | |
VALID_NUMBER_CHARS = "0123456789." | |
def read_number(): | |
res = "" | |
while True: | |
res += read() | |
if not has() or peek() not in VALID_NUMBER_CHARS: | |
break | |
return Token(TokenType.NUMBER, float(res) if "." in res else int(res)) | |
while has(): | |
skip_spaces() | |
next = peek() | |
if next in ops_syms: | |
tok = Token(TokenType.OPERATION, read()) | |
elif next in "()": | |
tok = Token(TokenType.PARENTHESIS, read()) | |
elif next in VALID_NUMBER_CHARS: | |
tok = read_number() | |
else: | |
raise ParseError(f"invalid character '{next}'", index) | |
tokens.append(tok) | |
return tokens | |
def parse(tokens): | |
index = 0 | |
def has(): | |
return index < len(tokens) | |
def current(): | |
if not has(): | |
raise Exception("expected token, got EOL") | |
return tokens[index] | |
def match(type: TokenType, val: Any = None): | |
return has() and tokens[index].type == type and (val is None or tokens[index].val == val) | |
def accept(type: TokenType, val: Any = None): | |
nonlocal index | |
if match(type, val): | |
index += 1 | |
return True | |
return False | |
def expect(type: TokenType, val: Any = None): | |
nonlocal index | |
if match(type, val): | |
index += 1 | |
return tokens[index - 1] | |
if not has(): | |
raise Exception(f"expected {type}, got EOL") | |
else: | |
raise Exception(f"expected {type}, got {current().type}") | |
def parse_bin(priority=0): | |
if priority > MAX_PRIORITY: | |
return parse_term() | |
left = parse_bin(priority + 1) | |
ops = ops_by_priority(priority) | |
while has() and current().type == TokenType.OPERATION: | |
for op in ops: | |
if accept(TokenType.OPERATION, op.symbol): | |
right = parse_bin(priority + 1) | |
left = op.perform(left, right) | |
break | |
else: | |
break | |
return left | |
def parse_term(): | |
token = current() | |
if token.type == TokenType.NUMBER: | |
return expect(TokenType.NUMBER).val | |
elif accept(TokenType.PARENTHESIS, "("): | |
val = parse_expr() | |
expect(TokenType.PARENTHESIS, ")") | |
return val | |
else: | |
raise Exception(f"expected term, got {token.type}") | |
def parse_expr(): | |
return parse_bin() | |
return parse_expr() | |
while True: | |
inp = input("> ") | |
try: | |
tok = tokenize(inp) | |
res = parse(tok) | |
print(res) | |
except Exception as e: | |
print(e) | |
print() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment