Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

물에 사는 벌레

바이트 코드 패턴을 이용한 VM 본문

디자인패턴

바이트 코드 패턴을 이용한 VM

물벌레 2019. 8. 13. 13:28

디자인 패턴 카테고리를 만들긴 했는데 디자인 패턴은 설명하기가 좀 난감하네요.

본래 설명을 잘하지 못하는데 디자인 패턴은 스스로 구현하고, 변형, 응용하여 공부하는 것이라고 생각되어 딱히 할 말이 생각나지 않네요.

디자인 패턴은 그냥 메모장 비슷하게 작성할 것 같습니다.

 

가상 머신과 바이트로 이루어진 가상의 명령어를 만들어 적용시켜 보았습니다.

먼저 명령어를 정의합니다. 

4부터 시작하는 이유는 좀 더 크게 만드려고 했으나 귀찮아서 지워버렸기 때문입니다...

#pragma once

enum class VMInstruction
{
	VMadd = 0x4,
	VMminus = 0x5,
	VMmultiply = 0x6,
	VMdivide = 0x7,
	VMprint = 0x8,
	VMprint_enter = 0x9,
	VMscan_int = 0x10,
	VMscan_string = 0x11,
	VMappendString = 0x12,
	VMintToString = 0x13,
	VMstringToInt = 0x14,
};

enum class VMType : unsigned char
{
	VMint = 0x1,
	VMstring = 0x2,
};

명령은 사칙연산과 출력, 입력으로 아주 간단하게 구성했습니다.


다음으로 가상 머신 클래스를 정의합니다.

#pragma once
#include "Stack.h"
#include "VMInstruction.h"
#include <iostream>
using namespace std;

#define STR_MAX 255

class VM
{
public:
	typedef unsigned char byte;
private:
	Stack<byte> stack;
private:
	inline bool IsType(VMType type);
public:
	void Push(byte *instructions, int size);
	void Push(byte instruction);
	void Push(VMInstruction instruction);
	void PushInt(int _int);
	void PushString(const char* str);
private:
	int PopInt();
	char* PopString();
	void Process();
};

#define TypeNotMatchException						\
{									\
	cout << "VM: type not match exception" << endl;			\
	cout << "file: " << __FILE__ << endl;				\
	cout << "line: " << __LINE__ << endl;				\
	cout << "function: " << __func__ << endl;			\
	throw exception("VM: type not match");				\
}//

Push 함수로 명령 혹은 데이터를 스택에 저장합니다.

PushInt를 통하여 int 데이터 타입을 4개의 바이트로 분할하여 저장합니다.

PushString을 통하여 문자열 타입을 각 문자로 분할하여 저장합니다.

PopInt를 사용하여 스택에서 4개의 바이트를 꺼내 int 데이터 타입으로 변환 후 반환합니다.

PopString을 통하여 스택에서 문자열 길이만큼의 바이트를 꺼내와 문자열로 변환 후 반환합니다.

Process를 사용하여 명령을 처리합니다.


#include "VM.h"

inline bool VM::IsType(VMType type)
{
	return (VMType)stack.Pop() == type;
}

void VM::Push(byte *instructions, int size)
{
	for (int i = 0; i < size; i++)
		stack.Push(instructions[i]);
}

void VM::Push(byte instruction)
{
	stack.Push(instruction);
}

void VM::Push(VMInstruction instruction)
{
	stack.Push((byte)instruction);
	Process();
}

void VM::PushInt(int _int)
{
	byte byte4 = _int;
	byte byte3 = _int >> 8;
	byte byte2 = _int >> 16;
	byte byte1 = _int >> 24;

	stack.Push(byte4);
	stack.Push(byte3);
	stack.Push(byte2);
	stack.Push(byte1);
	stack.Push((byte)VMType::VMint);
}

void VM::PushString(const char * str)
{
	int string_length = strlen(str);
	for (int i = string_length; i > 0; i--)
	{
		stack.Push(str[i - 1]);
	}
	stack.Push(string_length);
	stack.Push((byte)VMType::VMstring);
}

int VM::PopInt()
{
	int _int = 0x0;
	_int |= stack.Pop();
	_int <<= 8;
	_int |= stack.Pop();
	_int <<= 8;
	_int |= stack.Pop();
	_int <<= 8;
	_int |= stack.Pop();
	return _int;
}

char * VM::PopString()
{
	int string_length = stack.Pop();
	char *str = new char[string_length + 1];
	for (int i = 0; i < string_length; i++)str[i] = stack.Pop();
	str[string_length] = '\0';
	return str;
}

void VM::Process()
{
	VMInstruction instruction = (VMInstruction)stack.Pop();
	switch (instruction)
	{
	case VMInstruction::VMprint: {
		VMType vm_type = (VMType)stack.Pop();
		switch (vm_type)
		{
		case VMType::VMint:
			cout << PopInt();
			break;
		case VMType::VMstring:
			char *str = PopString();
			cout << str;
			delete[] str;
			break;
		}
	}break;
	case VMInstruction::VMprint_enter: {
		cout << endl;
	}break;
	case VMInstruction::VMscan_int: {
		int i; 
		cin >> i;
		while (cin.fail())
		{
			cin.clear();
			cin.ignore();
			cin >> i;
		}
		PushInt(i);
	}break;
	case VMInstruction::VMscan_string: {
		cout << "<< ";
		cin.clear(); cin.ignore();
		char str[STR_MAX]; cin.getline(str, STR_MAX);
		PushString(str);
	}break;
	case VMInstruction::VMadd: {
		if (!IsType(VMType::VMint))TypeNotMatchException;
		int last_int = PopInt();
		if (!IsType(VMType::VMint))TypeNotMatchException;
		int first_int = PopInt();
		int result = first_int + last_int;
		PushInt(result);
	}break;
	case VMInstruction::VMminus: {
		if (!IsType(VMType::VMint))TypeNotMatchException;
		int last_int = PopInt();
		if (!IsType(VMType::VMint))TypeNotMatchException;
		int first_int = PopInt();
		int result = first_int - last_int;
		PushInt(result);
	}break;
	case VMInstruction::VMmultiply: {
		if (!IsType(VMType::VMint))TypeNotMatchException;
		int last_int = PopInt();
		if (!IsType(VMType::VMint))TypeNotMatchException;
		int first_int = PopInt();
		int result = first_int * last_int;
		PushInt(result);
	}break;
	case VMInstruction::VMdivide: {
		if (!IsType(VMType::VMint))TypeNotMatchException;
		int last_int = PopInt();
		if (!IsType(VMType::VMint))TypeNotMatchException;
		int first_int = PopInt();
		int result = first_int / last_int;
		PushInt(result);
	}break;
	case VMInstruction::VMappendString: {
		if (!IsType(VMType::VMstring))TypeNotMatchException;
		char* str1 = PopString();
		if (!IsType(VMType::VMstring))TypeNotMatchException;
		char* str2 = PopString();

		int str1len = strlen(str1);
		int str2len = strlen(str2);
		int length = str1len + str2len;
		char* str3 = new char[length + 1];
		int cur = 0;

		for (int i = 0; i < str1len; i++)str3[cur++] = str1[i];
		for (int i = 0; i < str2len; i++)str3[cur++] = str2[i];
		str3[length] = '\0';
		delete[] str1; delete[] str2;

		PushString(str3);
	}break;
	case VMInstruction::VMintToString: {
		if (!IsType(VMType::VMint))TypeNotMatchException;
		char str[STR_MAX];
		_itoa_s(PopInt(), str, 10);
		PushString(str);
	}break;
	case VMInstruction::VMstringToInt: {
		if (!IsType(VMType::VMstring))TypeNotMatchException;
		int i = atoi(PopString());
		PushInt(i);
	}break;
	}
}

구현은 위와 같습니다.

문자열 하나 추가했다고 더러워진 것 보세요.


#include "VM.h"

int main()
{
	typedef VM::byte byte;
	VM vm;

	// ((1+2) * 3 + (2+3) * 4) * x

	vm.PushInt(1);
	vm.PushInt(2);
	vm.Push(VMInstruction::VMadd);
	vm.PushInt(3);
	vm.Push(VMInstruction::VMmultiply);
	// (1 + 2) * 3 = 9

	vm.PushInt(2);
	vm.PushInt(3);
	vm.Push(VMInstruction::VMadd);
	vm.PushInt(4);
	vm.Push(VMInstruction::VMmultiply);
	// (2 + 3) * 4 = 20

	vm.Push(VMInstruction::VMadd);
	// 9 + 20 = 29
	
	vm.PushString("[((1+2) * 3 + (2+3) * 4) * x]: x = ");
	vm.Push(VMInstruction::VMprint);
	vm.Push(VMInstruction::VMscan_int);
	vm.Push(VMInstruction::VMmultiply);
	// 29x

	vm.PushString("result: ");
	vm.Push(VMInstruction::VMprint);
	vm.Push(VMInstruction::VMprint);
	vm.Push(VMInstruction::VMprint_enter);
}

((1 + 2) * 3) + (2 + 3) * 4) * x 를 계산하는 과정입니다.

x는 사용자가 입력한 자연수입니다.

마지막 4줄에서는 계산 결과를 출력하고 있습니다.

한 번 작성해보면 스택의 입출력을 머리로 익히기 좋습니다.

 

x를 마지막까지 가져다가 식에 대입하여 다시 한번 출력하고 싶었는데 그러려면 추가적인 버퍼가 필요할 것 같습니다.

서브 버퍼의 자료구조를 스택, 큐, 리스트 중 무엇으로 해야할 지 애매하여 그냥 만들지 않았습니다.

Comments