"""
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
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. " )
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 )
print ( variables )
main ( )