Skip to content

Instantly share code, notes, and snippets.

@davefernig
Created May 17, 2022 07:07
Show Gist options
  • Select an option

  • Save davefernig/21c8facd98bb4c7a6dcc0e867e575adc to your computer and use it in GitHub Desktop.

Select an option

Save davefernig/21c8facd98bb4c7a6dcc0e867e575adc to your computer and use it in GitHub Desktop.

Revisions

  1. davefernig created this gist May 17, 2022.
    278 changes: 278 additions & 0 deletions basic evaluator
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,278 @@
    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)))