Files
COS301-HW1/interpreter.py
T

102 lines
5.4 KiB
Python

## Interpreter for PMPV Language
# author Nicholas Pease
# Resources
# Python String Reference: https://www.w3schools.com/python/python_ref_string.asp
# sys Module Reference: https://docs.python.org/3/library/sys.html#sys.stdout
# System Imports
import sys
import re
class Interpreter:
def __init__(this):
# Init basic local variables
this.variables = {}
this.inputRaw = ""
@staticmethod
def throwError(module,message):
# Static method to throw formatted errors
sys.stderr.write(f'[Interpreter] ({str(module)}) Error: {str(message)}')
def startCommandLine(this):
# Program Control Function
while True:
try:
this.inputRaw = input("" if this.inputRaw == "" else "\n")
if this.inputRaw != "": this.processAssignments(this.inputRaw)
except EOFError:
break
except KeyboardInterrupt:
break
def processAssignments(this, inputString):
# First Step - Determine if assignment operator present
if inputString.count('=') != 0:
# Working with Assignment Operator
# Stop immediately if multiple assignment operators in string
if inputString.count('=') > 1:
Interpreter.throwError("processAssignments","Multiple assignment operators in use (1 max)")
return
# If there are, we save to the variable, not print
assignmentSplit = inputString.split("=") # First Arg: variable, Second Arg: expression
this.variables[assignmentSplit[0].strip()] = this.processParenthesis(assignmentSplit[1].strip())
this.inputRaw = " " # Reset input to forgo the \n
else:
# If there are no operators, string is set to be evaluated
sys.stdout.write(f'{this.processParenthesis(inputString)}')
def processParenthesis(this,inputString):
# Second Step - Determine if expression contains parenthesis
# Recursively designed function to evaluate parenthesis from left to right, inner to outer until no more remain
if inputString.count('(') == 0:
# No parenthesis or all parenthesis evaluated
return this.evaluate(inputString)
else:
# Has parenthesis, verify properly formatted parenthesis
if inputString.count('(') == inputString.count(')'):
i = 0
# Left to right, find first open parenthesis, then stop when matching (closest) closing parenthesis
while inputString[i] != ')':
if inputString[i] == '(':
start = i
i+=1
# Split the string in 3, all the stuff before, the evaluated expression (minus the parenthesis), all the stuff after and repeat
inputString = inputString[:start] + this.evaluate(inputString[start+1:i]) + inputString[i+1:]
return this.processParenthesis(inputString)
else:
Interpreter.throwError("processParenthesis", "Parenthesis Mismatch, Ensure all Parenthesis are Closed")
def evaluate(this,inputString):
# Evalute the "cleaned" expression
# Stripped out all the assignment operators and parenthesis
expressionMap = list(filter(None, re.split('([^a-zA-Z0-9])', inputString.replace(" ", ""))))
# Varible substitution
for i in range(len(expressionMap)):
if expressionMap[i] in this.variables: expressionMap[i] = this.variables[expressionMap[i]]
total = 0
# If there is math to be done, do math
if len(expressionMap) > 1:
# Move around the expression looking to bind operators to operations
for i in range(len(expressionMap)-1):
# If we are at the start of the line, or there is an operation that is not followed by an operation, bind
if (expressionMap[i] == "+" or expressionMap[i] == "-") and (expressionMap[i+1] != "+" and expressionMap[i+1] != "-") or i == 0:
try:
# int allows + and - to be bound to an integer and reflected in its positivity/negation. By doing this, all elements of the string can simple be added
total+=(int(expressionMap[i]+expressionMap[i+1])) if (expressionMap[i] == "+" or expressionMap[i] == "-") and (expressionMap[i+1] != "+" and expressionMap[i+1] != "-") else (int(expressionMap[i]))
except:
# throw an error if something in the string is not an operation or a int
Interpreter.throwError("evaluate",f"Unknown symbol '{expressionMap[i]}'. '{expressionMap[i]}' is not a valid input or an undefined variable.")
return ""
# If there is a - and a - (double negation), swap next operator to a + and carry on
# Swapping next operation to a + is the same as subtracting a negative, you add. this flips the sign of the next operator and ignores the current negation
# A combination + and - is ignored as the negation will be bound to the int and the plus will be discarded
elif expressionMap[i] == "-" and expressionMap[i+1] == "-":
expressionMap[i+1] = '+'
else: #simple math, jsut return what's there
total = expressionMap[0]
return str(total)
PMPVInterpreter = Interpreter()
PMPVInterpreter.startCommandLine()