#!/usr/bin/env python3 import sys from enum import Enum class State(Enum): # States INSERT = 0 MATCH = 1 REMOVE = 2 # Substates FETCH = 3 LABEL = 4 VARIABLE = 5 def number(instruction): try: return int(instruction, 16) except: return None def seek(cursor, code, instruction): while cursor < len(code) and code[cursor] != instruction: cursor += 1 return cursor def step(machine, code): state, substate, cursor, label, stacks, offsets, variables = machine if cursor >= len(code): return (False, machine) instruction = code[cursor] if instruction == '+': state = State.INSERT substate = State.FETCH cursor += 1 elif instruction == '?': state = State.MATCH substate = State.FETCH cursor += 1 elif instruction == '-': state = State.REMOVE substate = State.FETCH cursor += 1 elif instruction == '.': state = State.INSERT substate = State.FETCH variables.clear() for offset in range(0xF): offsets[offset] = 0 cursor = seek(0, code, '?') elif instruction == '$': if substate is State.LABEL: substate = State.VARIABLE cursor += 1 else: # Gremlins live here. data = number(instruction) if data is not None: if substate is State.FETCH: if state is State.REMOVE: stacks[data].pop() else: label = data substate = State.LABEL elif substate is State.LABEL: if state is State.INSERT: stacks[label].append(data) elif state is State.MATCH: if len(stacks[label]) == 0 or offsets[label] >= len(stacks[label]) or stacks[label][-1 - offsets[label]] != data: cursor = seek(cursor, code, '.') variables.clear() for offset in range(0xF): offsets[offset] = 0 else: offsets[label] += 1 substate = State.FETCH elif substate is State.VARIABLE: if state is State.INSERT: if data in variables: stacks[label].append(variables[data]) else: stacks[label].append(0) elif state is State.MATCH: if len(stacks[label]) == 0 or offsets[label] > len(stacks[label]): cursor = seek(cursor, code, '.') variables.clear() for offset in range(0xF): offsets[offset] = 0 else: if data in variables: if stacks[label][-1 - offsets[label]] != variables[data]: cursor = seek(cursor, code, '.') variables.clear() for offset in range(0xF): offsets[offset] = 0 else: offsets[label] += 1 else: variables[data] = stacks[label][-1 - offsets[label]] offsets[label] += 1 substate = State.FETCH cursor += 1 return (cursor >= len(code), (state, substate, cursor, label, stacks, offsets, variables)) def run(code, machine=None): if not machine: stacks = [[] for _ in range(0x10)] offsets = [0] * 0x10 machine = (State.INSERT, State.FETCH, 0, 0, stacks, offsets, {}) halt = False while not halt: halt, machine = step(machine, code) return machine def pretty(machine): state, substate, cursor, label, stacks, offsets, variables = machine for index, stack in enumerate(stacks): if len(stack) == 0: continue print(str(index) + ':', " ".join(reversed([str(element) for element in stack]))) def main(name, arguments): rules = "" if len(arguments) == 0: print("usage:", name, + "...") return for argument in arguments: with open(argument) as file: rules += file.read() pretty(run(rules)) if __name__ == "__main__": main(sys.argv[0], sys.argv[1:])