""" =============================================================================== ENGR 13300 Fall 2023 Program Description An algebraic expression calculator. This program can evaluate expressions, assign values to variables, and use variables in additional expressions. The program consists of three main parts: 1. Parsing the expression. This is accomplished using the 'parse' function, defined in parse.py. The expression is parsed, and a list of 'tokens' is produced, with each token bein a part of the expression. For example '5 + 7 * 3' is parsed into '['5', '+', '7', '*', '3']. 2. Find the inner-most expression. This only applies to expressions that have parantheses. The 'find_inner' function is called recursively, until the inner expression is found. For example, given the expression '3 + (7 * (4 + 8))', the 'find_inner' function is called once, to produce '7 * (4 + 8)', and then called again, to produce '4 + 8'. 3. Evaluate the expression. The 'evaluate' function searches for operators, and then evaluates the operator with the preceding and succeeding token (if one of the tokens is a variable, the value of the variable replaces the variable). The result of the evaluation replaces the expression. Example: Given the expression '3 + (7 * (4 + 8))', the inner expression is evaluated first. This produces '3 + (7 * 12)'. The inner expression is evaluated again, producing '3 + 84'. Finally, the rest of the expression is evaluated, to produce 87. 4. If the expression consists of an assignment, the evaluated expression is stored into the variable on the left. The list of variables is maintained as a dictionary. Assignment Information Assignment: Individual Project Author: Aadhavan Srinivasan, srini193@purdue.edu Team ID: LC3 - 19 Contributor: Name, login@purdue [repeat for each] My contributor(s) helped me: [ ] understand the assignment expectations without telling me how they will approach it. [ ] understand different ways to think about a solution without helping me plan my solution. [ ] think through the meaning of a specific error or bug present in my code without looking at my code. Note that if you helped somebody else with their code, you have to list that person as a contributor here as well. ACADEMIC INTEGRITY STATEMENT I have not used source code obtained from any other unauthorized source, either modified or unmodified. Neither have I provided access to my code to another. The project I am submitting is my own original work. =============================================================================== """ import re from parse import * # List of valid operators opers = ['+', '-', '*', '/', '='] # Regular expression that checks for valid characters in an expression valid_chars = '[ 0-9a-z.\(\)+-\/*=]' variables = {} def print_error(error_code): # List of error codes: # 1 - Invalid characters found in expression # 2 - Unclosed parantheses # 3 - Two operators next to each other # 4 - Wrong number formatting (multiple periods) match error_code: case 1: print("You have invalid characters in your expression.") case 2: print("You have an unclosed parantheses in your expression.") case 3: print("You have two operators next to each other.") case 4: print("One of your values is improperly formatted.") case 5: print("Uninitialized variable.") case 6: print("Invalid expression.") print("") return def check_errors(expr): # Check for errors before parsing the expression expr_small = expr.replace(" ", "") # Remove spaces from the string, to make it easier to parse # Check if number of opening parantheses is equal to number of closing parantheses num_open_pars = 0 num_close_pars = 0 for index,val in enumerate(expr_small): if not re.match(valid_chars, val): return 1 num_open_pars = num_open_pars+1 if val == '(' else num_open_pars num_close_pars = num_close_pars+1 if val == ')' else num_close_pars if val in opers: if expr_small[index + 1] in opers: # Two consecutive operators return 3 if val == '.': if not expr_small[index + 1].isdigit(): # If you have a period, you must have a number after it return 4 if num_open_pars != num_close_pars: return 2 # Check for errors after parsing the expression expr = parse(expr) if expr[0].isalpha() and (len(expr) == 1 or expr[1] != '='): # If you just have an expression with a letter eg. 'x', or you use a variable without an assignment (e.g. 'x 5') return 6 for val in expr: if val.count('.') > 1: # A value can have at most 1 period return 4 if val.isalpha() and not val in variables and val != expr[0]: # If the token is a string, and isn't in the dictionary, and isn't the variable at index 0 (e.g. 'x' in 'x = 4') return 5 return 0 def evaluate(subexpr): # Evaluate a tokenized expression, that contains no parantheses subexpr = [element for element in subexpr if element != ''] # Remove empty characters in the expression # print(subexpr) for index, val in enumerate(subexpr): # Replace variables with their values if str(val).isalpha(): subexpr[index] = variables[val] # print(subexpr) if (len(subexpr) == 1): return float(subexpr[0]) if '/' in subexpr: index = subexpr.index('/') subexpr[index] = float(subexpr[index-1]) / float(subexpr[index+1]) subexpr[index-1] = '' subexpr[index+1] = '' elif '*' in subexpr: index = subexpr.index('*') subexpr[index] = float(subexpr[index-1]) * float(subexpr[index+1]) subexpr[index-1] = '' subexpr[index+1] = '' elif '+' in subexpr or '-' in subexpr: # Addition and subtraction have the same precedence index_plus, index_minus = float('inf'), float('inf') # Set both values to infinity if '+' in subexpr: index_plus = subexpr.index('+') if '-' in subexpr: index_minus = subexpr.index('-') index = index_plus if index_plus < index_minus else index_minus # Set the index to the index of the operator that occurs first subexpr[index] = float(subexpr[index-1]) + float(subexpr[index+1]) if index_plus < index_minus else float(subexpr[index-1]) - float(subexpr[index+1]) # If addition occured first, add the previous and next tokens. If subtraction occured first, subtract them. subexpr[index-1] = '' subexpr[index+1] = '' # print(subexpr) return evaluate(subexpr) def find_inner(subexpr): # print("expr: " + subexpr) subexpr = parse(subexpr) for index,val in enumerate(subexpr): if '(' in val or ')' in val: subexpr[index] = find_inner(val[1:len(val)-1]) subexpr_string = ''.join(subexpr) # print("New expr: " + subexpr_string) if not '(' in subexpr_string and not ')' in subexpr_string: return str(evaluate(subexpr)) def main(): while True: variable = '' expr = input() errno = check_errors(expr) if errno != 0: print_error(errno) continue # If an error was generated, print an error message and continue on to the next iteration of the loop expr_tokenized = parse(expr) if expr_tokenized[0].isalpha() and expr_tokenized[1] == '=': # If the expression assigns a value to a variable variable = expr_tokenized[0] # The first token is the variable expr_tokenized.pop(0) # Remove the first and second tokens expr_tokenized.pop(0) expr = find_inner(''.join(expr_tokenized)) variables.update({variable: expr}) else: expr = find_inner(expr) print(expr) if (len(variables) > 0): print(variables) print("") main()