from collections.abc import Callable from inspect import getmembers from typing import TypeAlias from traceback import print_exception from pprint import pprint from .util import hexdump Operation: TypeAlias = str | int Subroutine: TypeAlias = Callable OPCODE_HEADER = "__interpreter__opcode" def opcode(opcode: Operation): def _helper(func: Callable): setattr(func, OPCODE_HEADER, opcode) return func return _helper class Registers: def __init__(self) -> None: self.program_counter: int = 0 class Memory: def __init__(self, size: int) -> None: self.__memory_size: int = size self.__memory: bytearray = bytearray(self.__memory_size) def __getitem__(self, idx) -> int: return self.__memory[idx] def __setitem__(self, idx, value) -> None: self.__memory[idx] = value def dump(self): return self.__memory class Interpreter: def __init__(self, bits=8, memsize=None) -> None: self.__bits = bits self.__memory_max_size = 2**self.__bits self.__memory_size = min(memsize or 2**bits, self.__memory_max_size) self._memory: Memory = Memory(self.__memory_size) self._registers: Registers = Registers() self.__operations: dict[Operation, Subroutine] = dict() self.__init_opcodes() def __init_opcodes(self): for _, func in getmembers(self, lambda obj: hasattr(obj, OPCODE_HEADER)): opcode = getattr(func, OPCODE_HEADER) self.__operations[opcode] = func def run(self) -> None: try: # Keep running until you reach a null byte while (opcode := self._memory[self._registers.program_counter]) != 0: self.__operations[opcode]() self._registers.program_counter += 1 except Exception as e: self.dump() print_exception(e) def dump(self): hexdump( data=self._memory.dump(), program_counter=self._registers.program_counter, memory_pointer=self._registers.memory_pointer ) pprint(self.__operations) def load_program(self, program: bytearray) -> None: self._memory[: len(program)] = program[:]