Coding/Python 삽질기2023. 3. 17. 01:20

2부에 이어서, 3부를 시작 합니다.

 

8. 이제 생성된 코드를 리팩토링 해달라고 부탁 합니다.

from enum import Enum

class Command(Enum):
    PUSH = 'PUSH'
    POP = 'POP'
    ADD = 'ADD'
    SUB = 'SUB'
    MUL = 'MUL'
    DIV = 'DIV'
    MOD = 'MOD'
    DUP = 'DUP'
    SWAP = 'SWAP'
    JMP = 'JMP'
    JG = 'JG'
    JL = 'JL'
    JZ = 'JZ'
    JNZ = 'JNZ'
    CMP = 'CMP'
    MOV = 'MOV'
    NOP = 'NOP'
    HALT = 'HALT'


class StackMachine:
    """
    PUSH <value>: Push a value onto the stack. e.g., PUSH 10
    POP: Remove and return the top value from the stack.
    ADD: Remove the top two values from the stack, calculate their sum, and push the result onto the stack.
    SUB: Remove the top two values from the stack, calculate their difference, and push the result onto the stack.
    MUL: Remove the top two values from the stack, calculate their product, and push the result onto the stack.
    DIV: Remove the top two values from the stack, calculate their division, and push the result onto the stack.
    MOD: Remove the top two values from the stack, calculate their remainder, and push the result onto the stack.
    DUP: Copy the top value from the stack and push it onto the stack.
    SWAP: Swap the top two values on the stack.
    JMP <label>: Move program execution to the given label. e.g., JMP LOOP
    JG <label>: If the top value on the stack is greater than 0, move program execution to the given label.
    JL <label>: If the top value on the stack is less than 0, move program execution to the given label.
    JZ <label>: If the top value on the stack is 0, move program execution to the given label.
    JNZ <label>: If the top value on the stack is not 0, move program execution to the given label.
    CMP: Compare the top two values on the stack and push the result onto the stack.
    (-1: First value is smaller, 0: Both values are equal, 1: First value is larger)
    MOV <register>: Move the top value from the stack to the specified register (AX, BX, CX, or DX).
    NOP: Perform no operation. This command is ignored in the program.
    HALT: Stop program execution.
    """

    def __init__(self):
        self.stack = []
        self.program = []
        self.labels = {}
        self.registers = {'AX': 0, 'BX': 0, 'CX': 0, 'DX': 0}
        self.instruction_pointer = 0

    def load_program(self, program):
        self.program = program
        self.labels = {line[:-1]: i for i, line in enumerate(program) if line.endswith(':')}

    def parse_command(self, command_string):
        command, *args = command_string.split()
        if command not in Command.__members__:
            raise ValueError(f"Invalid command: {command}")
        return Command[command], args

    def execute_command(self, command, args):
        if command == Command.PUSH:
            value = int(args[0]) if 'X' not in args[0] else self.registers[args[0]]
            self.stack.append(value)
        elif command == Command.POP:
            self.stack.pop()
        elif command == Command.ADD:
            self.stack.append(self.stack.pop() + self.stack.pop())
        elif command == Command.SUB:
            b, a = self.stack.pop(), self.stack.pop()
            self.stack.append(a - b)
        elif command == Command.MUL:
            self.stack.append(self.stack.pop() * self.stack.pop())
        elif command == Command.DIV:
            b, a = self.stack.pop(), self.stack.pop()
            self.stack.append(a // b)
        elif command == Command.MOD:
            b, a = self.stack.pop(), self.stack.pop()
            self.stack.append(a % b)
        elif command == Command.DUP:
            self.stack.append(self.stack[-1])
        elif command == Command.SWAP:
            self.stack[-1], self.stack[-2] = self.stack[-2], self.stack[-1]
        elif command == Command.JMP:
            self.instruction_pointer = self.labels[args[0]]
            return
        elif command == Command.JG:
            if self.stack.pop() == 1:
                self.instruction_pointer = self.labels[args[0]]
                return
        elif command == Command.JL:
            if self.stack.pop() == -1:
                self.instruction_pointer = self.labels[args[0]]
                return
        elif command == Command.JZ:
            if self.stack.pop() == 0:
                self.instruction_pointer = self.labels[args[0]]
                return
        elif command == Command.JNZ:
            if self.stack.pop() != 0:
                self.instruction_pointer = self.labels[args[0]]
                return
        elif command == Command.CMP:
            b, a = self.stack.pop(), self.stack.pop()
            self.stack.append((a > b) - (a < b))
        elif command == Command.MOV:
            self.registers[args[0]] = self.stack[-1]
        elif command == Command.NOP:
            pass
        elif command == Command.HALT:
            return
        else:
            raise ValueError(f"Unknown command: {command}")

        self.instruction_pointer += 1

    def run(self):
        for _ in range(20000):
            if self.instruction_pointer >= len(self.program):
                break
            command_string = self.program[self.instruction_pointer]
            command, args = self.parse_command(command_string)
            self.execute_command(command, args)

생성된 코드를 돌려보면, 역시 안됩니다. 

디버깅 하면, Label 처리가 안되었을 알 수 있습니다. 

 

9. Label 처리를  위한 코드를 손으로 추가 하여 수정 합니다.

from enum import Enum


class Command(Enum):
    PUSH = 'PUSH'
    POP = 'POP'
    ADD = 'ADD'
    SUB = 'SUB'
    MUL = 'MUL'
    DIV = 'DIV'
    MOD = 'MOD'
    DUP = 'DUP'
    SWAP = 'SWAP'
    JMP = 'JMP'
    JG = 'JG'
    JL = 'JL'
    JZ = 'JZ'
    JNZ = 'JNZ'
    CMP = 'CMP'
    MOV = 'MOV'
    NOP = 'NOP'
    HALT = 'HALT'
    LABEL = 'LABEL'


class StackMachine:
    """
    PUSH <value>: Push a value onto the stack. e.g., PUSH 10
    POP: Remove and return the top value from the stack.
    ADD: Remove the top two values from the stack, calculate their sum, and push the result onto the stack.
    SUB: Remove the top two values from the stack, calculate their difference, and push the result onto the stack.
    MUL: Remove the top two values from the stack, calculate their product, and push the result onto the stack.
    DIV: Remove the top two values from the stack, calculate their division, and push the result onto the stack.
    MOD: Remove the top two values from the stack, calculate their remainder, and push the result onto the stack.
    DUP: Copy the top value from the stack and push it onto the stack.
    SWAP: Swap the top two values on the stack.
    JMP <label>: Move program execution to the given label. e.g., JMP LOOP
    JG <label>: If the top value on the stack is greater than 0, move program execution to the given label.
    JL <label>: If the top value on the stack is less than 0, move program execution to the given label.
    JZ <label>: If the top value on the stack is 0, move program execution to the given label.
    JNZ <label>: If the top value on the stack is not 0, move program execution to the given label.
    CMP: Compare the top two values on the stack and push the result onto the stack.
    (-1: First value is smaller, 0: Both values are equal, 1: First value is larger)
    MOV <register>: Move the top value from the stack to the specified register (AX, BX, CX, or DX).
    NOP: Perform no operation. This command is ignored in the program.
    HALT: Stop program execution.
    """

    def __init__(self):
        self.stack = []
        self.program = []
        self.labels = {}
        self.registers = {'AX': 0, 'BX': 0, 'CX': 0, 'DX': 0}
        self.instruction_pointer = 0

    def load_program(self, program):
        self.program = program
        self.labels = {line[:-1]: i for i, line in enumerate(program) if line.endswith(':')}

    def parse_command(self, command_string):
        command, *args = command_string.split()
        if command not in Command.__members__:
            if ':' in command:
                return Command['LABEL'], args
            else:
                raise ValueError(f"Unknown command: {command}")
        return Command[command], args

    def execute_command(self, command, args):
        if command == Command.PUSH:
            value = int(args[0]) if 'X' not in args[0] else self.registers[args[0]]
            self.stack.append(value)
        elif command == Command.POP:
            self.stack.pop()
        elif command == Command.ADD:
            self.stack.append(self.stack.pop() + self.stack.pop())
        elif command == Command.SUB:
            b, a = self.stack.pop(), self.stack.pop()
            self.stack.append(a - b)
        elif command == Command.MUL:
            self.stack.append(self.stack.pop() * self.stack.pop())
        elif command == Command.DIV:
            b, a = self.stack.pop(), self.stack.pop()
            self.stack.append(a // b)
        elif command == Command.MOD:
            b, a = self.stack.pop(), self.stack.pop()
            self.stack.append(a % b)
        elif command == Command.DUP:
            self.stack.append(self.stack[-1])
        elif command == Command.SWAP:
            self.stack[-1], self.stack[-2] = self.stack[-2], self.stack[-1]
        elif command == Command.JMP:
            self.instruction_pointer = self.labels[args[0]]
            return
        elif command == Command.JG:
            if self.stack.pop() == 1:
                self.instruction_pointer = self.labels[args[0]]
                return
        elif command == Command.JL:
            if self.stack.pop() == -1:
                self.instruction_pointer = self.labels[args[0]]
                return
        elif command == Command.JZ:
            if self.stack.pop() == 0:
                self.instruction_pointer = self.labels[args[0]]
                return
        elif command == Command.JNZ:
            if self.stack.pop() != 0:
                self.instruction_pointer = self.labels[args[0]]
                return
        elif command == Command.CMP:
            b, a = self.stack.pop(), self.stack.pop()
            self.stack.append((a > b) - (a < b))
        elif command == Command.MOV:
            self.registers[args[0]] = self.stack[-1]
        elif command == Command.NOP:
            pass
        elif command == Command.HALT:
            return
        elif command == Command.LABEL:
            pass
        else:
            raise ValueError(f"Unknown command: {command}")

        self.instruction_pointer += 1

    def run(self):
        for _ in range(20000):
            if self.instruction_pointer >= len(self.program):
                break
            command_string = self.program[self.instruction_pointer]
            command, args = self.parse_command(command_string)
            self.execute_command(command, args)

 

10.  execute_command() 함수 안의 수 많은 if - else 조합을 없애기 위해서 약간 코드를 고칩니다.

    def command_push(self, args):
        value = int(args[0]) if 'X' not in args[0] else self.registers[args[0]]
        self.stack.append(value)

    def command_pop(self, args):
        self.stack.pop()

위와 같이 함수를 2개 추가하고, 코드를 약간 고쳐 줍니다.

 

11. 수정 된 코드를 넣고, 리팩토링을 부탁합니다.

from enum import Enum


class Command(Enum):
    PUSH = 'PUSH'
    POP = 'POP'
    ADD = 'ADD'
    SUB = 'SUB'
    MUL = 'MUL'
    DIV = 'DIV'
    MOD = 'MOD'
    DUP = 'DUP'
    SWAP = 'SWAP'
    JMP = 'JMP'
    JG = 'JG'
    JL = 'JL'
    JZ = 'JZ'
    JNZ = 'JNZ'
    CMP = 'CMP'
    MOV = 'MOV'
    NOP = 'NOP'
    HALT = 'HALT'
    LABEL = 'LABEL'


class StackMachine:
    """
    PUSH <value>: Push a value onto the stack. e.g., PUSH 10
    POP: Remove and return the top value from the stack.
    ADD: Remove the top two values from the stack, calculate their sum, and push the result onto the stack.
    SUB: Remove the top two values from the stack, calculate their difference, and push the result onto the stack.
    MUL: Remove the top two values from the stack, calculate their product, and push the result onto the stack.
    DIV: Remove the top two values from the stack, calculate their division, and push the result onto the stack.
    MOD: Remove the top two values from the stack, calculate their remainder, and push the result onto the stack.
    DUP: Copy the top value from the stack and push it onto the stack.
    SWAP: Swap the top two values on the stack.
    JMP <label>: Move program execution to the given label. e.g., JMP LOOP
    JG <label>: If the top value on the stack is greater than 0, move program execution to the given label.
    JL <label>: If the top value on the stack is less than 0, move program execution to the given label.
    JZ <label>: If the top value on the stack is 0, move program execution to the given label.
    JNZ <label>: If the top value on the stack is not 0, move program execution to the given label.
    CMP: Compare the top two values on the stack and push the result onto the stack.
    (-1: First value is smaller, 0: Both values are equal, 1: First value is larger)
    MOV <register>: Move the top value from the stack to the specified register (AX, BX, CX, or DX).
    NOP: Perform no operation. This command is ignored in the program.
    HALT: Stop program execution.
    """

    def __init__(self):
        self.stack = []
        self.program = []
        self.labels = {}
        self.registers = {'AX': 0, 'BX': 0, 'CX': 0, 'DX': 0}
        self.command_list = {}
        self.instruction_pointer = 0
        self._init_command_list()

    def _init_command_list(self):
        self.command_list[Command.PUSH] = self.command_push
        self.command_list[Command.POP] = self.command_pop
        self.command_list[Command.ADD] = self.command_add
        self.command_list[Command.SUB] = self.command_sub
        self.command_list[Command.MUL] = self.command_mul
        self.command_list[Command.DIV] = self.command_div
        self.command_list[Command.MOD] = self.command_mod
        self.command_list[Command.DUP] = self.command_dup
        self.command_list[Command.SWAP] = self.command_swap
        self.command_list[Command.JMP] = self.command_jmp
        self.command_list[Command.JG] = self.command_jg
        self.command_list[Command.JL] = self.command_jl
        self.command_list[Command.JZ] = self.command_jz
        self.command_list[Command.JNZ] = self.command_jnz
        self.command_list[Command.CMP] = self.command_cmp
        self.command_list[Command.MOV] = self.command_mov
        self.command_list[Command.NOP] = self.command_nop
        self.command_list[Command.HALT] = self.command_halt
        self.command_list[Command.LABEL] = self.command_label

    def load_program(self, program):
        self.program = program
        self.labels = {line[:-1]: i for i, line in enumerate(program) if line.endswith(':')}

    def parse_command(self, command_string):
        command, *args = command_string.split()
        if command not in Command.__members__:
            if ':' in command:
                return Command['LABEL'], args
            else:
                raise ValueError(f"Unknown command: {command}")
        return Command[command], args

    def execute_command(self, command, args):
        if command in self.command_list:
            self.command_list[command](args)
        else:
            raise ValueError(f"Unknown command: {command}")

        self.instruction_pointer += 1

    def command_push(self, args):
        value = int(args[0]) if 'X' not in args[0] else self.registers[args[0]]
        self.stack.append(value)

    def command_pop(self, args):
        self.stack.pop()

    def command_add(self, args):
        self.stack.append(self.stack.pop() + self.stack.pop())

    def command_sub(self, args):
        b, a = self.stack.pop(), self.stack.pop()
        self.stack.append(a - b)

    def command_mul(self, args):
        self.stack.append(self.stack.pop() * self.stack.pop())

    def command_div(self, args):
        b, a = self.stack.pop(), self.stack.pop()
        self.stack.append(a // b)

    def command_mod(self, args):
        b, a = self.stack.pop(), self.stack.pop()
        self.stack.append(a % b)

    def command_dup(self, args):
        self.stack.append(self.stack[-1])

    def command_swap(self, args):
        self.stack[-1], self.stack[-2] = self.stack[-2], self.stack[-1]

    def command_jmp(self, args):
        self.instruction_pointer = self.labels[args[0]]

    def command_jg(self, args):
        if self.stack.pop() == 1:
            self.instruction_pointer = self.labels[args[0]]

    def command_jl(self, args):
        if self.stack.pop() == -1:
            self.instruction_pointer = self.labels[args[0]]

    def command_jz(self, args):
        if self.stack.pop() == 0:
            self.instruction_pointer = self.labels[args[0]]

    def command_jnz(self, args):
        if self.stack.pop() != 0:
            self.instruction_pointer = self.labels[args[0]]

    def command_cmp(self, args):
        b, a = self.stack.pop(), self.stack.pop()
        self.stack.append((a > b) - (a < b))

    def command_mov(self, args):
        self.registers[args[0]] = self.stack[-1]

    def command_nop(self, args):
        pass

    def command_halt(self, args):
        pass

    def command_label(self, args):
        pass

    def run(self):
        for _ in range(20000):
            if self.instruction_pointer >= len(self.program):
                break
            command_string = self.program[self.instruction_pointer]
            command, args = self.parse_command(command_string)
            self.execute_command(command, args)

위와 같이 아름답게 코드를 고쳐 주십니다.

그래서 나온 최종 버전은 아래에 있습니다.

https://github.com/chobocho/simple_vm

 

GitHub - chobocho/simple_vm: stack based simple stack machine

stack based simple stack machine. Contribute to chobocho/simple_vm development by creating an account on GitHub.

github.com

 

결론: 

1) 웬만하면 구글링 해서 open source 를 쓰자.

2) ChatGPT-4도 완전한 코드를 짜주지는 않는다 (디버깅은 필수)

Posted by chobocho