实现
- 首先前面已经说过,语法分析根据每一行的第一个属性字,然后对接下来的进行预测,如果符合预测,就是正确的指令
- 那么首先语法分析
Func
- Func不能嵌套定义,首先判断isFuncActive,如果是,则报错,否则继续读取下一个属性字是否是标识符,如果是标识符,那么就在函数表中查找,是否重复定义过。如果没有继续再读取下一个属性字是否是“{”,如果是,就判断函数名字是否是_Main,如果是就将MainHeader标记_Main已经出现过并且记录其指令入口点。 然后就将该函数的名字和函数入口点加入函数表,并且标记当前isFuncActive,还要记录当前函数的索引。函数信息的搜集不是一次完成的。当遇到右“}”关闭括号时候,判断当前函数是否是活跃的,如果是,就将统计的当前LocalSize和ParamCount放置进当前函数表中
- 【当虚拟机真正运行的时候,他遇到一个函数,首先根据其localSize+ParamCount+1开拓堆栈空间,然后Push的时候将参数放置在空间的底部,然后放置返回地址,然后每遇到一个局部数据,就放置进堆栈中。所以对于局部数据来说,在运行的时候想支持前向引用,你可以先将函数遍历然后将局部数据的定义一次性放入堆栈中。这样前向引用的时候可以在堆栈中获取到这个数据。不过这样没有必要,因为会造成阅读困难】
Param
- 【作者的思路是,在第一次遍历的时候我们需要首先统该函数计所有局部数据的大小iLoalSize并赋予其堆栈索引因为局部数据是位于栈顶的,并且同时统计其ParamCount,但是没有办法赋予Param堆栈索引,因为此时已经到达函数尾部了。当第二遍遍历的时候,每遇到一个Param,就赋予其堆栈索引-(localSize+2+1+currParamCount)】
- 因为这个原因,Param不支持前向引用,因为到第二遍遍历的时候,我们才知道其堆栈索引,在第二遍同时也要进行指令解析。不过作者好像没有注意到这个bug
- 【为什么不在第一次遍历的时候,对于Param和localVarible同时进行堆栈索引统计呢,因为在调用函数的时候,参数是通过Push统一压进去的,因此Param是一个连续的空间】
- 因此对于Param的执行,就是判断函数是否活跃,如果活跃,就紧接着判断其下一个属性字是否是标识符。如果是就获取其堆栈索引,然后将其加入到符号表中
Var/Var[]
- 首先判断接下来的属性字否是标识符,如果是标识符,就紧接着进行预读取,判断下一个是否是“【”,因为有可能是数组,如果是数组,就判断接下来是否是数字,如果是数字就将其保存到iSize,再判断接下来是否是“】”,
- 然后根据其是局部还是全局,获得其堆栈索引,全局堆栈索引为正,局部堆栈索引为负【局部堆栈索引等于—(当前局部数据大小+2)】
- 无论是全局变量还是局部变量,最后都要放进符号表里面。【通过StackIndex是否大于0来判断是局部还是全局】
- 其实应该先判断GetSymbol,如果可以获取得到,那么就直接报错。不用继续下去了
if(CurrNode.FuncIndex == FuncIndex | | CurrNode.StackIndex >= 0)
return CurrNode;
SetStackSize
- 如果是这个属性字,首先判断以前是否出现过,因为他只能定义一次,然后判断是否函数活跃的,因为声明堆栈大小只能在全局空间中。最后再获取下一个属性字,是否是一个整型变量,如果是,是否大于0,否则继续报错。如果将其转换为整数,保存到MainHeader结构体中,并且标识已经声明过堆栈大小。这个是在第一次遍历的时候就可以处理的指示符。
行标签
- 判断当前函数是否活跃,因为行标签只可以定义在函数当中
- 判断接下来的属性字是否是COMMA即冒号,如果是冒号,说明这就是一个行标签
- 然后将当前指令大小——即下一条指令的索引和该标签加入到标签表当中
指令解析
- 对于指令解析,首先根据指令字符串查找LookUpTable查找到指令,然后获取其iCount和iOpType
- 并且在指令流数组中当前索引处存储其iCount,并且给OpList分配空间
- 根据其iCount进行循环,每循环一次就读取其下一个属性字并且获取其操作数类型,然后将操作数类型和iOpType列表中对应操作数的类型掩码取与,如果不为零,则表明该操作数类型符合语法,并将其保存到OpList当中
- Op有个字段是iType,然后是value
- 对于指令来说,其操作数类型有以下几种
- 整型字面量
- 字符串型字面量
- 绝对堆栈索引
- 相对堆栈索引 【需要先去堆栈获取变量大小然后加上函数基址】
- 标签
- 函数
- 主应用程序API调用
- 一个寄存器 _RetVal