102 lines
5.4 KiB
Python
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() |