| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- #!/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 {state:16s} pc={pc_val}')
- for idx, inst in enumerate(program):
- print(f'{idx:02d} {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()
|