Skip to content

Instantly share code, notes, and snippets.

@davefernig
Created May 17, 2022 07:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davefernig/21c8facd98bb4c7a6dcc0e867e575adc to your computer and use it in GitHub Desktop.
Save davefernig/21c8facd98bb4c7a6dcc0e867e575adc to your computer and use it in GitHub Desktop.
import sys
import re
VARIABLE_ASSIGNMENT_REGEX = r'[a-zA-Z_]*='
SQR = 'SQR'
ABS = 'ABS'
INI = '^'
FIN = '$'
QTM = '\"'
EQO = '\='
OPP = '\('
CLP = '\)'
ADD = '\+'
SUB = '\-'
MLT = '\*'
DIV = '\/'
VAR = '[a-zA-Z]([a-zA-Z0-9_]*)'
INT = '\d+'
FLT = '(((\d+)\.(\d+))|(\.(\d+))|((\d+)\.))'
VAL = "(({})|({})|({}))".format(VAR, INT, FLT)
variables = {
"X": 0,
"Y": 1,
}
def replace_spaces_in_non_string_literals(expression):
in_string_literal = False
cleaned_expression = ""
for char in expression:
append = char
if char == '"' and not in_string_literal:
in_string_literal = True
elif char == '"' and in_string_literal:
in_string_literal = False
elif char == " " and not in_string_literal:
append = ""
cleaned_expression += append
return cleaned_expression
def evaluate(expression):
expression = replace_spaces_in_non_string_literals(expression)
# QUOTES
if re.match(INI + QTM, expression):
i = 1
q_expression = ""
while i < len(expression) and expression[i] != '"':
q_expression += expression[i]
i += 1
if expression[i] != '"':
raise(Exception("Unmatched quotation marks in {}").format(expression))
if i == len(expression)-1:
return q_expression
p_value = evaluate(p_expression)
if i >= len(expression):
return p_value
elif expression[i] == "+":
return evaluate(str(p_value) + "+" + expression[(i+1):])
elif expression[i] == "-":
return evaluate(str(p_value) + "-" + expression[(i+1):])
elif expression[i] == "*":
return evaluate(str(p_value) + "*" + expression[(i+1):])
elif expression[i] == "/":
return evaluate(str(p_value) + "/" + expression[(i+1):])
else:
raise(Exception("Syntax error: {}").format(expression))
# STRING LITERALS
# if expression.startswith('"'):
# if expression.endswith('"'):
# return expression.strip('"')
# else:
# raise(Exception("Invalid expression: {}").format(expression))
# NOT A STRING, SO SAFE TO REMOVE SPACES
# expression = replace_spaces_in_non_string_literals(expression)
# NUMERIC LITERALS AND VARIABLES
if re.match(INI+INT+FIN, expression):
return int(expression)
elif re.match(INI+FLT+FIN, expression):
return float(expression)
elif re.match(INI+VAR+FIN, expression):
return variables[expression]
# EQUALITY
elif re.match(INI + VAL + EQO, expression):
if re.match(INI + VAL + EQO + VAL + FIN, expression):
lhs, rhs = expression.split("=", 1)
return evaluate(lhs) == evaluate(rhs)
if re.match(INI + VAL + EQO + OPP, expression):
lhs, rhs = expression.split("=", 1)
return evaluate(lhs) == evaluate(rhs)
# ADDITION
elif re.match(INI + VAL + ADD, expression):
# GREEDY SUB-CASES: ADD IMMEDIATELY
if re.match(INI + VAL + ADD + VAL + FIN, expression):
lhs, rhs = expression.split("+", 1)
return evaluate(lhs) + evaluate(rhs)
if re.match(INI + VAL + ADD + VAL + ADD, expression):
s1, s2 = expression.split("+", 1)
s2, s3 = s2.split("+", 1)
return evaluate(str(evaluate(s1) + evaluate(s2)) + "+" + s3)
if re.match(INI + VAL + ADD + VAL + SUB, expression):
s1, s2 = expression.split("+", 1)
s2, s3 = s2.split("-", 1)
return evaluate(str(evaluate(s1) + evaluate(s2)) + "-" + s3)
# LAZY SUB-CASES: EVALUATE FIRST
if re.match(INI + VAL + ADD + VAL + MLT, expression):
s1, s2= expression.split("+", 1)
return evaluate(s1) + evaluate(s2)
if re.match(INI + VAL + ADD + VAL + DIV, expression):
s1, s2 = expression.split("+", 1)
return evaluate(s1) + evaluate(s2)
if re.match(INI + VAL + ADD + VAL + OPP, expression):
s1, s2 = expression.split("+", 1)
return evaluate(s1) + evaluate(s2)
# SUBTRACTION
elif re.match(INI + VAL + SUB, expression):
# GREEDY SUB-CASES: SUB IMMEDIATELY
if re.match(INI + VAL + SUB + VAL + FIN, expression):
lhs, rhs = expression.split("-", 1)
return evaluate(lhs) - evaluate(rhs)
if re.match(INI + VAL + SUB + VAL + ADD, expression):
s1, s2 = expression.split("-", 1)
s2, s3 = s2.split("+", 1)
return evaluate(str(evaluate(s1) - evaluate(s2)) + "+" + s3)
if re.match(INI + VAL + SUB + VAL + SUB, expression):
s1, s2 = expression.split("-", 1)
s2, s3 = s2.split("-", 1)
return evaluate(str(evaluate(s1) - evaluate(s2)) + "-" + s3)
# LAZY SUB-CASES: EVALUATE FIRST
if re.match(INI + VAL + SUB + VAL + MLT, expression):
s1, s2 = expression.split("-", 1)
return evaluate(s1) - evaluate(s2)
if re.match(INI + VAL + SUB + VAL + DIV, expression):
s1, s2 = expression.split("-", 1)
return evaluate(s1) + evaluate(s2)
if re.match(INI + VAL + SUB + VAL + OPP, expression):
s1, s2 = expression.split("-", 1)
return evaluate(s1) + evaluate(s2)
# MULTIPLICATION
elif re.match(INI + VAL + MLT, expression):
# GREEDY SUB-CASES: MLT IMMEDIATELY
if re.match(INI + VAL + MLT + VAL + FIN, expression):
lhs, rhs = expression.split("*", 1)
return evaluate(lhs) * evaluate(rhs)
if re.match(INI + VAL + MLT + VAL + ADD, expression):
s1, s2 = expression.split("*", 1)
s2, s3 = s2.split("+", 1)
return evaluate(str(evaluate(s1) * evaluate(s2)) + "+" + s3)
if re.match(INI + VAL + MLT + VAL + SUB, expression):
s1, s2 = expression.split("*", 1)
s2, s3 = s2.split("-", 1)
return evaluate(str(evaluate(s1) * evaluate(s2)) + "-" + s3)
if re.match(INI + VAL + MLT + VAL + MLT, expression):
s1, s2 = expression.split("*", 1)
s2, s3 = s2.split("*", 1)
return evaluate(str(evaluate(s1) * evaluate(s2)) + "*" + s3)
if re.match(INI + VAL + MLT + VAL + DIV, expression):
s1, s2 = expression.split("*", 1)
s2, s3 = s2.split("/", 1)
return evaluate(str(evaluate(s1) * evaluate(s2)) + "/" + s3)
# LAZY SUB-CASES: EVAL FIRST
if re.match(INI + VAL + MLT + OPP, expression):
s1, s2 = expression.split("*", 1)
return evaluate(s1) * evaluate(s2)
# DIVISION
elif re.match(INI + VAL + DIV, expression):
# GREEDY SUB-CASES: DIV IMMEDIATELY
if re.match(INI + VAL + DIV + VAL + FIN, expression):
lhs, rhs = expression.split("/", 1)
return evaluate(lhs) / evaluate(rhs)
if re.match(INI + VAL + DIV + VAL + ADD, expression):
s1, s2 = expression.split("/", 1)
s2, s3 = s2.split("+", 1)
return evaluate(str(evaluate(s1) / evaluate(s2)) + "+" + s3)
if re.match(INI + VAL + DIV + VAL + SUB, expression):
s1, s2 = expression.split("/", 1)
s2, s3 = s2.split("-", 1)
return evaluate(str(evaluate(s1) * evaluate(s2)) + "-" + s3)
if re.match(INI + VAL + DIV + VAL + MLT, expression):
s1, s2 = expression.split("/", 1)
s2, s3 = s2.split("*", 1)
return evaluate(str(evaluate(s1) / evaluate(s2)) + "*" + s3)
if re.match(INI + VAL + DIV + VAL + DIV, expression):
s1, s2 = expression.split("/", 1)
s2, s3 = s2.split("/", 1)
return evaluate(str(evaluate(s1) / evaluate(s2)) + "/" + s3)
# LAZY SUB-CASES: EVAL FIRST
if re.match(INI + VAL + DIV + OPP, expression):
s1, s2 = expression.split("/", 1)
return evaluate(s1) / evaluate(s2)
# PARENTHESES
elif re.match(INI + OPP, expression):
i = 1
stack = 1
p_expression = ""
while stack and i < len(expression):
if expression[i] == "(":
stack += 1
if expression[i] == ")":
stack -= 1
p_expression += expression[i]
i += 1
if stack:
raise(Exception("Unmatched parentheses in {}").format(expression))
p_expression = p_expression[:-1] if p_expression.endswith(")") else p_expression
p_value = evaluate(p_expression)
if i >= len(expression):
return p_value
elif expression[i] == "+":
return evaluate(str(p_value) + "+" + expression[(i+1):])
elif expression[i] == "-":
return evaluate(str(p_value) + "-" + expression[(i+1):])
elif expression[i] == "*":
return evaluate(str(p_value) + "*" + expression[(i+1):])
elif expression[i] == "/":
return evaluate(str(p_value) + "/" + expression[(i+1):])
else:
raise(Exception("Syntax error: {}").format(expression))
test_expressions = [
('""', "Empty string"),
('"1"', "Numeric string"),
('"A"', "Alpha string"),
('"A1"', "Alphanumeric string"),
('1', "integer"),
('1.', "float (nothing after decimal)"),
('.1', "float (nothing before decimal)"),
('1.1', "float (vals before and after decimal)"),
('2+3', "integer addition"),
('2-3', "integer subtraction"),
('2*3', "integer multiplication"),
('7/3', "integer division"),
('2.8+3.1', "float addition"),
('2-3', "float subtraction"),
('2*3', "float multiplication"),
('7/3', "float division"),
('2 + 3', "integer addition with spaces"),
('1=1', "identity with two ints"),
]
successes = len(test_expressions)
for test_expression, test_name in test_expressions:
if evaluate(test_expression) != eval(test_expression):
print("Failed test: {}".format(test_name))
successes -= 1
print("Passed {} of {} tests.".format(successes, len(test_expressions)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment