You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

204 lines
7.3 KiB
Python

"""
===============================================================================
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.
===============================================================================
"""
11 months ago
import re
from parse import *
# List of valid operators
opers = ['+', '-', '*', '/', '=']
11 months ago
# Regular expression that checks for valid characters in an expression
valid_chars = '[ 0-9a-z.\(\)+-\/*=]'
variables = {}
11 months ago
def print_error(error_code):
11 months ago
# 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)
11 months ago
match error_code:
case 1:
print("You have invalid characters in your expression.")
case 2:
11 months ago
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.")
return
11 months ago
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
11 months ago
# 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
11 months ago
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
11 months ago
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)
11 months ago
for index, val in enumerate(subexpr): # Replace variables with their values
if str(val).isalpha():
subexpr[index] = variables[val]
# print(subexpr)
11 months ago
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.
11 months ago
subexpr[index-1] = ''
subexpr[index+1] = ''
# print(subexpr)
11 months ago
return evaluate(subexpr)
def find_inner(subexpr):
# print("expr: " + subexpr)
11 months ago
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)
11 months ago
if not '(' in subexpr_string and not ')' in subexpr_string:
return str(evaluate(subexpr))
def main():
while True:
variable = ''
11 months ago
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)
11 months ago
print(expr)
if (len(variables) > 0):
print(variables)
11 months ago
main()