コード例 #1
0
ファイル: hackvm.py プロジェクト: Tenebrar/codebase
    def __init__(self, max_iterations=DEFAULT_MAX_ITERATIONS) -> None:
        """
        Initializes the vm
        :param max_iterations: The default maximum iterations is 10k, but some challenges set it to something else
        """
        self._memory = zeros(MEMORY_SIZE, dtype=int)
        self._call_stack = Stack()
        self._operand_stack = OperandStack()
        self._program_counter = 0

        self._iteration_counter = IterationCounter(max_iterations)

        # These are instance variables because they need to be accessible to the operations
        self._program = ''  # Will be set in the run method
        self._result: List[str] = []

        self._operations: Dict[str, Callable[[], Any]] = {
            ' ': lambda: None,  # NO_OP
            '\n': lambda: None,  # NO_OP
            'p': self._print_int,
            'P': self._print_char,
            '0': partial(self._operand_stack.push, 0),
            '1': partial(self._operand_stack.push, 1),
            '2': partial(self._operand_stack.push, 2),
            '3': partial(self._operand_stack.push, 3),
            '4': partial(self._operand_stack.push, 4),
            '5': partial(self._operand_stack.push, 5),
            '6': partial(self._operand_stack.push, 6),
            '7': partial(self._operand_stack.push, 7),
            '8': partial(self._operand_stack.push, 8),
            '9': partial(self._operand_stack.push, 9),
            '+': self._operand_stack.add_two_operands,
            '-': self._operand_stack.subtract_two_operands,
            '*': self._operand_stack.multiply_two_operands,
            '/': self._operand_stack.divide_two_operands,
            ':': self._operand_stack.cmp_two_operands,
            'g': self._jump,
            '?': self._jump_if_zero,
            'c': self._call,
            '$': self._return,
            '<': self._read_from_memory,
            '>': self._write_to_memory,
            '^': self._operand_stack.pick,
            'v': self._operand_stack.roll,
            'd': self._operand_stack.pop,
            '!': self._end,
        }
コード例 #2
0
def test_stack_push_and_pop(stack: OperandStack) -> None:
    assert to_list(stack) == []
    stack.push(4)
    assert to_list(stack) == [4]
    stack.push(5)
    assert to_list(stack) == [4, 5]
    stack.push(6)
    assert to_list(stack) == [4, 5, 6]

    assert stack.pop() == 6
    assert to_list(stack) == [4, 5]
    assert stack.pop() == 5
    assert to_list(stack) == [4]
    assert stack.pop() == 4
    assert to_list(stack) == []
コード例 #3
0
def test_add_two_operands(stack: OperandStack, values: List[int], expected: List[int]) -> None:
    make(stack, values)
    stack.add_two_operands()
    assert to_list(stack) == expected
コード例 #4
0
def test_roll_fail(stack: OperandStack, values: List[int]) -> None:
    make(stack, values)
    with raises(RuntimeError):
        stack.roll()
コード例 #5
0
def stack():
    return OperandStack()
コード例 #6
0
def test_roll(stack: OperandStack, values: List[int], expected: List[int]) -> None:
    make(stack, values)
    stack.roll()
    assert to_list(stack) == expected
コード例 #7
0
def test_cmp_two_operands_fail(stack: OperandStack, values: List[int]) -> None:
    make(stack, values)
    with raises(RuntimeError):
        stack.cmp_two_operands()
コード例 #8
0
def test_divide_two_operands_by_zero(stack: OperandStack) -> None:
    make(stack, [4, 0])
    with raises(ZeroDivisionError):
        stack.divide_two_operands()
コード例 #9
0
def make(stack: OperandStack, values: List[int]) -> None:
    assert len(stack) == 0
    for value in values:
        stack.push(value)
    assert to_list(stack) == values
コード例 #10
0
ファイル: hackvm.py プロジェクト: Tenebrar/codebase
class HackVm:
    """ VM that runs programs in the hackvm language """
    def __init__(self, max_iterations=DEFAULT_MAX_ITERATIONS) -> None:
        """
        Initializes the vm
        :param max_iterations: The default maximum iterations is 10k, but some challenges set it to something else
        """
        self._memory = zeros(MEMORY_SIZE, dtype=int)
        self._call_stack = Stack()
        self._operand_stack = OperandStack()
        self._program_counter = 0

        self._iteration_counter = IterationCounter(max_iterations)

        # These are instance variables because they need to be accessible to the operations
        self._program = ''  # Will be set in the run method
        self._result: List[str] = []

        self._operations: Dict[str, Callable[[], Any]] = {
            ' ': lambda: None,  # NO_OP
            '\n': lambda: None,  # NO_OP
            'p': self._print_int,
            'P': self._print_char,
            '0': partial(self._operand_stack.push, 0),
            '1': partial(self._operand_stack.push, 1),
            '2': partial(self._operand_stack.push, 2),
            '3': partial(self._operand_stack.push, 3),
            '4': partial(self._operand_stack.push, 4),
            '5': partial(self._operand_stack.push, 5),
            '6': partial(self._operand_stack.push, 6),
            '7': partial(self._operand_stack.push, 7),
            '8': partial(self._operand_stack.push, 8),
            '9': partial(self._operand_stack.push, 9),
            '+': self._operand_stack.add_two_operands,
            '-': self._operand_stack.subtract_two_operands,
            '*': self._operand_stack.multiply_two_operands,
            '/': self._operand_stack.divide_two_operands,
            ':': self._operand_stack.cmp_two_operands,
            'g': self._jump,
            '?': self._jump_if_zero,
            'c': self._call,
            '$': self._return,
            '<': self._read_from_memory,
            '>': self._write_to_memory,
            '^': self._operand_stack.pick,
            'v': self._operand_stack.roll,
            'd': self._operand_stack.pop,
            '!': self._end,
        }

    def run(self,
            program: str,
            initial_memory: Sequence[int] = (),
            verbose: bool = False) -> str:
        """
        Runs a hackvm program
        :param program: A hackvm program
        :param initial_memory: Values for the initial memory of the program (Needs only to indicate non-0 values)
        :param verbose: Whether to output each execute operation and resulting stack
        :return: The output of the program
        :raises RuntimeError: If the program counter goes out of bounds of the program, or the maximum iteration
        count is exceeded
        """
        self._program = program
        self._memory[:len(initial_memory)] = initial_memory
        logger.setLevel('DEBUG' if verbose else 'WARNING')

        while self._program_counter != len(self._program):
            op_code = self._program[self._program_counter]
            logger.debug(f'@{self._program_counter} {op_code}')
            self._program_counter += 1

            self._operations[op_code]()
            logger.debug(self._operand_stack)

            self._verify_validity()

        return ''.join(self._result)

    def _verify_validity(self):
        """ Verify the iteration count has not been exceeded and the program counter is in bounds """
        self._iteration_counter.increment()
        if not 0 <= self._program_counter <= len(self._program):
            raise RuntimeError(f'Out of code bounds, {self._program_counter}')

    def _print_char(self) -> None:
        value = self._operand_stack.pop()
        self._result.append(chr(value & 0x7F))

    def _print_int(self) -> None:
        value = self._operand_stack.pop()
        self._result.append(str(value))

    def _jump(self) -> None:
        self._program_counter += self._operand_stack.pop()

    def _jump_if_zero(self) -> None:
        offset = self._operand_stack.pop()
        if self._operand_stack.pop() == 0:
            self._program_counter += offset

    def _call(self) -> None:
        self._call_stack.push(self._program_counter)
        self._program_counter = self._operand_stack.pop()

    def _return(self) -> None:
        self._program_counter = self._call_stack.pop()

    def _read_from_memory(self) -> None:
        memory_address = self._validate_memory_address(
            self._operand_stack.pop())
        self._operand_stack.push(self._memory[memory_address])

    def _write_to_memory(self) -> None:
        memory_address = self._validate_memory_address(
            self._operand_stack.pop())
        self._memory[memory_address] = self._operand_stack.pop()

    def _validate_memory_address(self, memory_address: int) -> int:
        if memory_address < 0 or memory_address >= len(self._memory):
            raise RuntimeError(
                f'Invalid memory access, index {memory_address}')
        return memory_address

    def _end(self) -> None:
        self._program_counter = len(self._program)