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
Coding/Python 삽질기2023. 3. 17. 01:08

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

4.  ChatGPT님이 주신 코드를 디버깅 해서 돌아가게 만듭니다.

class StackMachine:
    def __init__(self):
        self.stack = []
        self.program = []
        self.labels = {}
        self.ip = 0
        self.ax = 0
        self.bx = 0
        self.cx = 0
        self.dx = 0

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

    def run(self):
        count = 20000
        while self.ip < len(self.program) and count > 0:
            count -= 1
            cmd = self.program[self.ip].split()
            op = cmd[0].upper()

            if op == 'PUSH':
                self.stack.append(int(cmd[1]))
            elif op == 'POP':
                self.stack.pop()
            elif op == 'ADD':
                self.stack.append(self.stack.pop() + self.stack.pop())
            elif op == 'SUB':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append(a - b)
            elif op == 'MUL':
                self.stack.append(self.stack.pop() * self.stack.pop())
            elif op == 'DIV':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append(a // b)
            elif op == 'MOD':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append(a % b)
            elif op == 'DUP':
                self.stack.append(self.stack[-1])
            elif op == 'SWAP':
                self.stack[-1], self.stack[-2] = self.stack[-2], self.stack[-1]
            elif op == 'JMP':
                self.ip = self.labels[cmd[1]]
                continue
            elif op == 'JG':
                if self.stack.pop() == 1:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'JZ':
                if self.stack.pop() == 0:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'JNZ':
                if self.stack.pop() != 0:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'CMP':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append((a > b) - (a < b))
            elif op == 'NOP':
                pass
            elif op == 'HALT':
                break

            self.ip += 1

아래와 같이 올바른 결과가 나오는 것을 확인 합니다.

C:\Users\chobo\AppData\Local\Programs\Python\Python38\python.exe C:\github\simple_vm\simple_vm\main.py 
Result: 3628800

5. 코드를 약간 리팩토링 합니다.

6.  수정한 코드를 ChatGPT님에게 드리고 주석을 영어로 바꿔달라고 합니다. 

주석은 Step1의 명령어 정리하기로 생성된 걸 그대로 붙였습니다.

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
    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)
    CALL <label>: Save the current position and move program execution to the given label.
    RET: Return to the previously saved position and continue program execution.
    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.register = {'AX': 0, 'BX': 0, 'CX': 0, 'DX': 0}
        self.ip = 0

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

    def run(self):
        count = 20000
        while self.ip < len(self.program) and count > 0:
            count -= 1
            cmd = self.program[self.ip].split()
            op = cmd[0].upper()

            if op == 'PUSH':
                if type(cmd[1]) == int:
                    self.stack.append(int(cmd[1]))
                else:
                    self.stack.append(int(self.register[cmd[1]]))

            elif op == 'POP':
                self.stack.pop()
            elif op == 'MOV':
                if cmd[1] == 'AX':
                    self.ax = self.stack[-1]
                elif cmd[1] == 'BX':
                    self.bx = self.stack[-1]
                elif cmd[1] == 'CX':
                    self.cx = self.stack[-1]
                elif cmd[1] == 'DX':
                    self.dx = self.stack[-1]
            elif op == 'ADD':
                self.stack.append(self.stack.pop() + self.stack.pop())
            elif op == 'SUB':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append(a - b)
            elif op == 'MUL':
                self.stack.append(self.stack.pop() * self.stack.pop())
            elif op == 'DIV':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append(a // b)
            elif op == 'MOD':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append(a % b)
            elif op == 'DUP':
                self.stack.append(self.stack[-1])
            elif op == 'SWAP':
                self.stack[-1], self.stack[-2] = self.stack[-2], self.stack[-1]
            elif op == 'JMP':
                self.ip = self.labels[cmd[1]]
                continue
            elif op == 'JG':
                if self.stack.pop() == 1:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'JL':
                if self.stack.pop() == -1:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'JZ':
                if self.stack.pop() == 0:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'JNZ':
                if self.stack.pop() != 0:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'CMP':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append((a > b) - (a < b))
            elif op == 'NOP':
                pass
            elif op == 'HALT':
                break

            self.ip += 1
            # print(f"{self.stack} / {self.ax}")

 

7. 주석과 코드가 안 맞습니다. 주석을 코드 베이스로 업데이트 해달라고 요청합니다.

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.register = {'AX': 0, 'BX': 0, 'CX': 0, 'DX': 0}
        self.ip = 0

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

    def run(self):
        count = 20000
        while self.ip < len(self.program) and count > 0:
            count -= 1
            cmd = self.program[self.ip].split()
            op = cmd[0].upper()

            if op == 'PUSH':
                if 'X' in cmd[1]:
                    self.stack.append(int(self.register[cmd[1]]))
                else:
                    self.stack.append(int(cmd[1]))
            elif op == 'POP':
                self.stack.pop()
            elif op == 'MOV':
                self.register[cmd[1]] = self.stack[-1]
            elif op == 'ADD':
                self.stack.append(self.stack.pop() + self.stack.pop())
            elif op == 'SUB':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append(a - b)
            elif op == 'MUL':
                self.stack.append(self.stack.pop() * self.stack.pop())
            elif op == 'DIV':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append(a // b)
            elif op == 'MOD':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append(a % b)
            elif op == 'DUP':
                self.stack.append(self.stack[-1])
            elif op == 'SWAP':
                self.stack[-1], self.stack[-2] = self.stack[-2], self.stack[-1]
            elif op == 'JMP':
                self.ip = self.labels[cmd[1]]
                continue
            elif op == 'JG':
                if self.stack.pop() == 1:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'JL':
                if self.stack.pop() == -1:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'JZ':
                if self.stack.pop() == 0:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'JNZ':
                if self.stack.pop() != 0:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'CMP':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append((a > b) - (a < b))
            elif op == 'NOP':
                pass
            elif op == 'HALT':
                break

            self.ip += 1
            # print(f"{self.stack} / {self.ax}")

 

3부에서 계속 됩니다.

Posted by chobocho
Coding/Python 삽질기2023. 3. 17. 00:51

ChatGPT의 도움을 받아서,  Python으로  간단한 Stack 기반의  virtual machine을 만들어 보기로 했다.

1. 명령어 정의 하기

Q: stack 기반 simple vm 용 어셈블리 명령어 리스트를 만들어줘

A:
아래는 스택 기반의 간단한 가상 머신 (VM)을 위한 어셈블리 명령어 리스트입니다. 
이러한 기본 명령어를 통해 작은 프로그램을 구현하고 실행할 수 있습니다.

PUSH <value>: 스택에 값을 푸시합니다. 예: PUSH 10
POP: 스택의 맨 위 값을 제거하고 반환합니다.
ADD: 스택의 맨 위 두 개의 값을 제거하고 합계를 계산한 후, 스택에 결과를 푸시합니다.
SUB: 스택의 맨 위 두 개의 값을 제거하고 차이를 계산한 후, 스택에 결과를 푸시합니다.
MUL: 스택의 맨 위 두 개의 값을 제거하고 곱을 계산한 후, 스택에 결과를 푸시합니다.
DIV: 스택의 맨 위 두 개의 값을 제거하고 나눗셈을 계산한 후, 스택에 결과를 푸시합니다. 
(0으로 나누는 경우에 대한 오류 처리를 고려하세요.)
MOD: 스택의 맨 위 두 개의 값을 제거하고 나머지를 계산한 후, 스택에 결과를 푸시합니다.
DUP: 스택의 맨 위 값을 복사하여 스택에 푸시합니다.
SWAP: 스택의 맨 위 두 개의 값을 교환합니다.
JMP <label>: 주어진 레이블로 프로그램 실행을 이동합니다. 예: JMP LOOP
JZ <label>: 스택의 맨 위 값이 0이면 주어진 레이블로 프로그램 실행을 이동합니다.
JNZ <label>: 스택의 맨 위 값이 0이 아니면 주어진 레이블로 프로그램 실행을 이동합니다.
CMP: 스택의 맨 위 두 개의 값을 비교하고 결과를 스택에 푸시합니다. 
(-1: 첫 번째 값이 작음, 0: 두 값이 같음, 1: 첫 번째 값이 큼)
CALL <label>: 현재 위치를 저장하고 주어진 레이블로 프로그램 실행을 이동합니다.
RET: 이전에 저장된 위치로 돌아가 프로그램 실행을 계속합니다.
NOP: 아무 작업도 수행하지 않습니다. 이 명령은 프로그램에서 무시됩니다.
HALT: 프로그램 실행을 중지합니다.
이 명령어들은 가장 기본적인 스택 기반 VM을 위한 것이며, 
필요에 따라 추가 명령어를 구현하여 더 복잡한 기능을 수행할 수 있습니다.

 

2. 명령어를 실행 할 수 있는 VM 만들기

위 명령어로 1부터 10까지 곱하는 어셈블리 프로그램을 짜줘

1부터 10까지 곱하는 스택 기반 가상 머신의 어셈블리 프로그램을 작성해 보겠습니다. 
이 프로그램은 10! (10 팩토리얼)을 계산하는 것과 동일합니다. 결과는 스택의 맨 위에 있게 됩니다.

vbnet
Copy code
; 팩토리얼 계산 시작
  PUSH 1         ; 스택에 초기 결과 (1)을 푸시
  PUSH 1         ; 스택에 초기 반복 횟수 (1)을 푸시

; 루프 시작
LOOP:
  DUP           ; 반복 횟수를 복사
  PUSH 10       ; 10을 스택에 푸시
  CMP           ; 반복 횟수와 10을 비교
  JG END_LOOP   ; 반복 횟수가 10보다 크면 루프 종료
  DUP           ; 반복 횟수를 복사
  MUL           ; 현재 결과와 반복 횟수를 곱하기
  PUSH 1        ; 1을 스택에 푸시
  ADD           ; 반복 횟수 증가
  JMP LOOP      ; 루프로 돌아가기

; 루프 종료
END_LOOP:
  NOP           ; 여기서 프로그램이 끝납니다. 필요한 경우 HALT 명령어를 사용할 수 있습니다.
이 프로그램을 실행하면 스택의 맨 위에 10! (3,628,800)이라는 결과가 남게 됩니다.

이렇게 버그가 포함된 코드를 짜주셨다. 이걸 실행 하면, 26이 나온다.

왜냐면 아래 코드가 잘못되었기 때문이다. 

  DUP           ; 반복 횟수를 복사
  MUL           ; 현재 결과와 반복 횟수를 곱하기

야간의 삽질 끝에 고친 실제 동작하는 코드는 아래와 같다. (ChatGPT 의 코드를 디버깅 하고 있었다;;;)

; 팩토리얼 계산 시작
  PUSH 1         ; 스택에 초기 결과 (1)을 푸시
  PUSH 1         ; 스택에 초기 반복 횟수 (1)을 푸시

; 루프 시작
LOOP:
  DUP           ; 반복 횟수를 복사
  PUSH 10       ; 10을 스택에 푸시
  CMP           ; 반복 횟수와 10을 비교
  JG END_LOOP   ; 반복 횟수가 10보다 크면 루프 종료
  MOV AX        ; 반복 횟수를 AX 레지스터에 복사
  MUL           ; 현재 결과와 반복 횟수를 곱하기
  PUSH AX       ; AX 레지스터의 값을 스택에 푸시
  PUSH 1        ; 1을 스택에 푸시
  ADD           ; 반복 횟수 증가
  JMP LOOP      ; 루프로 돌아가기

; 루프 종료
END_LOOP:
  NOP           ; 여기서 프로그램이 끝납니다. 필요한 경우 HALT 명령어를 사용할 수 있습니다.
이 프로그램을 실행하면 스택의 맨 위에 10! (3,628,800)이라는 결과가 남게 됩니다.

 

3. 위  코드를 실행 할 수 있는 VM 코드 생성

Q: 위 스택머신을 이용해서 파이썬으로 gwbasic 인터프리터 만들어줘

A:
GW-BASIC 인터프리터를 파이썬으로 만드는 것은 복잡한 작업입니다. 
그러나 여기 간단한 스택 기반 가상 머신 인터프리터를 제공합니다. 
이 인터프리터는 위에서 제공한 어셈블리 명령어를 사용할 수 있습니다.

python
Copy code
class StackMachine:
    def __init__(self):
        self.stack = []
        self.program = []
        self.labels = {}
        self.ip = 0

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

    def run(self):
        while self.ip < len(self.program):
            cmd = self.program[self.ip].split()
            op = cmd[0].upper()

            if op == 'PUSH':
                self.stack.append(int(cmd[1]))
            elif op == 'POP':
                self.stack.pop()
            elif op == 'ADD':
                self.stack.append(self.stack.pop() + self.stack.pop())
            elif op == 'SUB':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append(a - b)
            elif op == 'MUL':
                self.stack.append(self.stack.pop() * self.stack.pop())
            elif op == 'DIV':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append(a // b)
            elif op == 'MOD':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append(a % b)
            elif op == 'DUP':
                self.stack.append(self.stack[-1])
            elif op == 'SWAP':
                self.stack[-1], self.stack[-2] = self.stack[-2], self.stack[-1]
            elif op == 'JMP':
                self.ip = self.labels[cmd[1]]
                continue
            elif op == 'JZ':
                if self.stack.pop() == 0:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'JNZ':
                if self.stack.pop() != 0:
                    self.ip = self.labels[cmd[1]]
                    continue
            elif op == 'CMP':
                b, a = self.stack.pop(), self.stack.pop()
                self.stack.append((a > b) - (a < b))
            elif op == 'NOP':
                pass
            elif op == 'HALT':
                break

            self.ip += 1

# 예제 프로그램
program = [
    'PUSH 1',
    'PUSH 1',
    'LOOP:',
    'DUP',
    'PUSH 10',
    'CMP',
    'JG END_LOOP',
    'DUP',
    'MUL',
    'PUSH 1',
    'ADD',
    'JMP LOOP',
    'END_LOOP:',
    'NOP',
]

# 스택 머신 인스턴스 생성
stack_machine = StackMachine()

# 프로그램 로드 및 실행
stack_machine.load_program(program)
stack_machine.run()

# 결과 출력
print("계산 결과:", stack_machine.stack[-1])
이 코드는 간단한 스택 기반 가상 머신 인터프리터를 구현합니다. 
이것은 GW-BASIC 인터프리터가 아니지만, 이 인터프리터를 확장하여 
GW-BASIC 인터프리터를 구현하는 데 사용할 수 있습니다. 
GW-BASIC 인터프리터를 구현하려면 다음 단계를 수행하세요:

GW-BASIC 문법 정의: GW-BASIC의 문법을 정의하고 토큰화합니다. 
키워드, 상수, 변수, 연산자, 함수 등을 인식하도록 토크나이저를 작성해야 합니다.

파서 작성: 토크나이저에서 생성된 토큰을 사용하여 문법 트리를 작성하는 파서를 구현합니다. 
이 파서는 GW-BASIC 프로그램을 읽고 실행 가능한 중간 표현으로 변환합니다.

실행 엔진 확장: 현재 스택 기반 가상 머신을 확장하여 GW-BASIC의 
기능에 맞게 사용자 정의 변수, 프로시저, 함수, 조건문, 반복문 등을 처리할 수 있도록 해야 합니다.

입출력 및 에러 처리: GW-BASIC 인터프리터는 사용자 입력을 처리하고 출력을 표시해야 합니다. 
또한 실행 중 발생할 수 있는 에러를 적절하게 처리해야 합니다.

테스트: 다양한 GW-BASIC 프로그램을 사용하여 인터프리터를 테스트하고 버그를 수정합니다.

위 단계를 따르면 파이썬으로 GW-BASIC 인터프리터를 만드는 데 도움이 됩니다. 
그러나 이 작업은 상당한 시간과 노력이 필요합니다. 
이미 만들어진 BASIC 인터프리터가 여러 개 있으므로, 
이러한 프로젝트 중 하나를 참조하거나 사용하는 것도 좋은 방법입니다. 
이러한 프로젝트 중 하나는 pcbasic이며, 
다음 링크에서 확인할 수 있습니다: https://github.com/robhagemans/pcbasic

일단 위와 같이 대충 돌아가는 것 처럼 보이는 코드를 작성해 주신다. (물론 안돌아 간다)

그리고 ChatGPT 님 께서도, 오픈소스 가져다 쓰라고 추천 하신다.

2부에서 계속...

 

Posted by chobocho