Skip to content

Instantly share code, notes, and snippets.

@silphendio
Created February 2, 2024 04:26
Show Gist options
  • Save silphendio/d39ed56e239d0c701c2bd7660c19f519 to your computer and use it in GitHub Desktop.
Save silphendio/d39ed56e239d0c701c2bd7660c19f519 to your computer and use it in GitHub Desktop.
a simple math expression parser
# This is a simple math expression parser
# it solves expressions the way we learned it in math class
# and optionally prints the in-between steps
#
# malformed input produces either ValueError or IndexError
# supported are numbers (int, float, complex), parentheses, and operators (+ , - , / , * and ^)
# ^ is used for exponents, because ** would be a pain to implement
#
# licensed under Zero-Clause BSD (https://opensource.org/license/0bsd/)
# - silphendio
dbg = False
def dbg_array(arr):
if dbg: print(' '.join(map(str, arr)))
def str_to_number(x):
try:
return int(x)
except ValueError:
try:
return float(x)
except ValueError:
return complex(x)
# calculations
def _calc_prefix(x, op):
if op == '+':
return x
if op == '-':
return -x
raise ValueError("invalid operand")
def _calc_infix(a, b, op):
if op == '+':
return a + b
if op == '-':
return a - b
if op == '*':
return a * b
if op == '/':
return a / b
if op == '^':
return a ** b
raise ValueError("invalid operand")
# helper functions
def _get_matching_paren(tokens, lp_idx):
p_count = 1
for i in range(lp_idx + 1, len(tokens)):
p_count += (tokens[i] == '(')
p_count -= (tokens[i] == ')')
if p_count == 0:
return i
raise ValueError("mismatched parenthesis")
def _is_op(token, ops = '+-/*()^'):
return token in iter(ops)
def solve_expr(expr):
tokens = []
j = 0
for i, x in enumerate(expr):
if x in '()*/+-^':
if expr[j:i].strip() != "":
tokens += [expr[j:i].strip()]
tokens += [x]
j = i + 1
if expr[j:].strip() != '':
tokens += [expr[j:].strip()]
for i, tok in enumerate(tokens):
if not _is_op(tok):
tokens[i] = str_to_number(tok)
return _solve(tokens)
def _solve(tokens):
dbg_array(tokens)
# ( )
while '(' in tokens:
lp_idx = tokens.index('(')
rp_idx = _get_matching_paren(tokens, lp_idx)
tokens[lp_idx : rp_idx+1] = [_solve(tokens[lp_idx+1 : rp_idx])]
dbg_array(tokens)
# prefix + -
i = 0
while i < len(tokens):
if _is_op(tokens[i], '+-') and (i==0 or _is_op(tokens[i-1])) and not _is_op(tokens[i+1]):
tokens[i: i+2] = [_calc_prefix(tokens[i+1], tokens[i])]
dbg_array(["="] + tokens)
i += 1
# do ^ then * / then + -
for ops in ['^', '*/', '+-']:
i = 0
while i < len(tokens):
if _is_op(tokens[i], ops):
tokens[i-1: i+2] = [_calc_infix(tokens[i-1], tokens[i+1], tokens[i])]
dbg_array(["="] + tokens)
i -= 1
i += 1
if len(tokens) > 1:
raise ValueError("can't finish calculation: " + ' '.join(map(str, tokens)))
return tokens[0]
if __name__ == "__main__":
import sys
dbg = True
if len(sys.argv) > 1:
solve_expr(sys.argv[1])
exit()
while True:
expr = input(">>>")
try:
result = solve_expr(expr)
print("-------------------------")
print(result)
except ValueError as error:
print("error: " + error)
except Exception:
print("syntax error")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment