|
@@ -0,0 +1,120 @@
|
|
|
|
|
+#!/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()
|