Last active
March 24, 2022 12:07
-
-
Save Jemhunter/183de6b27232d04068f547df2612fdae to your computer and use it in GitHub Desktop.
Interpreter for Stacks Of Queues esolang
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
Stacks of Queues Interpreter v1.0 | |
Created by Jemhunter | |
''' | |
import argparse | |
import time | |
class Stack (list): | |
'''Class to implement a stack''' | |
def __init__(self, loop : bool) -> None: | |
'''Constructor''' | |
#Setup list superclass | |
super().__init__() | |
#Store boolean value as to whether this stack should repeat | |
self.repeat = loop | |
def remove (self) -> int: | |
'''Pop the top value from the stack and return it, returns -1 if empty''' | |
if len(self) > 0: | |
value = self[-1] | |
del self[-1] | |
return value | |
else: | |
return -1 | |
def add (self, value : int) -> None: | |
'''Push the given value on to the top of the stack''' | |
self.append(value) | |
def reverse (self) -> None: | |
'''Reverse the order of all the items in the stack''' | |
self[::] = self[::-1] | |
def swapTop (self) -> None: | |
'''Swap the order of the top two items in the stack''' | |
if len(self) > 1: | |
self[-1], self[-2] = self[-2], self[-1] | |
def duplicate (self) -> None: | |
'''Pushes a copy of the top item, pushes -1 if empty''' | |
if len(self) > 0: | |
value = self[-1] | |
self.add(value) | |
else: | |
self.add(-1) | |
def rotateForward (self) -> None: | |
'''Rotate all items forward, top item becomes bottom all others move up''' | |
value = self.remove() | |
self.insert(0, value) | |
def rotateBackward (self) -> None: | |
'''Rotate all items backward, bottom item becomes top all others move down''' | |
if len(self) > 0: | |
value = self[0] | |
del self[0] | |
self.add(value) | |
else: | |
self.add(-1) | |
class Queue (Stack): | |
'''Class to implement a queue as a subclass of Stack''' | |
def remove (self) -> int: | |
'''Dequeue the item from the front of the queue and return it''' | |
if len(self) > 0: | |
value = self[0] | |
del self[0] | |
return value | |
else: | |
return -1 | |
def add (self, value : int) -> None: | |
'''Enqueue the given value to the end of the queue''' | |
self.append(value) | |
def swapTop (self) -> None: | |
'''Swap the order of the top two items in the queue''' | |
if len(self) > 1: | |
self[0], self[1] = self[1], self[0] | |
def duplicate (self) -> None: | |
'''Enqueue a copy of the first item in the queue, enqueues -1 if empty''' | |
if len(self) > 0: | |
value = self[0] | |
self.add(value) | |
else: | |
self.add(-1) | |
def rotateForward (self) -> None: | |
'''Rotate all items forwards, first item becomes last all others move towards the front''' | |
value = self.remove() | |
self.add(value) | |
def rotateBackward (self) -> None: | |
'''Rotate all items backwards, last item becomes first all others move towards the back''' | |
if len(self) > 0: | |
value = self[-1] | |
del self[-1] | |
self.insert(0, value) | |
else: | |
self.insert(0, -1) | |
class Interpreter (): | |
'''Class to handle code execution''' | |
def __init__(self, code : str) -> None: | |
'''Constructor''' | |
#Current working structures | |
self.data = [] | |
#String of program code | |
self.code = code | |
#Current instruction pointer | |
self.ip = 0 | |
#If the program is still running - has not halted | |
self.running = True | |
#If currently in string mode (pushing ordinals to stack instead of instructions) | |
self.string = False | |
#Current input mode: 0 - number, 1 - character, 2 - line | |
self.inputMode = 0 | |
#Current output mode: 0 - number, 1 - character | |
self.outputMode = 0 | |
#Stored error code | |
self.error = None | |
#Lists of positions for start of looped structures | |
self.lastLoopStack = [] | |
self.lastLoopQueue = [] | |
#Literal values | |
self.literals = "0123456789abcdefghijklmnopqrstuvwxyz" | |
#Arithmetic operators and lambda functions for each | |
self.operators = "+-*/\%" | |
self.operations = [lambda x, y: x + y, lambda x, y: x - y, lambda x, y: x * y, lambda x, y: x / y, lambda x, y: x // y, lambda x, y: x % y] | |
#Logical operators and lambda functions for each | |
self.logic = "=MW" | |
self.logical = [lambda x, y: int(x == y), lambda x, y: int(x > y), lambda x, y: int(x < y)] | |
#Character used to enable and disable string mode | |
self.stringLit = "'" | |
#Reserved command characters | |
self.commands = "RSDPQLFBTGXYZICNO?" | |
#Data about each type of structure | |
self.starts = ["(", "<", "{", "["] | |
self.ends = [")", ">", "}", "]"] | |
self.structs = [Stack, Stack, Queue, Queue] | |
self.looping = [False, True, False, True] | |
#If the next instruction should be skipped | |
self.skip = False | |
def addValue(self, val: int) -> None: | |
'''Add the given value to the current working stack or queue''' | |
if len(self.data) > 0: | |
self.data[-1].add(val) | |
def remValue(self) -> int: | |
'''Take a value from the current working stack or queue and return it, returns -1 if there is no structure or the structure is empty''' | |
if len(self.data) > 0: | |
return self.data[-1].remove() | |
return -1 | |
def giveValue(self) -> None: | |
'''Pop or dequeue value from current working stack and push or enqueue to parent structure, if there is no parent the value is lost''' | |
if len(self.data) > 0: | |
value = self.remValue() | |
if len(self.data) > 1: | |
self.data[-2].add(value) | |
def takeValue(self) -> None: | |
'''Pop or dequeue value from parent structure and push or enqueue to current working structure, if there is no parent or parent is empty -1 is pushed or enqueued''' | |
if len(self.data) > 1: | |
value = self.data[-2].remove() | |
self.addValue(value) | |
else: | |
self.addValue(-1) | |
def getInput(self) -> None: | |
'''Take input from the user, type dependent on input mode''' | |
inputValue = input() | |
#Number mode | |
if self.inputMode == 0: | |
try: | |
#Convert to a float | |
number = float(inputValue) | |
#Convert to an integer | |
integer = int(number) | |
#If no accuracy is lost by integer | |
if integer == number: | |
#Store as integer | |
self.addValue(integer) | |
else: | |
#Store as float | |
self.addValue(number) | |
except: | |
#If input is not a valid number add -1 instead | |
self.addValue(-1) | |
#Character mode | |
elif self.inputMode == 1: | |
#If there was input | |
if len(inputValue) > 0: | |
#Add the first character given | |
self.addValue(ord(inputValue[0])) | |
else: | |
#In the event of empty input - add -1 instead | |
self.addValue(-1) | |
#Line mode | |
elif self.inputMode == 2: | |
#If there was input given | |
if len(inputValue) > 0: | |
#Iterate through characters | |
for ch in inputValue: | |
#Add the ordinal value for the character | |
self.addValue(ord(ch)) | |
else: | |
#In the event of empty input - add -1 instead | |
self.addValue(-1) | |
def output(self, val: int) -> None: | |
'''Output the given value, formatted depending on output mode''' | |
#Character mode | |
if self.outputMode == 1: | |
#If it is a positive value | |
if val > -1: | |
#Print the character (floats are truncated) | |
print(chr(int(val)), end="", flush=True) | |
#Number mode | |
elif self.outputMode == 0: | |
#Print as a number | |
print(val, end="", flush=True) | |
def executeStep(self) -> None: | |
'''Execute a single instruction and move instruction pointer''' | |
#Store if this instruction is skipped - and reset stored value | |
skipping = self.skip | |
self.skip = False | |
#If the program is still running and the instruction pointer is within the code | |
if self.running and self.ip < len(self.code): | |
#Get the next character of the code | |
char = self.code[self.ip] | |
#If currently in string mode | |
if self.string: | |
#If the value is not skipped (not sure how this would happen) | |
if not skipping: | |
#If the character is the string literal toggle character "'" | |
if char == self.stringLit: | |
#Turn off string mode | |
self.string = False | |
else: | |
#Store the ordinal value of the character in the current structure | |
self.addValue(ord(char)) | |
else: | |
#If there is a current working stack - otherwise literals and commands are ignored | |
if len(self.data) > 0: | |
#If this character is a literal value - and not skipped | |
if char in self.literals and not skipping: | |
#Add the value of the literal to the current structure | |
self.addValue(self.literals.index(char)) | |
#If the character is a mathematical operator - and not skipped | |
if char in self.operators and not skipping: | |
#Get the index of the operator | |
op = self.operators.index(char) | |
#Get the first and second values from the current structure | |
f = self.remValue() | |
s = self.remValue() | |
#If this is not a division or the first value is not 0 | |
if op < 3 or f != 0: | |
#Perform the mathematical calculation and add the result to the current structure | |
self.addValue(self.operations[op](s, f)) | |
else: | |
#Divide by zero error | |
self.error = "Cannot divide by zero." | |
#If this character is a logical operator - and not skipped | |
if char in self.logic and not skipping: | |
#Get the index of the operator | |
op = self.logic.index(char) | |
#Get the first and second values from the current structure | |
f = self.remValue() | |
s = self.remValue() | |
#Perform the logical operation and add the result to the current structure | |
self.addValue(self.logical[op](s, f)) | |
#If this character is a command - and not skipped | |
if char in self.commands and not skipping: | |
#Reverse the structure | |
if char == "R": | |
self.data[-1].reverse() | |
#Swap the first two items | |
elif char == "S": | |
self.data[-1].swapTop() | |
#Duplicate the first item | |
elif char == "D": | |
self.data[-1].duplicate() | |
#Pop or deQueue the first item and discard | |
elif char in ["P", "Q"]: | |
self.remValue() | |
#Add the length of the current structure | |
elif char == "L": | |
self.addValue(len(self.data[-1])) | |
#Rotate all values forward in the current structure | |
elif char == "F": | |
self.data[-1].rotateForward() | |
#Rotate all values backward in the current structure | |
elif char == "B": | |
self.data[-1].rotateBackward() | |
#Take a single value from the parent structure | |
elif char == "T": | |
self.takeValue() | |
#Give a single value to the parent structure | |
elif char == "G": | |
self.giveValue() | |
#Switch to character input | |
elif char == "X": | |
self.inputMode = 1 | |
#Switch to line input | |
elif char == "Y": | |
self.inputMode = 2 | |
#Switch to number input (default) | |
elif char == "Z": | |
self.inputMode = 0 | |
#Get input from the user (based on input mode) | |
elif char == "I": | |
self.getInput() | |
#Switch to character output | |
elif char == "C": | |
self.outputMode = 1 | |
#Switch to number output | |
elif char == "N": | |
self.outputMode = 0 | |
#Output the first value from the structure (based on output mode) | |
elif char == "O": | |
value = self.remValue() | |
self.output(value) | |
#Test the first value (removed from structure) and if it is less than or equal to zero, skip the next instruction | |
elif char == "?": | |
value = self.remValue() | |
if value <= 0: | |
self.skip = True | |
#If this character is the string literal starter - and not skipped | |
if char == self.stringLit and not skipping: | |
#Switch to string mode | |
self.string = True | |
#If this is the start of a structure | |
if char in self.starts: | |
#If it has not been skipped | |
if not skipping: | |
#Check type | |
if char in ["(", "<"]: | |
#Create stack | |
self.data.append(Stack(char == "<")) | |
if char == "<": | |
#Store start point if looping | |
self.lastLoopStack.append(self.ip) | |
else: | |
#Create queue | |
self.data.append(Queue(char == "[")) | |
if char == "[": | |
#Store start point if looping | |
self.lastLoopQueue.append(self.ip) | |
else: | |
endChar = self.ends[self.starts.index(char)] | |
#Skip past end bracket | |
done = False | |
depth = 0 | |
#Iterate through | |
while self.ip < len(self.code) - 1 and not done: | |
#Increment instruction pointer | |
self.ip = self.ip + 1 | |
#Get the character | |
ch = self.code[self.ip] | |
#If another open is found | |
if ch == char: | |
#Increase the depth | |
depth = depth + 1 | |
#If it is the close symbol | |
if ch == endChar: | |
#At the correct depth | |
if depth == 0: | |
done = True | |
else: | |
#Decrease the depth | |
depth = depth - 1 | |
#If no associated close was found | |
if not done: | |
#Report error | |
self.error = "Expected paired {0} but not found by EOF.".format(endChar) | |
#If this is the end of a structure | |
if char in self.ends: | |
strIndex = self.ends.index(char) | |
#If there is a data structure | |
if len(self.data) > 0: | |
#If it is the right kind of structure | |
if type(self.data[-1]) == self.structs[strIndex] and self.data[-1].repeat == self.looping[strIndex]: | |
if skipping or not self.looping[strIndex]: | |
if self.looping[strIndex]: | |
if self.structs[strIndex] == Stack and len(self.lastLoopStack) > 0: | |
del self.lastLoopStack[-1] | |
elif self.structs[strIndex] == Queue and len(self.lastLoopQueue) > 0: | |
del self.lastLoopQueue[-1] | |
#If it is the only remaining structure | |
if len(self.data) == 1: | |
#Iterate while it is not empty | |
while len(self.data[0]) > 0: | |
#Output each value in turn | |
value = self.remValue() | |
self.output(value) | |
#If it is in number mode | |
if self.outputMode == 0 and len(self.data[0]) > 0: | |
#Output a space between each number | |
self.outputMode = 1 | |
self.output(ord(" ")) | |
self.outputMode = 0 | |
#Remove the data structure | |
del self.data[-1] | |
else: | |
#Remove the structure | |
del self.data[-1] | |
#Get the structure data | |
newStruct = self.structs[strIndex] | |
newLoop = self.looping[strIndex] | |
#Add a new structure | |
self.data.append(newStruct(newLoop)) | |
if newStruct == Stack: | |
#If there is a place to jump back to | |
if len(self.lastLoopStack) > 0: | |
#Jump back | |
self.ip = self.lastLoopStack[-1] | |
else: | |
#Unpaired bracket | |
self.error = "Unexpected {0} found.".format(char) | |
elif newStruct == Queue: | |
#If there is a place to jump back to | |
if len(self.lastLoopQueue) > 0: | |
#Jump back | |
self.ip = self.lastLoopQueue[-1] | |
else: | |
#Unpaired bracket | |
self.error = "Unexpected {0} found.".format(char) | |
else: | |
#Incorrect bracket found | |
self.error = "Unexpected {0} found, another close was expected first.".format(char) | |
else: | |
#Unpaired bracket | |
self.error = "Unexpected {0} found.".format(char) | |
#If there is no error | |
if self.error == None: | |
#Increment instruction pointer | |
self.ip = self.ip + 1 | |
else: | |
#Report error and terminate | |
lineNumber = 1 | |
character = 1 | |
index = 0 | |
#Iterate through code counting lines | |
while index < self.ip: | |
index = index + 1 | |
if self.code[index] == '\n': | |
lineNumber = lineNumber + 1 | |
character = 1 | |
else: | |
character = character + 1 | |
#Add where the error occurred to the error message | |
self.error = self.error + " Occurred at line {0} character {1}.".format(lineNumber, character) | |
#Display the error | |
print(self.error) | |
#Terminate the program | |
self.running = False | |
else: | |
#If the end of code was reached | |
if self.ip > len(self.code): | |
#If there is still a structure open | |
if len(self.data) > 0: | |
#Display error for end of file with correct structure bracket | |
if type(self.data[-1]) == Stack: | |
if not self.data[-1].repeat: | |
self.error = "Expected ) but not found by EOF." | |
else: | |
self.error = "Expected > but not found by EOF." | |
elif type(self.data[-1]) == Queue: | |
if not self.data[-1].repeat: | |
self.error = "Expected } but not found by EOF." | |
else: | |
self.error = "Expected ] but not found by EOF." | |
#If an error occurred | |
if self.error != None: | |
#Display the error | |
print(self.error) | |
#Terminate execution | |
self.running = False | |
def runProgram(self, delay : float, debug : bool) -> int: | |
'''Run the program code until it terminates, delay in seconds between command, debug if true displays debug information''' | |
#Repeat until termination | |
while self.running: | |
try: | |
if debug: | |
#Display instruction pointer | |
print(self.ip, end = "") | |
#Execute next character | |
self.executeStep() | |
if debug: | |
#Display working structure | |
print(self.data) | |
#Delay between steps | |
time.sleep(delay) | |
except KeyboardInterrupt: | |
#Cleanly exit on keyboard interrupt | |
return -1 | |
return 0 | |
#Only if this is the main called program | |
if __name__ == "__main__": | |
#Create parser to handle program arguments | |
parser = argparse.ArgumentParser(description="Run the StackQueue interpreter. Either pass a file name or use -c then type the code. Use --help or -h for more information.") | |
#Add argument group for code | |
argGroup = parser.add_argument_group("code") | |
codeGroup = argGroup.add_mutually_exclusive_group(required = True) | |
#Add file argument to code group | |
codeGroup.add_argument("file", type = argparse.FileType("r"), nargs = "?", help = "File containing StackQueue code to run. Should be .sq format.") | |
#Add text code argument to code group | |
codeGroup.add_argument("-c", "--code", metavar = "code", help = "A string of StackQueue code to be run.") | |
#Add options group | |
options = parser.add_argument_group("options") | |
#Add time delay to arguments | |
options.add_argument("-t", "--time", type = float, default = 0.0, metavar = "seconds", help = "Give a time delay, in seconds, between each instruction being executed.") | |
#Add debug mode to arguments | |
options.add_argument("-d", "--debug", action = "store_true", default = False, dest = "debugging", help = "Verbose output showing instruction pointer and working data structures.") | |
#Parse the given arguments | |
args = parser.parse_args() | |
#Default values for code and delay | |
inputCode = "" | |
delayTime = 0.0 | |
#Get the code and store it | |
if args.file: | |
#Open read and close the file | |
inputCode = args.file.read() | |
args.file.close() | |
else: | |
#Get the code as a string | |
inputCode = args.code | |
#Store the delay time | |
if args.time: | |
delayTime = args.time | |
#Create an instance of the interpreter | |
interp = Interpreter(inputCode) | |
#Run the interpreter | |
result = interp.runProgram(delayTime, args.debugging) | |
#End if keyboard interrrupt occurred | |
if result == -1: | |
parser.exit(message = "\n") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment