Skip to content

Instantly share code, notes, and snippets.

@Inndy
Created February 21, 2025 09:18
Show Gist options
  • Save Inndy/9ce2757b7732025385bbb66a03589693 to your computer and use it in GitHub Desktop.
Save Inndy/9ce2757b7732025385bbb66a03589693 to your computer and use it in GitHub Desktop.
import re
import operator
import readline
units = {
None: 1,
'm': 1e6,
'b': 1e9,
't': 1e12,
'qd': 1e15,
'qt': 1e18,
'st': 1e21,
}
expr_re = re.compile(r'''
(?P<val>\d+(?:\.\d*)?)(?P<unit>m|b|t|qd|qt|st)?|
(?P<pair>[()])|
(?P<op>[-+*/])|
(?P<spaces>\s+)
''', re.VERBOSE)
leveled_ops = (
{
'+': operator.add,
'-': operator.sub,
},
{
'*': operator.mul,
'/': operator.truediv,
},
)
def eval_tree(node: list, depth: int = 0) -> float:
#indent = ' ' * (4 * depth)
if type(node) is float:
return node
if len(node) == 1:
return eval_tree(node[0], depth)
#print(indent + 'eval:', node)
for ops in leveled_ops:
if indexes := [
i
for i, e in enumerate(node)
if type(e) is not list and e in ops
]:
val = eval_tree(node[:indexes[0]], depth + 1)
for i, j in zip(indexes, indexes[1:] + [len(node)]):
op = node[i]
rnode = node[i+1:j]
#print(indent + 'calc -> %s %s %s' % (val, op, rnode))
val = ops[op](val, eval_tree(rnode, depth + 1))
#print(indent + '.... =', val)
return val
def calc(expr):
stack = [[]]
last_i = 0
for m in expr_re.finditer(expr):
begin, end = m.span()
if begin != last_i:
raise ValueError('Bad token: %r' % expr[last_i:begin])
last_i = end
match m.groups():
case val, unit, None, None, None:
n = float(val) * units[unit]
stack[-1].append(n)
case None, None, '(', None, None:
new_node = []
stack[-1].append(new_node)
stack.append(new_node)
case None, None, ')', None, None:
if len(stack) <= 1:
raise ValueError('Unbalanced parentheses')
stack.pop()
case None, None, None, op, None:
stack[-1].append(op)
case None, None, None, None, space:
pass
case _:
assert 0
if last_i != len(expr):
raise ValueError('Bad token: %r' % expr[last_i:])
return eval_tree(stack[0])
#tests = (
# '1+2-3*45qt',
# '234-(1*(2-9/3)-(1m-2b+3qd/4t+ 5t-6st))',
# '((((((((((((((((((((((1))))))))))))))))))))))'
#)
#
#for i in tests:
# print('>> '+ i)
# calc(i)
try:
while expr := input('>> ').strip():
result = calc(expr)
for unit, v in reversed(units.items()):
if result >= v:
break
print('%g%s' % (result / v, unit or ''))
except EOFError:
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment