From 1d0858c5452c20750882f8a26ce529582871e724 Mon Sep 17 00:00:00 2001 From: Jens Bouman Date: Wed, 29 Apr 2020 13:42:12 +0200 Subject: [PATCH] Interpreter --- GUI/TKinter/canvasManager.py | 57 +++++ GUI/TKinter/infoManager.py | 79 ++++++ GUI/TKinter/main.py | 156 ++++++++++++ GUI/__init__.py | 0 GUI/assets/tkinterLayout - kopie.ui | 337 +++++++++++++++++++++++++ GUI/assets/tkinterLayout.ui | 371 ++++++++++++++++++++++++++++ GUI/kivy/Debugger.kv | 68 +++++ GUI/kivy/kivyWindow.py | 74 ++++++ interpreter/__init__.py | 0 interpreter/colors.py | 67 +++++ interpreter/imageWrapper.py | 126 ++++++++++ interpreter/lexer.py | 138 +++++++++++ interpreter/lexerTokens.py | 77 ++++++ interpreter/main.py | 96 +++++++ interpreter/movement.py | 168 +++++++++++++ interpreter/programState.py | 24 ++ interpreter/runner.py | 277 +++++++++++++++++++++ 17 files changed, 2115 insertions(+) create mode 100644 GUI/TKinter/canvasManager.py create mode 100644 GUI/TKinter/infoManager.py create mode 100644 GUI/TKinter/main.py create mode 100644 GUI/__init__.py create mode 100644 GUI/assets/tkinterLayout - kopie.ui create mode 100644 GUI/assets/tkinterLayout.ui create mode 100644 GUI/kivy/Debugger.kv create mode 100644 GUI/kivy/kivyWindow.py create mode 100644 interpreter/__init__.py create mode 100644 interpreter/colors.py create mode 100644 interpreter/imageWrapper.py create mode 100644 interpreter/lexer.py create mode 100644 interpreter/lexerTokens.py create mode 100644 interpreter/main.py create mode 100644 interpreter/movement.py create mode 100644 interpreter/programState.py create mode 100644 interpreter/runner.py diff --git a/GUI/TKinter/canvasManager.py b/GUI/TKinter/canvasManager.py new file mode 100644 index 0000000..925f74e --- /dev/null +++ b/GUI/TKinter/canvasManager.py @@ -0,0 +1,57 @@ +import interpreter.imageWrapper as imageWrapper + + +class canvasManager(): + def __init__(self, canvas, image, programState, scaleSize): + self.canvas = canvas + self.image = image + self.programState = programState + self.scaleSize = scaleSize + + def updateImage(self, newImage): + self.image = newImage + + def updateScaleSize(self, scaleSize): + self.scaleSize = scaleSize + + def updateProgramState(self, newProgramState): + self.programState = newProgramState + + def pixelToHexString(self, pixel) -> str: + return '#%02x%02x%02x' %(pixel[0], pixel[1], pixel[2]) + + def updateCanvas(self): + if self.image is None or self.canvas is None or self.programState is None or self.scaleSize is None: + return False + self.drawImage() + self.highlightCodel() + # Draw breakpoint + return True + + def drawImage(self): + self.clearCanvas() + for raw_y, row in enumerate(self.image): + for raw_x, pixel in enumerate(row): + x = raw_x * self.scaleSize + y = raw_y * self.scaleSize + color = self.pixelToHexString(pixel) + self.canvas.create_rectangle(x,y, x+self.scaleSize, y+self.scaleSize, fill=color, outline=color) + + + def clearCanvas(self): + width = self.canvas.winfo_width() + height = self.canvas.winfo_height() + self.canvas.create_rectangle(0,0, width, height, fill="#FFFFFF") + + + def highlightCodel(self): + codel = imageWrapper.getCodel(self.image, self.programState.position) + pixel = imageWrapper.getPixel(self.image, self.programState.position) + color = self.pixelToHexString(pixel) + self.colorCodel(codel, color, "#000000") + + def colorCodel(self, codel, fill, outline): + for position in codel: + x = position[0] * self.scaleSize + y = position[1] * self.scaleSize + self.canvas.create_rectangle(x,y, x+self.scaleSize - 1, y+self.scaleSize - 1, fill=fill, outline=outline) diff --git a/GUI/TKinter/infoManager.py b/GUI/TKinter/infoManager.py new file mode 100644 index 0000000..b3a379f --- /dev/null +++ b/GUI/TKinter/infoManager.py @@ -0,0 +1,79 @@ +import interpreter.imageWrapper as imageWrapper +import interpreter.colors as colors +import interpreter.lexerTokens as lexerTokens +import interpreter.movement as movement + +class infoManager(): + def __init__(self, builder, generalInfoFrame, programStateInfoFrame): + self.builder = builder + self.generalInfo = generalInfoFrame + self.programStateInfoFrame = programStateInfoFrame + + def updateInfo(self, image, graph, programState): + self.updateGeneralinfo(image, graph, programState) + self.updateProgramStateInfo(programState) + + def updateGeneralinfo(self, image, graph, programState): + self.updateCodelInfo(image, programState.position) + self.updateEdgesInfo(image, graph, programState) + + def updateProgramStateInfo(self, programState): + self.updateStackInfo(programState.dataStack) + self.updatePointersInfo(programState.position, programState.pointers) + + def updateCodelInfo(self, image, newPosition): + infoMessage = self.builder.get_object('positionInfoMessage', self.generalInfo) + if colors.isBlack(imageWrapper.getPixel(image, newPosition)): + infoMessage.configure(text="Black pixels are no codel, and have no edges") + return None + + baseString = "Selected codel contains:\n" + codel = imageWrapper.getCodel(image, newPosition) + for position in codel: + baseString += "{}\n".format(position) + + infoMessage.configure(text=baseString.strip('\n')) + + + def updateEdgesInfo(self, image, graph, programState): + edgesInfo = self.builder.get_object('codelEdgesMessage', self.generalInfo) + + if colors.isBlack(imageWrapper.getPixel(image, programState.position)): + edgesInfo.configure(text = "Black pixels are no codel, and have no edges") + return None + + codel = imageWrapper.getCodel(image, programState.position) + baseString = "Next step will be:\n" + edge = graph[hash(frozenset(codel))][hash(programState.pointers)] + baseString += self.getEdgeDescription(edge, programState.pointers) + + baseString += "\nCodel edges are as follows:\n" + #Generate pointers + edgePointers = list(map(lambda i: (i%4, int(i/4)), iter(range(8)))) + for edgePointer in edgePointers: + edge = graph[hash(frozenset(codel))][hash(edgePointer)] + baseString += self.getEdgeDescription(edge, edgePointer) + edgesInfo.configure(text = baseString) + + def getEdgeDescription(self, edge, pointer): + if isinstance(edge[0], lexerTokens.toColorToken) and edge[0].type == "push": + return "{}/{},{} -> {}({})\n".format(edge[1], movement.getDP(pointer[0]), movement.getCC(pointer[1]), edge[0].type, edge[0].codelSize) + else: + return "{}/{},{} -> {}\n".format(edge[1], movement.getDP(pointer[0]), movement.getCC(pointer[1]), edge[0].type) + + def updateStackInfo(self, stack): + baseString = "" + for item in reversed(stack): + baseString += "{}\n".format(item) + baseString.strip("\n") + + stackInfoMessage = self.builder.get_object("stackContents", self.programStateInfoFrame) + stackInfoMessage.configure(text=baseString) + + def updatePointersInfo(self, position, pointers): + print("Update pointers: {} -> Arrow: {}".format(pointers, movement.getArrow(pointers))) + baseString = "Pos: ({},{})\n".format(position[0], position[1]) + baseString += u"DP: {} ({},{})".format(movement.getArrow(pointers), movement.getDP(pointers[0]), movement.getCC(pointers[1])) + + pointersInfoMessage = self.builder.get_object("pointerMessage", self.programStateInfoFrame) + pointersInfoMessage.configure(text=baseString) diff --git a/GUI/TKinter/main.py b/GUI/TKinter/main.py new file mode 100644 index 0000000..c29cb08 --- /dev/null +++ b/GUI/TKinter/main.py @@ -0,0 +1,156 @@ +# helloworld.py +from time import sleep +import threading +import tkinter as tk +import pygubu + +import interpreter.imageWrapper as imageWrapper +import interpreter.lexer as lexer +import interpreter.lexerTokens as lexerTokens +import interpreter.colors as colors +import interpreter.movement as movement +import interpreter.programState as programState +import interpreter.main as main +import threading + +import infoManager +import canvasManager + + +class GUI: + def __init__(self): + # In pixelWidth/height per pixel. scaleSize = 25 means that every pixel will show as a 25x25 square + self.scaleSize = 25 + # In percentage + self.executionSpeed = 15 + + # In seconds + self.maxWait = 5 + + self.image = None + self.graph = None + self.programState = None + self.selectedPosition = None + + self.optionBar = None + self.actionBar = None + self.content = None + self.canvas = None + + #1: Create a builder + self.builder = builder = pygubu.Builder() + + #2: Load an ui file + builder.add_from_file('../assets/tkinterLayout.ui') + + #3: Create the mainwindow + self.mainwindow = builder.get_object('rootWindow') + + self.initializeFrames() + self.initializeCallbacks() + self.infoManager = infoManager.infoManager(self.builder, self.generalInfoFrame, self.programStateInfoFrame) + self.canvasManager = canvasManager.canvasManager(self.canvas, self.image, self.programState, self.scaleSize) + + + def run(self): + self.mainwindow.mainloop() + + + def initializeCallbacks(self): + self.builder.connect_callbacks({ + 'loadFile': self.loadFile, + 'setScale': self.setScale, + 'takeStep': self.takeStep, + 'setExecutionSpeed': self.setExecutionSpeed, + 'setBreakpoint': self.setBreakpoint, + 'runProgram': self.runProgram + }) + + self.canvas.bind("", self.canvasPressed) + + def initializeFrames(self): + self.optionBar = self.builder.get_object('optionBar', self.mainwindow) + self.content = self.builder.get_object('content', self.mainwindow) + self.actionBar = self.builder.get_object('actionBar', self.mainwindow) + self.generalInfoFrame = self.builder.get_object("generalInfoFrame", self.content) + self.programStateInfoFrame = self.builder.get_object("programStateInfoFrame", self.content) + canvasFrame = self.builder.get_object('canvasFrame', self.content) + self.canvas = self.builder.get_object('canvas', canvasFrame) + + + def update(self): + self.infoManager.updateInfo(self.image, self.graph, self.programState) + self.canvasManager.updateScaleSize(self.scaleSize) + self.canvasManager.updateImage(self.image) + self.canvasManager.updateProgramState(self.programState) + self.canvasManager.updateCanvas() + + + def takeStep(self): + if self.image is None or self.programState is None or self.graph is None: + return None + + newProgramState = main.takeStep(self.image, self.programState) + if isinstance(newProgramState, bool): + return False + + self.programState = newProgramState + self.selectedPosition = self.programState.position + self.update() + print("Take step!") + return True + + + def setBreakpoint(self): + print("BREAKPOINT") + + + def setExecutionSpeed(self, pos): + if 0 < float(pos) < 100: + self.executionSpeed = float(pos) + + def getWaitTime(self): + return self.executionSpeed/100*self.maxWait + + def runProgram(self): + if self.graph is None or self.image is None: + return None + + step = self.takeStep() + if step: + timer = threading.Timer(self.getWaitTime(), self.runProgram) + timer.start() + return True + else: + return False + + + def setScale(self): + scaleValue = int(self.builder.get_object('scaleEntry', self.optionBar).get()) + if 0 < scaleValue < 100: + self.scaleSize = int(scaleValue) + self.update() + print("SCALE") + + + def loadFile(self): + fileName = self.builder.get_object('fileNameEntry', self.optionBar).get() + self.image = imageWrapper.getImage(fileName) + self.graph = lexer.graphImage(self.image) + self.programState = programState.programState(self.graph, (0,0), (0,0)) + + self.update() + print("LOAD FILE!") + + + def canvasPressed(self, event): + unscaled_x = int(event.x / self.scaleSize) + unscaled_y = int(event.y / self.scaleSize) + + self.selectedPosition = (unscaled_x, unscaled_y) + self.update() + + +if __name__ == '__main__': + app = GUI() + app.run() diff --git a/GUI/__init__.py b/GUI/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/GUI/assets/tkinterLayout - kopie.ui b/GUI/assets/tkinterLayout - kopie.ui new file mode 100644 index 0000000..ee1c008 --- /dev/null +++ b/GUI/assets/tkinterLayout - kopie.ui @@ -0,0 +1,337 @@ + + + + 200 + 200 + + 0 + True + 0 + + + + 2 + 200 + 200 + + 0 + True + 0 + + + 0 + + + + + + loadFile + Open File + 7.5 + + 0 + 5 + 2 + 10 + 2 + True + 0 + + + + + + false + ../../Add.png + 44 + + 1 + 2 + True + 0 + w + + + + + + setScale + Set scale + int:scaleSize + + 2 + 5 + 2 + 10 + True + 0 + + + + + + 25 + + 3 + 2 + True + 0 + + + + + + + + 200 + 200 + + 0 + 7 + True + 1 + + + 5 + + + + + 15 + + + 10 + + + 15 + + + + + + runProgram + Run program + + 0 + True + 0 + + + + + + setExecutionSpeed + 0 + horizontal + 100 + 15 + int:pos + + 1 + True + 0 + + + + + + takeStep + takeStep + + 2 + 15 + True + 0 + e + + + + + + + + 200 + 200 + + 0 + True + 2 + + + 0 + + + 0 + + + + + + 200 + 200 + + 0 + True + 0 + n + + + 5 + + + 0 + + + 0 + + + 15 + + + + + + Codel info: + + 0 + True + 0 + + + + + + #ffffff + #a7a7a7 + 1 + Selected codel contains the following positions: +- (0,0) +- (0,1) + 150 + + 0 + 5 + True + 1 + + + + + + #FFFFFF + #a7a7a7 + 1 + Codel edges are as follows: + +(0,1) -> (1,0) push 0 + 150 + + 0 + True + 2 + + + + + + setBreakpoint + Set breakpoint + + 0 + True + 3 + + + + + + + + 200 + 200 + + 1 + True + 0 + n + + + + center + Stack + 15 + + 0 + True + 0 + + + + + + #ffffff + #a7a7a7 + 1 + 2 +15 +4 +0 + 150 + + 0 + True + 1 + + + + + + + + 200 + 200 + + 2 + True + 0 + + + + #ffffff + 400 + #8a8a8a + 1 + 700 + + 0 + True + 0 + + + + + + vertical + + 1 + True + 0 + + + + + + horizontal + + 0 + True + 1 + + + + + + + + + diff --git a/GUI/assets/tkinterLayout.ui b/GUI/assets/tkinterLayout.ui new file mode 100644 index 0000000..734128d --- /dev/null +++ b/GUI/assets/tkinterLayout.ui @@ -0,0 +1,371 @@ + + + + 200 + 200 + + 0 + True + 0 + + + + 2 + 200 + 200 + + 0 + True + 0 + + + 0 + + + + + + loadFile + Open File + 7.5 + + 0 + 5 + 2 + 10 + 2 + True + 0 + + + + + + false + ../../Add.png + 44 + + 1 + 2 + True + 0 + w + + + + + + setScale + Set scale + int:scaleSize + + 2 + 5 + 2 + 10 + True + 0 + + + + + + 75 + + 3 + 2 + True + 0 + + + + + + + + 200 + 200 + + 0 + 7 + True + 1 + + + 5 + + + + + 15 + + + 10 + + + 15 + + + + + + runProgram + Run program + + 0 + True + 0 + + + + + + setExecutionSpeed + 1 + horizontal + 100 + 15 + int:pos + + 1 + True + 0 + + + + + + takeStep + takeStep + + 2 + 15 + True + 0 + e + + + + + + + + 200 + 200 + + 0 + True + 2 + + + 0 + + + 0 + + + + + + 200 + 250 + + 0 + 5 + True + 0 + n + + + 5 + + + 0 + + + 0 + + + 15 + + + + + + Codel info: + + 0 + True + 0 + + + + + + #ffffff + #a7a7a7 + 1 + Selected codel contains the following positions: +- (0,0) +- (0,1) + 200 + + 0 + 5 + True + 1 + + + + + + #FFFFFF + #a7a7a7 + 1 + Codel edges are as follows: + +(0,1) -> (1,0) push 0 + 200 + + 0 + True + 2 + + + + + + setBreakpoint + Set breakpoint + + 0 + True + 3 + + + + + + + + 200 + 200 + + 1 + True + 0 + n + + + + center + #ffffff + Stack + 15 + + 0 + True + 1 + + + + + + center + #ffffff + #a7a7a7 + 1 + + 0 + True + 2 + + + + + + Program state: + + 0 + 2 + True + 0 + + + + + + #ffffff + Current direction + + 1 + True + 1 + + + + + + #ffffff + + 1 + True + 2 + + + + + + + + 200 + 200 + + 2 + True + 0 + + + + #ffffff + 400 + #8a8a8a + 1 + 700 + + 0 + 5 + 5 + 5 + 5 + True + 0 + + + + + + vertical + + 1 + True + 0 + + + + + + horizontal + + 0 + True + 1 + + + + + + + + + diff --git a/GUI/kivy/Debugger.kv b/GUI/kivy/Debugger.kv new file mode 100644 index 0000000..fda6dc9 --- /dev/null +++ b/GUI/kivy/Debugger.kv @@ -0,0 +1,68 @@ +: + rows : 3 + row_default_height: 25 + canvas: + Color: + rgb: [1, 1, 1, 1] + Rectangle: + pos:self.pos + size:self.size + OptionBar + ToolBar + ContentLayout + + +: + spacing:5 + padding: (3,1) + BoxLayout: + size: root.width*0.70, root.height + size_hint: None, None + Button: + id: myFileButton + text: "Open file:" + on_press: root.setFile(filePath.text) + TextInput: + id: filePath + hint_text: "File path" + cursor_color: (0,1,0,1) + multiline: False + + BoxLayout: + Button: + id: myOptionsButton + text: "Set pixel scale" + on_press: root.setScale(scaleSize.text) + TextInput: + id: scaleSize + input_type: "number" + hint_text: "10" + +: + Button: + id: myMiddleButton + text: "Next step" + + +: + rows: 1 + cols: 3 + size_hint_y : 100 + Button: + size: root.width*0.15, root.height + size_hint: None, None + id: myLeftBottomButton + text: "Left Bottom" + + ImageCanvas + + + Button: + size: root.width*0.15, root.height + size_hint: None, None + id: myRightBottmButton + text: "Right Bottom" + + +: + id: imageCanvas diff --git a/GUI/kivy/kivyWindow.py b/GUI/kivy/kivyWindow.py new file mode 100644 index 0000000..2521e59 --- /dev/null +++ b/GUI/kivy/kivyWindow.py @@ -0,0 +1,74 @@ +from kivy.app import App +from kivy.uix.button import Button +from kivy.uix.gridlayout import GridLayout +from kivy.uix.boxlayout import BoxLayout +from kivy.graphics.vertex_instructions import Line +from kivy.graphics.context_instructions import Color +from kivy.properties import ObjectProperty + +import interpreter.lexer as lexer +import interpreter.imageWrapper as imageWrapper + + +class GeneralLayout(GridLayout): + pass + +class ContentLayout(GridLayout): + pass + +class ImageCanvas(BoxLayout): + def __init__(self, **kwargs): + super().__init__(**kwargs) + image = None + + + def drawImage(self, image, scale): + app = App.get_running_app() + self.image = app.loadImage(image) + max_height = self.size + print(max_height) + with self.canvas: + for y_axis, row in enumerate(image): + for x_axis, pixel in enumerate(row): + x = x_axis*scale + y = y_axis*scale + + # with self.: + # Color([x/255 for x in pixel]) + # Line(points = [x_axis, x, y_axis, y]) + + +class OptionBar(BoxLayout): + def setFile(self, value): + app = App.get_running_app() + app.loadImage(value) + self.ids["filePath"].hint_text = value + self.ids['filePath'].text = "" + + def setScale(self, value): + app = App.get_running_app() + self.ids['scaleSize'].hint_text = value + self.ids['scaleSize'].text = "" + app.pixelScale = value + +class ToolBar(BoxLayout): + pass + + +class DebuggerApp(App): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.pixelScale = 10 + self.canvas = ObjectProperty() + + + def loadImage(self, filepath): + image = imageWrapper.getImage(filepath) + return image + + def build(self): + return GeneralLayout() + +if __name__ == '__main__': + GUI = DebuggerApp() + GUI.run() diff --git a/interpreter/__init__.py b/interpreter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/interpreter/colors.py b/interpreter/colors.py new file mode 100644 index 0000000..6f653e9 --- /dev/null +++ b/interpreter/colors.py @@ -0,0 +1,67 @@ +from typing import Dict + +import numpy as np + + +class possiblePixels: + def __init__(self): + self.colors = [ + [255, 192, 192], # Light red + [255, 0, 0], # Red + [192, 0, 0], # Dark red + [255, 255, 192], # Light yellow + [255, 255, 0], # Yellow + [192, 192, 0], # Dark yellow + [192, 255, 192], # Light green + [0, 255, 0], # Green + [0, 192, 0], # Dark green + [192, 255, 255], # Light cyan + [0, 255, 255], # Cyan + [0, 192, 192], # Dark cyan + [192, 192, 255], # Light blue + [0, 0, 255], # Blue + [0, 0, 192], # Dark blue + [255, 192, 255], # Light magenta + [255, 0, 255], # Magenta + [192, 0, 192] # Dark magenta + ] + self.white = [255, 255, 255] + self.black = [0, 0, 0] + + +def getPixelChange(colorStart: np.ndarray, colorEnd: np.ndarray) -> Dict[str, int]: + pixelsColors = possiblePixels() + + + if isWhite(colorStart) or isWhite(colorEnd): + return {"hueChange": 0, "lightChange": 0} + + # Converting np arrays to common lists + colorStart = list(colorStart)[:3] + colorEnd = list(colorEnd)[:3] + indexStart = pixelsColors.colors.index(colorStart) + indexEnd = pixelsColors.colors.index(colorEnd) + + # Calculating hue and lightness changes + hueChange = (int(indexEnd / 3) - int(indexStart / 3)) % 6 + lightChange = (indexEnd - indexStart) % 3 + + return {"hueChange": hueChange, "lightChange": lightChange} + + +def isWhite(testColor: np.ndarray) -> bool: + colors = possiblePixels() + testColor = list(testColor)[:3] + return testColor == colors.white + + +def isBlack(testColor: np.ndarray) -> bool: + colors = possiblePixels() + testColor = list(testColor)[:3] + return testColor == colors.black + + +def isColor(testColor: np.ndarray) -> bool: + colors = possiblePixels() + testColor = list(testColor)[:3] + return testColor in colors.colors diff --git a/interpreter/imageWrapper.py b/interpreter/imageWrapper.py new file mode 100644 index 0000000..418f830 --- /dev/null +++ b/interpreter/imageWrapper.py @@ -0,0 +1,126 @@ +from typing import Tuple, Union, Set, List + +from PIL import Image +import numpy as np + +import interpreter.movement as movement +import interpreter.colors as colors + + +def boundsChecker(image: np.ndarray, position: Tuple[int, int]) -> bool: + # Position 0 = x-axis, while matrix[0] = y-axis. This is why we compare position[0] with matrix[1] + return 0 <= position[0] < image.shape[1] and \ + 0 <= position[1] < image.shape[0] + + +def getPixel(image: np.ndarray, position: Tuple[int, int]) -> Union[np.ndarray, bool]: + """ + This function the pixel at a specific location + :param image: np.ndarray of image + :param position: wanted position + :return: either a cell or False, if the cell is not inside the image + """ + if boundsChecker(image, position): + return image[position[1]][position[0]] + else: + return False + + +def getImage(fileName: str) -> np.ndarray: + """ + Returns an np.ndarray of the image found at the given file location + :param fileName: Complete filename (including extension) + :return: np.ndarray of the image + """ + image = Image.open(fileName) + if fileName.split('.')[-1] == "gif": + image = image.convert("RGB") + return np.array(image) + + +def getCodel(image: np.ndarray, position: Tuple[int, int], foundPixels: Set[Tuple[int, int]] = None) -> Set[Tuple[int, int]]: + """ + This function finds all adjacent pixels with the same color as the pixel on the given position + + If you pass a white pixel, this will return a set with only the white pixel in it. + + :param image: The image with all pixel values + :param position: Starting position + :param foundPixels: currently found pixels + :return: A Set with all positions of same-colored pixels (Also known as a codel) + """ + if foundPixels is None: + foundPixels = set() + + # If this position is already in the set, it has already been traversed + if position in foundPixels: + return foundPixels + + if colors.isWhite(getPixel(image, position)): + foundPixels.add(position) + return foundPixels + + x = position[0] + y = position[1] + + foundPixels.add(position) + + # right + if boundsChecker(image, (x + 1, y)) and np.all(image[y][x + 1] == image[y][x]): + newPosition = (position[0] + 1, position[1]) + foundPixels = foundPixels.union(getCodel(image, newPosition, foundPixels)) + + # below + if boundsChecker(image, (x, y - 1)) and np.all(image[y - 1][x] == image[y][x]): + newPosition = (position[0], position[1] - 1) + foundPixels = foundPixels.union(getCodel(image, newPosition, foundPixels)) + + # left + if boundsChecker(image, (x - 1, y)) and np.all(image[y][x - 1] == image[y][x]): + newPosition = (position[0] - 1, position[1]) + foundPixels = foundPixels.union(getCodel(image, newPosition, foundPixels)) + + # above + if boundsChecker(image, (x, y + 1)) and np.all(image[y + 1][x] == image[y][x]): + newPosition = (position[0], position[1] + 1) + foundPixels = foundPixels.union(getCodel(image, newPosition, foundPixels)) + + return foundPixels + + +def getWhiteLine(image: np.ndarray, startPosition: Tuple[int, int], directionPointer: int, foundPixels: List[Tuple[int, int]] = None) -> List[Tuple[int, int]]: + """ + Finds all adjacent white pixels in the same direction + :param image: base image + :param startPosition: Starting position from which the white line starts + :param directionPointer: Direction in which the line goes + :param foundPixels: already found pixels + :return: A list of white pixels found + """ + + # Can't give mutable values as default parameter + if foundPixels is None: + foundPixels = [] + + # If it is already found, skip + if startPosition in foundPixels: + return foundPixels + + foundPixels.append(startPosition) + + # Get the new position, and check if the colors match + newPos = movement.getNextPosition(startPosition, directionPointer) + if boundsChecker(image, newPos) and colors.isWhite(image[newPos[1]][newPos[0]]): + return getWhiteLine(image, newPos, directionPointer, foundPixels) + else: + return foundPixels + + +def getNewWhiteDirection(image: np.ndarray, startPosition: Tuple[int, int], directionPointer: int) -> int: + newPosition = movement.getNextPosition(startPosition, directionPointer) + + if boundsChecker(image, newPosition) and (not colors.isBlack(getPixel(image, newPosition))): + return directionPointer + else: + return getNewWhiteDirection(image, startPosition, movement.flipDP(directionPointer)) + diff --git a/interpreter/lexer.py b/interpreter/lexer.py new file mode 100644 index 0000000..4232600 --- /dev/null +++ b/interpreter/lexer.py @@ -0,0 +1,138 @@ +from typing import List, Tuple, Set, Dict, Union + +import numpy as np + +import interpreter.colors as colors +import interpreter.imageWrapper as imageWrapper +import interpreter.lexerTokens as lexerTokens +import interpreter.movement as movement + + +def cyclePosition(image: np.ndarray, startPosition: Tuple[int, int]) -> Union[Tuple[int, int], bool]: + """ + :param image: numpy image array + :param startPosition: from where to go to Tuple (x,y) + :return: newPosition (x,y), or false if new position would fall out of bounds + """ + if not imageWrapper.boundsChecker(image, startPosition): + return False + + if startPosition[0] == image.shape[1] - 1: + if startPosition[1] < image.shape[0] - 1: + return (0, startPosition[1] + 1) + else: + return False + else: + return (startPosition[0] + 1, startPosition[1]) + + + +def getCodelsEfficient(image: np.ndarray, positionList: List[Tuple[int, int]]) -> List[Set[Tuple[int, int]]]: + if len(positionList) == 0: + return [] + copiedList = positionList.copy() + newPosition = copiedList.pop(0) + + + if colors.isBlack(imageWrapper.getPixel(image, newPosition)): + return getCodelsEfficient(image, copiedList) + + newCodel = imageWrapper.getCodel(image, newPosition) + + # print("Original positionList: {}".format(positionList)) + # print("Codel found: {}".format(newCodel)) + # Remove found positions from position list + copiedList = list(set(copiedList) - newCodel) + # print("New positionList: {}".format(copiedList)) + codelList = getCodelsEfficient(image, copiedList) + + codelList.append(newCodel) + return codelList + + + + +def getAllCodels(image: np.ndarray, position: Tuple[int, int] = (0, 0), + foundCodels: List[Set[Tuple[int, int]]] = None) -> List[Set[Tuple[int, int]]]: + if foundCodels is None: + foundCodels = [] + + # Checks if the current position is already in a found codel, and also if the current pixel is white or black + if (True in map(lambda codelSet, lambdaPosition=position: lambdaPosition in codelSet, foundCodels)) or colors.isBlack(imageWrapper.getPixel(image, position)): + nextPosition = cyclePosition(image, position) + if type(nextPosition) == bool and not nextPosition: + return foundCodels + return getAllCodels(image, nextPosition, foundCodels) + + newCodel = imageWrapper.getCodel(image, position) + foundCodels.append(newCodel) + + nextPosition = cyclePosition(image, position) + if type(nextPosition) == bool and nextPosition is False: + return foundCodels + else: + return getAllCodels(image, nextPosition, foundCodels) + + +def edgesToCodeldict(image: np.ndarray, edges: List[Tuple[Tuple[int, int], Tuple[int, int]]]) -> Dict[int, Tuple[lexerTokens.baseLexerToken, Tuple[int, int]]]: + """ + Constructs a dictionary with each pointer possibility as key and (token, position) as value + :param image: Image required to find calculate tokens + :param edges: List[Tuple[position, pointers]] + :return: + """ + return dict(map(lambda x, lambdaImage=image: (hash(x[1]), (lexerTokens.edgeToToken(lambdaImage, x), x[0])), edges)) + + +def isCodeldictTerminate(codelDict: Dict[int, Tuple[lexerTokens.baseLexerToken, Tuple[int, int]]]) -> bool: + return all(map(lambda x: isinstance(x[1][0], lexerTokens.toBlackToken), codelDict.items())) + + +def codelDictToTerminate(codelDict: Dict[int, Tuple[lexerTokens.baseLexerToken, Tuple[int, int]]]) -> Dict[int, Tuple[lexerTokens.terminateToken, Tuple[int, int]]]: + return dict(map(lambda x: (x[0], (lexerTokens.terminateToken(), x[1][1])), codelDict.items())) + + +def codelToCodelDict(image: np.ndarray, codel: Set[Tuple[int, int]], edgePointers: List[Tuple[int, int]]) -> Dict[int, Tuple[lexerTokens.baseLexerToken, Tuple[int, int]]]: + """ + :param image: image + :param codel: set of positions within the same color + :param edgePointers: list of pointers to find tokens for + :return: A dictionary with each pointer possibility as key and (token, position) as value + """ + # make codel immutable + copiedCodel = frozenset(codel) + # Find all edges along the codel and edgepointers + edges = list(map(lambda pointers, lambdaCodel=copiedCodel: (movement.findEdge(lambdaCodel, pointers), pointers), edgePointers)) + codelDict = edgesToCodeldict(image, edges) + + if isCodeldictTerminate(codelDict): + codelDict = codelDictToTerminate(codelDict) + + return codelDict + + +def graphImage(image: np.ndarray, position: Tuple[int, int] = (0, 0)) -> Dict[int, Dict[int, Tuple[lexerTokens.baseLexerToken, Tuple[int, int]]]]: + """ + Returns a dict with hashes of each codel as keys, and a codelDict as value. That codelDict contains hashed pointers (Tuple[int, int]) as keys to tokens as values. + :param image: + :param position: + :return: + """ + # allCodels = getAllCodels(image, position) + allPositions = [] + whiteCodels = [] + print(image) + for y, row in enumerate(image): + for x, pixel in enumerate(row): + if not colors.isBlack(pixel): + if colors.isWhite(pixel): + whiteCodels.append((x,y)) + else: + allPositions.append((x,y)) + print(len(allPositions)) + + + allCodels = getCodelsEfficient(image, allPositions) + # Get an iterator of all possible pointers + edgePointers = list(map(lambda i: (i % 4, int(i / 4)), iter(range(8)))) + return dict(map(lambda x: (hash(frozenset(x)), codelToCodelDict(image, x, edgePointers)), allCodels)) diff --git a/interpreter/lexerTokens.py b/interpreter/lexerTokens.py new file mode 100644 index 0000000..a4e0bc3 --- /dev/null +++ b/interpreter/lexerTokens.py @@ -0,0 +1,77 @@ +from typing import Tuple, Union +import numpy as np + +import interpreter.imageWrapper as imageWrapper +import interpreter.movement as movement +import interpreter.colors as colors + + +class baseLexerToken(): + def __init__(self, tokenType: str): + self.tokenType = tokenType + + def __str__(self): + return "Token tokenType = {}".format(self.tokenType) + + def __repr__(self): + return str(self) + + +class toBlackToken(baseLexerToken): + def __init__(self, tokenType: str = "toBlack"): + super().__init__(tokenType) + + +class toWhiteToken(baseLexerToken): + def __init__(self): + super().__init__("toWhite") + + +class terminateToken(baseLexerToken): + def __init__(self): + super().__init__("exit") + + +class toColorToken(baseLexerToken): + def __init__(self, tokenType: str, codelSize: int): + super().__init__(tokenType) + self.codelSize = codelSize + + def __str__(self): + return "{}, codelSize = {}".format(super().__str__(), self.codelSize) + + +def getToken(hueChange: int, lightChange: int) -> str: + tokens = [ + ["noop", "push", "pop"], + ["add", "subtract", "multiply"], + ["divide", "mod", "not"], + ["greater", "pointer", "switch"], + ["duplicate", "roll", "inN"], + ["inC", "outN", "outC"], + ] + return tokens[hueChange][lightChange] + + +def edgeToToken(image: np.ndarray, edge: Tuple[Tuple[int, int], Tuple[int, int]]) -> Union[baseLexerToken, bool]: + """ + :param image: + :param edge: (position, direction) + :return: + """ + if not imageWrapper.boundsChecker(image, edge[0]): + return False + + nextPosition = movement.getNextPosition(edge[0], edge[1][0]) + if not imageWrapper.boundsChecker(image, nextPosition): + return toBlackToken("edge") + + elif colors.isBlack(imageWrapper.getPixel(image, nextPosition)): + return toBlackToken("toBlack") + + if colors.isWhite(imageWrapper.getPixel(image, nextPosition)): + return toWhiteToken() + + colorChange = colors.getPixelChange(imageWrapper.getPixel(image, edge[0]), imageWrapper.getPixel(image, nextPosition)) + tokenType = getToken(colorChange['hueChange'], colorChange['lightChange']) + return toColorToken(tokenType, len(imageWrapper.getCodel(image, edge[0]))) diff --git a/interpreter/main.py b/interpreter/main.py new file mode 100644 index 0000000..5e7a77a --- /dev/null +++ b/interpreter/main.py @@ -0,0 +1,96 @@ +import copy +from typing import Union +import threading +import time +import sys + +import numpy as np + +import interpreter.imageWrapper as imageWrapper +import interpreter.lexer as lexer +import interpreter.lexerTokens as lexerTokens +import interpreter.movement as movement +import interpreter.programState as programState +import interpreter.runner as runner + + +def interpret(image: np.ndarray): + graph = lexer.graphImage(im) + position = (0, 0) + pointers = (0, 0) + PS = programState.programState(graph, position, pointers) + + runProgram(image, PS) + + +def runProgram(image: np.ndarray, PS: programState) -> programState: + newState = PS + currentCodel = imageWrapper.getCodel(image, newState.position) + + frozencodel = frozenset(currentCodel) + newToken = newState.graph[hash(frozencodel)][hash(newState.pointers)][0] + + if isinstance(newToken, lexerTokens.terminateToken): + print("") + print("TERMINATE!") + return newState + + newState = takeStep(image, newState) + + return runProgram(image, newState) + + +def takeStep(image: np.ndarray, PS: programState.programState) -> Union[programState.programState, bool]: + newState = copy.deepcopy(PS) + currentCodel = imageWrapper.getCodel(image, newState.position) + + frozencodel = frozenset(currentCodel) + newToken = newState.graph[hash(frozencodel)][hash(newState.pointers)][0] + edgePosition = newState.graph[hash(frozencodel)][hash(newState.pointers)][1] + result = runner.executeToken(newToken, newState.pointers, newState.dataStack) + + if result is None: + print("TERMINATE") + return False + + if isinstance(newToken, lexerTokens.toWhiteToken) or isinstance(newToken, lexerTokens.toColorToken): + newState.position = movement.getNextPosition(edgePosition, newState.pointers[0]) + + newState.pointers = result[0] + newState.dataStack = result[1] + + return newState + + +class run: + def __init__(self, image: np.ndarray): + self.image = image + + + def __call__(self): + self.run_program(self.image, programState.programState(lexer.graphImage(self.image), (0,0), (0,0)) ) + + + def run_program(self,image: np.ndarray, PS: programState) -> programState: + currentCodel = imageWrapper.getCodel(image, PS.position) + + frozencodel = frozenset(currentCodel) + newToken = PS.graph[hash(frozencodel)][hash(PS.pointers)][0] + + if isinstance(newToken, lexerTokens.terminateToken): + print("") + print("TERMINATE!") + return PS + return self.run_program(image, takeStep(image, PS)) + + +if __name__ == "__main__": + im = imageWrapper.getImage("../brainfuck_interpreter_black.png") + interpret(im) + + start_time = time.time() + sys.setrecursionlimit(0x100000) + threading.stack_size(256000000) #set stack to 256mb + t = threading.Thread(target=run(im)) + t.start() + t.join() diff --git a/interpreter/movement.py b/interpreter/movement.py new file mode 100644 index 0000000..d4a218e --- /dev/null +++ b/interpreter/movement.py @@ -0,0 +1,168 @@ +from typing import Tuple, Set, Union + + +def getDP(directionPointer: int) -> str: + if directionPointer == 0: + return 'r' + elif directionPointer == 1: + return 'd' + elif directionPointer == 2: + return 'l' + else: + return 'u' + + +def getCC(codelChooser: int) -> str: + if codelChooser == 0: + return 'l' + else: + return 'r' + + +def getArrow(pointers: Tuple[int, int]) -> str: + if pointers[0] == 0: + if pointers[1] == 0: + return "\u2197" + elif pointers[1] == 1: + return "\u2198" + elif pointers[0] == 1: + if pointers[1] == 0: + return "\u2198" + elif pointers[1] == 1: + return "\u2199" + elif pointers[0] == 2: + if pointers[1] == 0: + return "\u2199" + elif pointers[1] == 1: + return "\u2196" + elif pointers[0] == 3: + if pointers[1] == 0: + return "\u2196" + elif pointers[1] == 1: + return "\u2197" + else: + return "" + + +def flipCC(codelChooser: int) -> int: + """ + Flips the codelChooser 0 -> 1, 1 -> 0 + :param codelChooser: unflipped codelChooser + :return: flipped codelChooser + """ + return int(not codelChooser) + + +def flipDP(directionPointer: int) -> int: + """ + Cycles the directionpointer 0 -> 1, 1 -> 2, 2 -> 3, 3 -> 0 + :param directionPointer: unflipped directionPointer + :return: new DirectionPointer + """ + if directionPointer != 3: + return directionPointer + 1 + return 0 + + +def flip(pointers: Tuple[int, int]) -> Tuple[int, int]: + """ + Chooses what part of the general pointer to flip, by DP%2 == CC rule, providing the following flow: + (0,0) -> (0,1) + (0,1) -> (1,1) + (1,1) -> (1,0) + (1,0) -> (2,0) + (2,0) -> (2,1) + (2,1) -> (3,1) + (3,1) -> (3,0) + (3,0) -> (0,0) + :param pointers: Original state of the pointers + :return: Tuple of ints containing new pointers + """ + if pointers[0] % 2 == pointers[1]: + return (pointers[0], flipCC(pointers[1])) + else: + return (flipDP(pointers[0]), pointers[1]) + + +# TODO FIX KEYERROR +def getNextPosition(startPosition: Tuple[int, int], directionPointer: int) -> Union[Tuple[int, int], KeyError]: + if directionPointer == 0: + return (startPosition[0] + 1, startPosition[1]) + elif directionPointer == 1: + return (startPosition[0], startPosition[1] + 1) + elif directionPointer == 2: + return (startPosition[0] - 1, startPosition[1]) + elif directionPointer == 3: + return (startPosition[0], startPosition[1] - 1) + else: + return KeyError("Given key {} is no valid Direction Pointer (0, 1, 2, or 3)".format(directionPointer)) + + +def getPreviousPosition(startPosition: Tuple[int, int], directionPointer: int) -> Tuple[int, int]: + if directionPointer == 0: + return getNextPosition(startPosition, 2) + elif directionPointer == 1: + return getNextPosition(startPosition, 3) + elif directionPointer == 2: + return getNextPosition(startPosition, 0) + return getNextPosition(startPosition, 1) + # TODO: make the else return an error, and elif return 'd' position + + +# TODO Error handling +def findEdge(codel: Set[Tuple[int, int]], pointers: Tuple[int, int]) -> Union[Tuple[int, int], bool]: + """ + Finds the edge of the codel according to the direction pointer and the codel chooser + :param codel: Set of adjacent positions with the same color + :param pointers: Tuple where pointers[0] = DP and pointers[1] = CC + :return: Position within the codel that is adjacent to the next pixel to go to + """ + dp = pointers[0] + cc = pointers[1] + + if dp == 0: + edgePosition = max(codel, key=lambda lambdaPos: lambdaPos[0]) + for pos in codel: + if pos[0] == edgePosition[0]: + # -> ^ Right and up + if cc == 0 and pos[1] < edgePosition[1]: + edgePosition = pos + # -> V Right and down + elif cc == 1 and pos[1] > edgePosition[1]: + edgePosition = pos + return edgePosition + elif dp == 1: + edgePosition = max(codel, key=lambda lambdaPos: lambdaPos[1]) + for pos in codel: + if pos[1] == edgePosition[1]: + # V -> Down and right + if cc == 0 and pos[0] > edgePosition[0]: + edgePosition = pos + # V <- Down and left + elif cc == 1 and pos[0] < edgePosition[0]: + edgePosition = pos + return edgePosition + elif dp == 2: + edgePosition = min(codel, key=lambda lambdaPos: lambdaPos[0]) + for pos in codel: + if pos[0] == edgePosition[0]: + # <- V Left and down + if cc == 0 and pos[1] > edgePosition[1]: + edgePosition = pos + # <- ^ left and up + elif cc == 1 and pos[1] < edgePosition[1]: + edgePosition = pos + return edgePosition + elif dp == 3: + edgePosition = min(codel, key=lambda lambdaPos: lambdaPos[1]) + for pos in codel: + if pos[1] == edgePosition[1]: + # ^ <- Up and left + if cc == 0 and pos[0] < edgePosition[0]: + edgePosition = pos + # ^ -> Up and right + elif cc == 1 and pos[0] > edgePosition[0]: + edgePosition = pos + return edgePosition + else: + raise SyntaxError("DirectionPointer '{}' is unknown".format(dp)) diff --git a/interpreter/programState.py b/interpreter/programState.py new file mode 100644 index 0000000..70873e9 --- /dev/null +++ b/interpreter/programState.py @@ -0,0 +1,24 @@ +from typing import Dict, List, Tuple +from copy import deepcopy + +import interpreter.lexerTokens as lexerTokens + + +class programState(): + def __init__(self, graph: Dict[int, Dict[int, Tuple[lexerTokens.baseLexerToken, Tuple[int, int]]]], position: Tuple[int, int], pointers: Tuple[int, int], dataStack: List[int] = None): + if dataStack is None: + dataStack = [] + + self.graph = graph + self.pointers = pointers + self.position = position + self.dataStack = dataStack + + def __str__(self): + return "{pos} / {pointers}. Stack: {stack}".format(pos=self.position, pointers=self.pointers, stack=self.dataStack) + + def __repr__(self): + return str(self) + + def __deepcopy__(self, memodict): + return programState(self.graph, deepcopy(self.position), deepcopy(self.pointers), deepcopy(self.dataStack)) diff --git a/interpreter/runner.py b/interpreter/runner.py new file mode 100644 index 0000000..8912538 --- /dev/null +++ b/interpreter/runner.py @@ -0,0 +1,277 @@ +from typing import List, Tuple + +import interpreter.lexerTokens as lexerTokens +import interpreter.movement as movement + + +# TODO Nettere afhandeling errors (Union[Tuple[List[int], Tuple[int, int]], bool]) +# TODO Test cases maken per token +def executeToken(token: lexerTokens.baseLexerToken, pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + if isinstance(token, lexerTokens.toBlackToken): + newPointers = movement.flip(pointers) + return (newPointers, dataStack) + elif isinstance(token, lexerTokens.toWhiteToken): + return (pointers, dataStack) + elif isinstance(token, lexerTokens.toColorToken): + result = executeColorToken(token, pointers, dataStack) + return (result[0], result[1]) + + +def executeColorToken(token: lexerTokens.toColorToken, pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + if token.tokenType == "noop": + return noopOperator(pointers, dataStack) + elif token.tokenType == "push": + # Needs the codelsize to push + return pushOperator(token, pointers, dataStack) + elif token.tokenType == "pop": + return popOperator(pointers, dataStack) + + elif token.tokenType == "add": + return addOperator(pointers, dataStack) + elif token.tokenType == "subtract": + return subtractOperator(pointers, dataStack) + elif token.tokenType == "multiply": + return multiplyOperator(pointers, dataStack) + + elif token.tokenType == "divide": + return divideOperator(pointers, dataStack) + elif token.tokenType == "mod": + return modOperator(pointers, dataStack) + elif token.tokenType == "not": + return notOperator(pointers, dataStack) + + elif token.tokenType == "greater": + return greaterOperator(pointers, dataStack) + elif token.tokenType == "pointer": + return pointerOperator(pointers, dataStack) + elif token.tokenType == "switch": + return switchOperator(pointers, dataStack) + + elif token.tokenType == "duplicate": + return duplicateOperator(pointers, dataStack) + elif token.tokenType == "roll": + return rollOperator(pointers, dataStack) + elif token.tokenType == "inN": + return inNOperator(pointers, dataStack) + + elif token.tokenType == "inC": + return inCOperator(pointers, dataStack) + elif token.tokenType == "outN": + return outNOperator(pointers, dataStack) + elif token.tokenType == "outC": + return outCOperator(pointers, dataStack) + else: + # TODO Elegantere manier van afhandelen + print("Type niet gevonden, noop uitgevoerd") + return noopOperator(pointers, dataStack) + + +def noopOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + """ + Does nothing + :param pointers: The tuple with the direction pointer and codel chooser + :param dataStack: input dataStack + :return: Tuple of a copy of the dataStack and the endpointers of the token + """ + return (pointers, list(dataStack)) + + +def addOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + """ + Pops the two values from the stack and add them together, then pushes the result + :param pointers: The tuple with the direction pointer and codel chooser + :param dataStack: input datastack + :return: + """ + newStack = list(dataStack) + if len(newStack) < 2: + return (pointers, newStack) + newStack.append(newStack.pop() + newStack.pop()) + return (pointers, newStack) + + +def subtractOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + if len(newStack) < 2: + return (pointers, newStack) + + first = newStack.pop() + second = newStack.pop() + newStack.append(second - first) + return (pointers, newStack) + + +def multiplyOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + if len(newStack) < 2: + return (pointers, newStack) + newStack.append(newStack.pop() * newStack.pop()) + return (pointers, newStack) + + +def divideOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + """ + Provides integer division (//) + :param pointers: The tuple with the direction pointer and codel chooser + :param dataStack: A list of ints as stack. last entry is the top + :return: Tuple with the new data stack and new pointers + """ + newStack = list(dataStack) + if len(newStack) < 2: + return (pointers, newStack) + + first = newStack.pop() + second = newStack.pop() + if second == 0: + raise ZeroDivisionError("{} / {} ".format(first, second)) + newStack.append(newStack.pop() // newStack.pop()) + return (pointers, newStack) + + +def modOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + if len(newStack) < 2: + return (pointers, newStack) + valA = newStack.pop() + valB = newStack.pop() + if valB == 0: + return (pointers, newStack) + newStack.append(valA % valB) + return (pointers, newStack) + + +def greaterOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + """ + Compares the second value of the stack with the first value of the stack. If the stack is empty, this gets ignored + :param pointers: The tuple with the direction pointer and codel chooser + :param dataStack: The list of values as the stack, last entry is the top of the stack + :return: A tuple of pointers and new data stack + """ + newStack = list(dataStack) + if len(newStack) < 2: + return (pointers, newStack) + + newStack.append(int(newStack.pop() < newStack.pop())) + return (pointers, newStack) + + +def notOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + """ + Compares the second value of the stack with the first value of the stack + :param pointers: The tuple with the direction pointer and codel chooser + :param dataStack: The input list of ints as stcak. Last entry is the top of the stack + :return: A tuple of pointers and new data stack + """ + newStack = list(dataStack) + if len(newStack) < 1: + return (pointers, newStack) + + result = 1 if newStack.pop() == 0 else 0 + newStack.append(result) + return (pointers, newStack) + + +def pointerOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + if len(newStack) < 1: + return (pointers, newStack) + + dpTurnCount = newStack.pop() % 4 + newDp = (pointers[0] + dpTurnCount) % 4 + return ((newDp, pointers[1]), newStack) + + +def switchOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + if len(newStack) < 1: + return (pointers, newStack) + + ccTurnCount = newStack.pop() % 2 + newCC = (pointers[1] + ccTurnCount) % 2 + return ((pointers[0], newCC), newStack) + + +# TODO BETERE IO +def inNOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + newVal = int(input("Input number: ")) + newStack.append(newVal) + return (pointers, newStack) + + +def inCOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + newVal = input("Input character") + if len(newVal) < 1: + return (pointers, newStack) + + appendedStack = pushCharacters(newStack, newVal) + return (pointers, appendedStack) + + +def pushCharacters(dataStack: List[int], characters: str) -> List[int]: + newStack = list(dataStack) + if len(characters) < 1: + return newStack + else: + newStack.append(ord(characters[0])) + return pushCharacters(newStack, characters[1:]) + + +def outNOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + print(newStack.pop(), end="") + return (pointers, newStack) + + +def outCOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + print(chr(newStack.pop()), end="") + return (pointers, newStack) + + +def pushOperator(token: lexerTokens.toColorToken, pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + newStack.append(token.codelSize) + return (pointers, newStack) + + +def popOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + if len(newStack) < 1: + return (pointers, newStack) + newStack.pop() + return (pointers, newStack) + + +def duplicateOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + if len(newStack) < 1: + return (pointers, newStack) + + val = newStack.pop() + newStack.append(val) + newStack.append(val) + return (pointers, newStack) + + +def rollOperator(pointers: Tuple[int, int], dataStack: List[int]) -> Tuple[Tuple[int, int], List[int]]: + newStack = list(dataStack) + if len(newStack) < 3: + return (pointers, newStack) + rolls = newStack.pop() + depth = newStack.pop() + insertIndex = len(newStack) - depth + + if depth <= 0 or insertIndex < 0 or insertIndex >= len(newStack) or rolls == 0 or depth == rolls: + return (pointers, newStack) + + # TODO could also do rolls % depth times, instead of rolls times + if rolls < 0: + for i in range(abs(rolls)): + newStack.append(newStack.pop(insertIndex)) + else: + for i in range(rolls): + newStack.insert(insertIndex, newStack.pop()) + + return (pointers, newStack)