#!/bin/bash env python3 """ Reads stdin in full then interprets the instructions on each line as follows: - `DEC $R $S`: if register `$R` is > 0, decrement it, otherwise jump to state `$S` - `INC $R`: increments register `$R` - `.$STATE`: labels the current position as `$STATE` allowing transition from `DEC`. Note that the entirety of the line $STATE`, i.e. the name of the state _includes_ the leading `.` - `HALT`: halts the machine and exits - `%`: debug instructions, e.g. printing program state Any string is allowed for register names and state names as long as they don't contain a space. Comments are allowed via `#` where everything pass `#` and `#` itself is stripped out prior to execution. """ from dataclasses import dataclass from collections import Counter import sys import time @dataclass class Instruction: OP_DEC = 0 OP_INC = 1 OP_HALT = 2 OP_STATE = 3 OP_DEBUG = 99 op: int pc: int source_line: str = None reg_arg: str = None state_arg: str = None def main(): program: list[Instruction] = [] registers = Counter() # maps labels to PC counter values states: dict[str, int] = {} source_code = [l.strip() for l in sys.stdin.readlines()] pc = 0 for line in source_code: line = line.split('#')[0] if len(line) == 0: continue tokens = line.split(' ') inst = None if tokens[0] == 'DEC': inst = Instruction(Instruction.OP_DEC, pc, line, reg_arg = tokens[1], state_arg=tokens[2]) if tokens[0] == 'INC': inst = Instruction(Instruction.OP_INC, pc, line, reg_arg = tokens[1]) if tokens[0] == 'HALT': inst = Instruction(Instruction.OP_HALT, pc, line) if tokens[0][0] == '.': inst = Instruction(Instruction.OP_STATE, pc, line) states[tokens[0]] = pc if tokens[0][0] == '%': inst = Instruction(Instruction.OP_DEBUG, pc, line) if inst is None: raise ValueError(f'Unknown instruction: {line}') program.append(inst) pc += 1 print('Parsed program:') for state, pc_val in states.items(): print(f'{state=} {pc_val=}') for idx, inst in enumerate(program): print(idx + 1, inst) print('Executing...') pc = 0 halted = False while not halted and pc >= 0: inst = program[pc] if inst.op == Instruction.OP_DEC: if registers[inst.reg_arg] == 0: pc = states[inst.state_arg] else: registers[inst.reg_arg] -= 1 pc += 1 if inst.op == Instruction.OP_INC: registers[inst.reg_arg] += 1 pc += 1 if inst.op == Instruction.OP_HALT: halted = True pc -= 1 if inst.op == Instruction.OP_STATE: pc += 1 if inst.op == Instruction.OP_DEBUG: pc += 1 tokens = inst.source_line[1:].split(' ') if tokens[0] == 'print': if tokens[1] == 'reg': for k in sorted(registers.keys()): print(f'{k} = {registers[k]}', end=' ') print() if tokens[0] == 'sleep': t = float(tokens[1]) time.sleep(t) if __name__ == '__main__': main()