vm.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. #!/bin/bash env python3
  2. """
  3. Reads stdin in full then interprets the instructions on each line as follows:
  4. - `DEC $R $S`: if register `$R` is > 0, decrement it, otherwise jump to state `$S`
  5. - `INC $R`: increments register `$R`
  6. - `.$STATE`: labels the current position as `$STATE` allowing transition from `DEC`.
  7. Note that the entirety of the line $STATE`, i.e. the name of the state _includes_
  8. the leading `.`
  9. - `HALT`: halts the machine and exits
  10. - `%`: debug instructions, e.g. printing program state
  11. Any string is allowed for register names and state names as long as they don't contain
  12. a space.
  13. Comments are allowed via `#` where everything pass `#` and `#` itself is stripped out
  14. prior to execution.
  15. """
  16. from dataclasses import dataclass
  17. from collections import Counter
  18. import sys
  19. import time
  20. @dataclass
  21. class Instruction:
  22. OP_DEC = 0
  23. OP_INC = 1
  24. OP_HALT = 2
  25. OP_STATE = 3
  26. OP_DEBUG = 99
  27. op: int
  28. pc: int
  29. source_line: str = None
  30. reg_arg: str = None
  31. state_arg: str = None
  32. def main():
  33. program: list[Instruction] = []
  34. registers = Counter()
  35. # maps labels to PC counter values
  36. states: dict[str, int] = {}
  37. source_code = [l.strip() for l in sys.stdin.readlines()]
  38. pc = 0
  39. for line in source_code:
  40. line = line.split('#')[0]
  41. if len(line) == 0:
  42. continue
  43. tokens = line.split(' ')
  44. inst = None
  45. if tokens[0] == 'DEC':
  46. inst = Instruction(Instruction.OP_DEC, pc, line, reg_arg = tokens[1], state_arg=tokens[2])
  47. if tokens[0] == 'INC':
  48. inst = Instruction(Instruction.OP_INC, pc, line, reg_arg = tokens[1])
  49. if tokens[0] == 'HALT':
  50. inst = Instruction(Instruction.OP_HALT, pc, line)
  51. if tokens[0][0] == '.':
  52. inst = Instruction(Instruction.OP_STATE, pc, line)
  53. states[tokens[0]] = pc
  54. if tokens[0][0] == '%':
  55. inst = Instruction(Instruction.OP_DEBUG, pc, line)
  56. if inst is None:
  57. raise ValueError(f'Unknown instruction: {line}')
  58. program.append(inst)
  59. pc += 1
  60. print('Parsed program:')
  61. for state, pc_val in states.items():
  62. print(f'{state=} {pc_val=}')
  63. for idx, inst in enumerate(program):
  64. print(idx + 1, inst)
  65. print('Executing...')
  66. pc = 0
  67. halted = False
  68. while not halted and pc >= 0:
  69. inst = program[pc]
  70. if inst.op == Instruction.OP_DEC:
  71. if registers[inst.reg_arg] == 0:
  72. pc = states[inst.state_arg]
  73. else:
  74. registers[inst.reg_arg] -= 1
  75. pc += 1
  76. if inst.op == Instruction.OP_INC:
  77. registers[inst.reg_arg] += 1
  78. pc += 1
  79. if inst.op == Instruction.OP_HALT:
  80. halted = True
  81. pc -= 1
  82. if inst.op == Instruction.OP_STATE:
  83. pc += 1
  84. if inst.op == Instruction.OP_DEBUG:
  85. pc += 1
  86. tokens = inst.source_line[1:].split(' ')
  87. if tokens[0] == 'print':
  88. if tokens[1] == 'reg':
  89. for k in sorted(registers.keys()):
  90. print(f'{k} = {registers[k]}', end=' ')
  91. print()
  92. if tokens[0] == 'sleep':
  93. t = float(tokens[1])
  94. time.sleep(t)
  95. if __name__ == '__main__':
  96. main()