"""Calculator, worte in AST in python
"""
from ast import literal_eval
from math import inf
def ADD(x, y):
return str(literal_eval(x) + literal_eval(y))
def SUB(x, y):
return str(literal_eval(x) - literal_eval(y))
def POW(x, y):
return str(literal_eval(x) ** literal_eval(y))
def MUL(x, y):
return str(literal_eval(x) * literal_eval(y))
def DIV(x, y):
return str(literal_eval(x) / literal_eval(y))
operations = {
'+': [1, ADD],
'-': [1, SUB],
'*': [2, MUL],
'/': [2, DIV],
'^': [3, POW]
}
parens = {
'(': 1,
')': -1
}
class TreeNode(object):
__slots__ = ('val', 'left', 'right')
def __init__(self, val=None, left=None, right=None):
self.val, self.left, self.right = val, left, right
def __repr__(self):
left, right = ' ', ' '
if self.left is not None:
left = repr(self.left) + ' <- '
if self.right is not None:
right = ' -> ' + repr(self.right)
return '(' + left + repr(self.val) + right + ')'
def lexer(string):
result = list()
if not string:
return result
head, tail = 0, 0
numeric = False
while tail < len(string):
if string[tail] in {'+', '-'} and numeric:
numeric = False
result.append(('SYMB', string[tail]))
elif (string[tail].isdecimal() or string[tail] == '.') or (string[tail] in {'+', '-'} and not numeric):
numeric = True
head = tail
tail += 1
while tail < len(string) and (string[tail].isdecimal() or string[tail] == '.'):
tail += 1
num = string[head:tail]
if num in {'+', '-'}:
num += '1'
result.append(('NUM', num))
tail -= 1
elif string[tail] in {'*', '/', '^', '(', ')'}:
symb = 'PAREN' if string[tail] in parens else 'SYMB'
if numeric and string[tail] == '(':
result.append(('SYMB', '*'))
numeric = True if string[tail] == ')' else False
result.append((symb, string[tail]))
tail += 1
return result
def parser(tokens):
remove_enclosing_parans_flag = True
while remove_enclosing_parans_flag:
if len(tokens) == 0:
return
remove_enclosing_parans_flag = remove_enclosing_parans_flag and (tokens[0][1] == '(' and tokens[-1][1] == ')')
depth = 0
for TYPE, TOKEN in tokens[:-1]:
depth += parens.get(TOKEN, 0)
if depth == 0:
remove_enclosing_parans_flag = remove_enclosing_parans_flag and (depth != 0)
break
if remove_enclosing_parans_flag:
tokens = tokens[1:-1]
depth = 0
min_priority = inf
min_priority_index = None
for i, (TYPE, TOKEN) in enumerate(tokens):
if TYPE == 'PAREN':
depth += parens[TOKEN]
elif TYPE == 'SYMB' and depth == 0:
if operations[TOKEN][0] < min_priority:
min_priority_index, min_priority = i, operations[TOKEN][0]
if min_priority_index is None:
if tokens[0][1] in parens or tokens[0][1] in operations:
return
else:
return TreeNode(tokens[0][1])
left = parser(tokens[:min_priority_index])
right = parser(tokens[min_priority_index + 1:])
return TreeNode(tokens[min_priority_index][1], left, right)
def solve(ast_root, DEBUG=False):
"""Traverse the AST and do calculation
"""
if ast_root is None:
raise ValueError('AST cannot be None')
if ast_root.left is ast_root.right is None:
return ast_root.val
else:
if DEBUG:
left = solve(ast_root.left, DEBUG)
right = solve(ast_root.right, DEBUG)
result = operations[ast_root.val][1](left, right)
print('Calculating:', ast_root.val, ast_root.left, '|', ast_root.right, '=>', left, ast_root.val, right, '=', result)
return result
else:
return operations[ast_root.val][1](solve(ast_root.left), solve(ast_root.right))
def calculator(string):
return solve(parser(lexer(string)))
def main():
DEBUG = False
prompt = ['Expression > ', 'DEBUG > ']
try:
while 1:
string = input(prompt[DEBUG])
if string.upper().strip() == 'DEBUG':
DEBUG = not DEBUG
continue
if DEBUG:
print('Atoms :', list(zip(*lexer(string)))[1])
print('Tree :', repr(parser(lexer(string))))
print('PyResult :', eval(string.replace('^', '**')))
print('MyResult :', solve(parser(lexer(string)), DEBUG))
else:
print('Result:', calculator(string))
except (EOFError, KeyboardInterrupt):
exit()
if __name__ == '__main__':
main()