如何自己写一个基于堆栈,虚拟机的语言

基于堆栈的语言

想必大家都已经了解GOF的解释器模式,他的优点很明显-安全,因为语法行为是我们自己定义的,不直接接触底层,但缺点也很明显——效率低,内存开销大。

再来看下我们的C++,直接编译成机器码,机器码是一组密集的,线性的,底层的指令,效率飞快。但又有谁愿意直接编辑机器码呢。

那有没有兼得熊掌和鱼的方法呢,有——定义自己的虚拟机器码,然后再在需要的地方写一个小的模拟器。

我们将这个模拟器叫做虚拟机(简称"VM"),虚拟机器码叫字节码

下面我们从简单的例子起步

游戏中我们需要创建英雄,设置英雄的血量,简单起见,可以像下面这样

void creatHero(int health = 10);				//创建英雄
void setHealth(int index,int health);			//改变指定英雄生命值

当然还可以是底层API的调用,比如

void playSound(int soundID);		//播放音乐
void printDebug();						//打印状态

来让我们看下我们是如何一步步将API分离,让我们的字节码解析执行的

首先是定义字节码,我们使用枚举

enum Instruction
{
	INST_SET_HEALTH = 0x00,
	INST_NEW_HERO = 0x01,
	INST_PLAY_SOUND = 0x02,
	INST_PRINT_DEBUG = 0x03
};

每一个字节码对应一个行为,我们使用switch来完成

switch(instruction)
{
	case INST_SET_HEALTH:
		setHealth(0,10);
		break;
	case INST_NEW_HERO:
		creatHero();
		break;
	case INST_PLAY_SOUND:
		playSound(DROP_SOUND)
		break;
	case INST_PRINT_DEBUG:
		printDebug();
		break;
}

于是我们完成我们的第一个虚拟机

class VM
{
public:
	void interpret(string bytecode)
	{
		int instruction;
		for(int i = 0;i < bytecode.size();i++)
		{
			instruction = (int)(bytecode[i] - '0')
			switch(instruction)
			{
				case INST_SET_HEALTH:
					setHealth(0,10);
					break;
				case INST_NEW_HERO:
					creatHero();
					break;
				case INST_PLAY_SOUND:
					playSound(DROP_SOUND)
					break;
				case INST_PRINT_DEBUG:
					printDebug();
					break;
			}
		}
	}		
}

但这还不够灵活,我们不能修改指定英雄血量。对,我们需要传入参数
于是我们引入堆栈

calss VM
{
public:
	VM()
	:stackSize_(0)
	{}
private:
	static const int MAX_STACK = 128;
	int stackSize_;
	int stack_[MAX_STACK];
}

然后我们定义加入(push)和取出(pop)功能

class VM
{
public:
	.......//something else
	void push(int value){
		stack_[stackSize++] = value; 
	}
	int pop(){
		return stack[--stackSize];
	}
}

现在我们可以像这样传入参数

switch(instruction)
{
	case INST_SET_HEALTH:
		{
			int health = pop();
			int index = pop();
			setHealth(index,health);	
		}
		break;
	//do something
}

然后我们需要一个机器码指定push指令,于是我们定义

enum Instruction
{
	//else
	INST_LITERAL = 0x04	
};

然后加入行为

switch(instruction)
{
	case INST_LITERRAL:
		{
			int  _value = bytecode[i++];
			push(_value);
		}
		break;
}

举个例子,设置index为1的英雄生命值为10为以下字节码
INST_LITERAL 1 INST_LITERAL 10 INST_SET_HEALTH
分析:
INST_LITERAL 1 INST_LITERAL 10:将1压入堆栈,将10压入堆栈
INST_SET_HEALTH:出站10作为health,出站1为index,将index为1的英雄的生命值设置为10

我们还需要让它能够实现行为

比如我们需要将编号为1的生命值设置为编号为3的生命值和编号4的生命值的和,我们就得需要用到运算,我们需要获取指定英雄的血量
于是我们像这样给运算定义字节码


//else
int getHealth(int index);

enum Instruction
{
	INST_ADD = 0x05,
	INST_RIDE = 0x06,
	INST_GET_HEALTH = 0x07
};

然后像下面

switch(instruction)
{
	//else
	case INST_ADD:
		{
			int _lvalue = pop();
			int _rvalue = pop();
			push(_lvalue+_rvalue);
		}
		break;
	case INST_RIDE:
		{
			int _lvalue = pop();
			int _rvalue = pop();
			push(_lvalue+_rvalue);
		}
		break;
	case INST_GET_HEALTH:
		{
			int index = pop();
			push(getHealth(index));
		}
		break;
}

现在我们就可以这样实现我们的例子
(我们需要将编号为1的生命值设置为编号为3的生命值和编号4的生命值的和)
INST_LITERAL 1 INST_LITERAL 3 INST_GET_HEALTH INST_LITERAL 4 INST_GET_HEALTH INST_ADD INST_SET_HEALTH
分析:
INST_LITERAL 1:将1压入堆栈,作为index
NST_LITERAL 3 INST_GET_HEALTH:将3压入堆栈,最为index,出站3作为index,获取编号为3的血量并压入堆栈
INST_LITERAL 4 INST_GET_HEALTH:将4压入堆栈,最为index,出站4作为index,获取编号为4的血量并压入堆栈
INST_ADD:出站4的血量,出站3的血量,将他们相加并压入堆栈
INST_SET_HEALTH:出站血量和,作为参数health,出站1,作为index,设置1的血量为health

到目前为止,我们已经可以可以使用我们的语言完成一些工作了,但这还不够我们需要逻辑的支持,代码重用,条件语句详情请期待下期更新
我打算在最近更新渗透测试骚操作,(其实鸽了很久了)

参考:https://blog.csdn.net/u010223072/article/details/59483093

猜你喜欢

转载自blog.csdn.net/nick131410/article/details/86558232
今日推荐