#!/usr/bin/env python3 import sys from enum import Enum class State(Enum): # States INSERT = 0 MATCH = 1 REMOVE = 2 # Substates FETCH = 3 LITERAL = 4 VARIABLE = 5 class Machine: def __init__(self, code, hooks=[]): self.code = code self.literals = "0123456789abcdefABCDEF" self.length = len(code) self.cursor = 0 self.stack = 0 self.state = State.INSERT self.substate = State.FETCH self.stacks = [[] for _ in range(0x10)] self.offsets = [0] * 0x10 self.variables = {} self.hooks = hooks for hook in hooks: hook(self) def seek(self, instruction): while self.cursor < self.length: if self.code[self.cursor] == '`' and instruction != '`': self.cursor += 1 self.seek('`') self.cursor += 1 elif self.code[self.cursor] == instruction: break else: self.cursor += 1 def branch(self): self.clear() self.seek('.') def transition(new): def instruction(self): self.state = new self.substate = State.FETCH self.cursor += 1 return self.cursor >= self.length return instruction def variable(self): if self.substate is State.LITERAL: self.substate = State.VARIABLE self.cursor += 1 return self.cursor >= self.length def clear(self): self.variables.clear() for stack in range(0x10): self.offsets[stack] = 0 def reset(self): self.clear() self.cursor = 0 self.state = State.INSERT self.substate = State.FETCH self.seek('?') for hook in self.hooks: hook(self) def number(self, instruction): try: return int(instruction, 16) except: return None def insert(self, instruction): if self.substate is State.LITERAL: self.stacks[self.stack].append(instruction) else: if instruction in self.variables: self.stacks[self.stack].append(self.variables[instruction]) else: self.stacks[self.stack].append(0) self.substate = State.FETCH self.cursor += 1 return self.cursor >= self.length def match(self, instruction): depth = len(self.stacks[self.stack]) offset = self.offsets[self.stack] if depth == 0 or offset >= depth: self.branch() else: if self.substate is State.LITERAL: if self.stacks[stack][-1 - offset] != instruction: self.branch() elif self.substate is State.VARIABLE: if instruction in self.variables: if self.variables[instruction] != self.stacks[self.stack][-1 - offset]: self.branch() else: self.variables[instruction] = self.stacks[self.stack][-1 - offset] self.substate = State.FETCH self.cursor += 1 return self.cursor >= self.length def literal(self, instruction): if self.substate is State.FETCH: if self.state is State.REMOVE: self.stacks[instruction].pop() else: self.stack = instruction self.substate = State.LITERAL self.cursor += 1 return self.cursor >= self.length if self.state is State.INSERT: return self.insert(instruction) return self.match(instruction) def comment(self): self.cursor += 1 self.seek('`') self.cursor += 1 return self.cursor >= self.length def step(self): instructions = { '+': Machine.transition(State.INSERT), '?': Machine.transition(State.MATCH), '-': Machine.transition(State.REMOVE), '$': Machine.variable, '.': Machine.reset, '`': Machine.comment } if self.cursor >= self.length: return True instruction = self.code[self.cursor] if instruction in instructions: return instructions[instruction](self) elif instruction in self.literals: return self.literal(self.number(instruction)) self.cursor += 1 return False def run(self): halt = False while not halt: halt = self.step() return self def print(self): for index, stack in enumerate(self.stacks): if len(stack) != 0: print(str(index) + ':', " ".join([str(element) for element in stack])) def main(name, arguments): code = "" if len(arguments) == 0: print("usage:", name, "") return for argument in arguments: if argument == '-': code += input() continue with open(argument) as file: code += file.read() Machine(code).run().print() if __name__ == "__main__": main(sys.argv[0], sys.argv[1:])