Skip to content

Instantly share code, notes, and snippets.

@zdimension
Created February 26, 2021 15:14
Show Gist options
  • Save zdimension/986b66787cb93d56f8d21b5ecee58e82 to your computer and use it in GitHub Desktop.
Save zdimension/986b66787cb93d56f8d21b5ecee58e82 to your computer and use it in GitHub Desktop.
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