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