from typing import List, Tuple, Union import copy import interpreter.tokens as lexerTokens import interpreter.movement as movement import interpreter.errors as errors from interpreter.dataStructures import direction # TODO Nettere afhandeling errors (Union[Tuple[List[int], Tuple[int, int]], bool]) # TODO Test cases maken per token def executeToken(token: lexerTokens.baseLexerToken, inputDirection: direction, dataStack: List[int]) -> Union[Tuple[direction, List[int]], BaseException]: """ Executes the function associated with tokens :param token: Input token :param inputDirection: Input direction :param dataStack: Input stack :return: Either a combination of a new stack and direction, or a runtime Exception """ if isinstance(token, lexerTokens.toBlackToken): newPointers = movement.flip(inputDirection) return (newPointers, dataStack) if isinstance(token, lexerTokens.toWhiteToken): return (inputDirection, dataStack) if isinstance(token, lexerTokens.toColorToken): return executeColorToken(token, inputDirection, dataStack) if isinstance(token, lexerTokens.terminateToken): return (inputDirection, dataStack) return errors.UnknownTokenError("Token of type {} is unknown") def executeColorToken(token: lexerTokens.toColorToken, inputDirection: direction, dataStack: List[int]) -> Union[Tuple[direction, List[int]], BaseException]: """ Executes the to color operations :param token: input token :param inputDirection: Input direction :param dataStack: Input data stack :return: either a combination of a new direction and data stack, or a runtime Exception """ if token.tokenType == "noop": return noopOperator(inputDirection, dataStack) elif token.tokenType == "push": # Needs the codelsize to push return pushOperator(token, inputDirection, dataStack) elif token.tokenType == "pop": return popOperator(inputDirection, dataStack) elif token.tokenType == "add": return addOperator(inputDirection, dataStack) elif token.tokenType == "subtract": return subtractOperator(inputDirection, dataStack) elif token.tokenType == "multiply": return multiplyOperator(inputDirection, dataStack) elif token.tokenType == "divide": return divideOperator(inputDirection, dataStack) elif token.tokenType == "mod": return modOperator(inputDirection, dataStack) elif token.tokenType == "not": return notOperator(inputDirection, dataStack) elif token.tokenType == "greater": return greaterOperator(inputDirection, dataStack) elif token.tokenType == "pointer": return pointerOperator(inputDirection, dataStack) elif token.tokenType == "switch": return switchOperator(inputDirection, dataStack) elif token.tokenType == "duplicate": return duplicateOperator(inputDirection, dataStack) elif token.tokenType == "roll": return rollOperator(inputDirection, dataStack) elif token.tokenType == "inN": return inNOperator(inputDirection, dataStack) elif token.tokenType == "inC": return inCOperator(inputDirection, dataStack) elif token.tokenType == "outN": return outNOperator(inputDirection, dataStack) elif token.tokenType == "outC": return outCOperator(inputDirection, dataStack) else: return errors.UnknownTokenError("Token {} not found".format(token.tokenType)) def noopOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, 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 (copy.deepcopy(inputDirection), dataStack.copy()) def addOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, 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 = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 2: return (inputDirection, newStack) newStack.append(newStack.pop() + newStack.pop()) return (inputDirection, newStack) def subtractOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, List[int]]: """ Subtracts the second value from the first value of the stack """ newStack = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 2: return (inputDirection, newStack) first = newStack.pop() second = newStack.pop() newStack.append(second - first) return (inputDirection, newStack) def multiplyOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, List[int]]: """ Pops the first 2 values from the stack, and pushes the product of them """ newStack = list(dataStack) inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 2: return (inputDirection, newStack) newStack.append(newStack.pop() * newStack.pop()) return (inputDirection, newStack) def divideOperator(inputDirection: direction, dataStack: List[int]) -> Union[Tuple[direction, List[int]], BaseException]: """ 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 = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 2: return (inputDirection, newStack) first = newStack.pop() second = newStack.pop() if second == 0: return ZeroDivisionError("Division by zero {}/{}".format(first, second)) newStack.append(int(second / first)) return (inputDirection, newStack) def modOperator(inputDirection: direction, dataStack: List[int]) -> Union[Tuple[direction, List[int]], BaseException]: """ Pops the first two values of the stack, mods the second value by the first value and pushes the result back to the stack :param inputDirection: :param dataStack: :return: Tuple of direction and new data stack """ newStack = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 2: return (inputDirection, newStack) valA = newStack.pop() valB = newStack.pop() if valB == 0: # TODO ERROR return ZeroDivisionError("Second value is 0: {}%{}".format(valA, valB)) newStack.append(valB % valA) return (inputDirection, newStack) def greaterOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, 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 = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 2: return (inputDirection, newStack) valA = newStack.pop() valB = newStack.pop() newStack.append(int(valB > valA)) return (inputDirection, newStack) def notOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, 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 = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 1: return (inputDirection, newStack) result = 1 if newStack.pop() == 0 else 0 newStack.append(result) return (inputDirection, newStack) def pointerOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, List[int]]: """ Pop the top value of the stack, and turn the direction pointer that many times. (counter clockwise if negative) :param inputDirection: :param dataStack: :return: """ newStack = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 1: return (inputDirection, newStack) dp = inputDirection.pointers[0] dpTurnCount = newStack.pop() # Python module makes negative modulo's positive, so we need to manually flip the DP the required amount of times if dpTurnCount < 0: dp = movement.flipDPInvert(dp, dpTurnCount) return (direction((dp, inputDirection.pointers[1])), newStack) else: # Cycle the DP forward by using the module operator newDP = (inputDirection.pointers[0] + (dpTurnCount % 4)) % 4 return (direction((newDP, inputDirection.pointers[1])), newStack) def switchOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, List[int]]: """ Pop the first value of the stack, and turn the codel chooser that many times. :param pointers: :param dataStack: :return: """ newStack = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 1: return (inputDirection, newStack) ccTurnCount = abs(newStack.pop()) % 2 newCC = (inputDirection.pointers[1] + ccTurnCount) % 2 return (direction((inputDirection.pointers[0], newCC)), newStack) def inNOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, List[int]]: """ Add a number from the input. If it isn't a number, nothing is added instead """ newStack = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) newVal = input("Input number: ") if newVal.isdigit(): newStack.append(newVal) return (inputDirection, newStack) def inCOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, List[int]]: """ Add a numeric representation of a character to the stack. """ newStack = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) newVal = input("Input character") if len(newVal) < 1: return (inputDirection, newStack) newStack.append(ord(newVal[0])) return (inputDirection, newStack) def outNOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, List[int]]: """ Pops the top number from the stack and outputs it as a number """ newStack = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 1: return (inputDirection, newStack) print(newStack.pop(), end="") return (inputDirection, newStack) def outCOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, List[int]]: """ Pops the top number from the stack and outputs it as a number. Does nothing if top value is negative """ newStack = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 1: return (inputDirection, newStack) valA = newStack.pop() if valA < 0: newStack.append(valA) return (inputDirection, newStack) print(chr(valA), end="") return (inputDirection, newStack) def pushOperator(token: lexerTokens.toColorToken, inputDirection: direction, dataStack: List[int]) -> Tuple[direction, List[int]]: """ Pushes the codelsize of the token to the stack """ newStack = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) newStack.append(token.codelSize) return (inputDirection, newStack) def popOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, List[int]]: """ Pops and discards the top number of the stack """ newStack = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 1: return (inputDirection, newStack) newStack.pop() return (inputDirection, newStack) def duplicateOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, List[int]]: """ Duplicates the top value of the stack """ newStack = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 1: return (inputDirection, newStack) val = newStack.pop() newStack.append(val) newStack.append(val) return (inputDirection, newStack) def rollOperator(inputDirection: direction, dataStack: List[int]) -> Tuple[direction, List[int]]: """ Rolls the stack x times, to a depth of y, where x is equal to the top value of the stack, and y is equal to the second value of the stack """ newStack = dataStack.copy() inputDirection = copy.deepcopy(inputDirection) if len(newStack) < 2: return (inputDirection, newStack) rolls = newStack.pop() depth = newStack.pop() insertIndex = len(newStack) - depth newStack = rollStack(newStack, rolls, insertIndex) return (inputDirection, newStack) def rollStack(dataStack: List[int], numberOfRolls: int, insertIndex: int) -> List[int]: """ Rolls the stack recursively, and inverted when negative number of rolls :param dataStack: Input stack :param numberOfRolls: Number of rolls :param insertIndex: At which index to either insert new values, or to get values from :return: Rolled data stack """ newStack = dataStack.copy() if numberOfRolls > 0: newStack.insert(insertIndex, newStack.pop()) return rollStack(newStack, numberOfRolls - 1, insertIndex) elif numberOfRolls < 0: newStack.append(newStack.pop(insertIndex)) return rollStack(newStack, numberOfRolls + 1, insertIndex) else: return newStack