Created
May 17, 2022 07:07
-
-
Save davefernig/21c8facd98bb4c7a6dcc0e867e575adc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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