Skip to content

Instantly share code, notes, and snippets.

@Jemhunter
Last active March 24, 2022 12:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jemhunter/183de6b27232d04068f547df2612fdae to your computer and use it in GitHub Desktop.
Save Jemhunter/183de6b27232d04068f547df2612fdae to your computer and use it in GitHub Desktop.
Interpreter for Stacks Of Queues esolang
'''
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