前几节实现了流程控制结构,如if条件选择,for循环控制等,但是编程经常需要将常用的代码做成模块的形式,方便调用和维护,通常的一个做法就是使用函 数,将有用的代码放在一个函数定义里,需...
前几节实现了流程控制结构,如if条件选择,for循环控制等,但是编程经常需要将常用的代码做成模块的形式,方便调用和维护,通常的一个做法就是使用函 数,将有用的代码放在一个函数定义里,需要的时候直接调用该函数即可,所以这一节,我们来实现一下函数(在面向对象的编程语言里喜欢叫做方法)。
还是一样的开场白,先介绍一下本节v0.0.12版本的源代码的下载地址:http://pan.baidu.com/share/link?shareid=165884&uk=940392313 (此为百度云盘的共享链接地址),访问该地址可以看到三个文件:zengl_lang_v0.0.12_forXP.rar (XP系统下的vs2008解决方案和源代码), zengl_language_v0.0.12_forLinux.tar.gz (Linux系统下的源代码和makefile) ,v0.0.12-v0.0.11-diffs.txt (v0.0.12和v0.0.11的代码变化情况)。
SourceForge.net上的仓库地址为:https://sourceforge.net/projects/zengl/files/ 从里面可以看到各个版本的代码压缩包,比如本节的zengl_lang_v0.0.12_forXP.rar ,zengl_language_v0.0.12_forLinux.tar.gz,v0.0.12-v0.0.11-diffs.txt 。
先来看下本版本的描述 (在linux代码包里的usage.txt里有这段描述,在最近几个带有git版本的....diffs.txt和git log中也有这段描述)
v0.0.12版本,该版本实现了FUN函数结构,以及FUNCALL函数调用的实现。
和其他控制结构一样,FUN和FUNCALL的语法树都是在parser.c中生成,在express2函数里通过增加ID标识符紧跟左括号的处理实现FUNCALL的语法树,在statement中FUN语法树的生成和FOR,IF的大同小异。
main.c中添加了fun,endfun的保留字的token。
symbol.c中在原来全局变量Hash表的基础上,增加了FUN函数名的Hash表以及函数里面的局部local变量的hash表。这样在后面的汇编和链接中就可以在hash表中查询到对应函数的函数id,以及函数里的局部变量的索引值。
难点在于assemble.c生成汇编代码部分,通过增加ARG,LOC寄存器来访问栈空间里的参数和局部变量。在调用某个函数时,先将原函数的 ARG,LOC压入栈,紧接着将参数压入栈,并将参数位置先保存在ARGTMP中,最后要跳转时再赋给ARG,因为在生成参数时还可能要用到原来的ARG 参数。
在参数压入栈后,再将返回地址压入栈,最后设置ARG,LOC寄存器,接着跳转到相应的函数去。
在FUN函数里,开头需要一个JMP跳转到函数RET后的地址,这样在没有调用函数时就不会执行FUN里的代码,而是执行FUN后面的代码,当 FUNCALL调用此函数时,只要跳转到第一条JMP语句后即可执行该函数。在FUN的第一个JMP后紧跟着FUNARG指令,FUNARG指令后面接的 是该函数定义的参数个数,当调用函数所提供的实参少于函数定义的参数数目时,将自动压入0到栈中,使得参数一致。
在FUNARG前会调用Symbol.c中的ScanFunArg函数将扫描到的参数添加到参数Hash表中,参数和局部变量共用一个hash表。在 FUNARG指令后会调用Symbol.c中的ScanFunLocal将局部变量也加入到hash表中,并且对每个扫描到的局部变量都PUSH压入栈。
所以在栈空间中调用函数时,最开始部分是原来调用函数的ARG,LOC寄存器值,接着是ARG参数部分,再后面是返回地址部分,最后是LOC局部变量部 分,ARG,LOC寄存器分别指向参数和局部变量第一个值的位置处。如可以通过arg(0)来引用第一个参数,loc(1)来引用第二个局部变量等。
汇编代码生成后,最后就是run.c实现汇编代码的部分。在run.c中根据assemble.c中的代码相应的增加了ARG,LOC,ARGTMP寄存 器,并通过MEM_ARG_LOC_OP_GET及MEM_ARG_LOC_OP宏来操作全局变量以及函数里的参数和局部变量的内存部分。
最后将FUNARG,RESET,RET汇编指令加以实现,就完成了FUN函数的实现。
作者:zenglong
时间:2012年3月17日
官方网站:www.zengl.com
因为时间和篇幅关系,请结合源代码中的注释,加上git工具以及vs2008或者eclipse+CDT或者gcc,gdb等工具进行分析。
下面通过例子来说明fun函数定义和函数调用的实现。
本节test.zl测试脚本中的代码如下(下面的注释是为了在这里进行说明而添加的,在源文件中并没有):
fun test(a,b) //通过fun关键字定义一个名为test的函数,该函数接受两个参数:a和b ,这两个参数目前可以是数字或者字符串等。
print 'your test function arg a is ' + a; //打印第一个参数
print 'your test function arg b is ' + b; //打印第二个参数
endfun //必须以endfun结束函数的定义
fun test2(a,b) //通过fun关键字定义一个名为test2的函数,该函数接受两个参数:a和b ,这两个参数目前可以是数字或者字符串等。
print a; //直接打印出第一个参数
print b; //直接打印出第二个参数
endfun //必须以endfun结束函数的定义
test('hello world','my name is zengl'); //调用test函数进行打印
test('welcome to zengl.com','my name is zengl.com');
test2('走自己的路,让别人去说吧',' ─────阿利盖利·但丁'); //调用test2函数进行参数的直接打印输出
test.zl脚本经过main.c的词法扫描,parser.c的语法解析后,生成的AST(抽象语法树)如下:
以上是该例子生成的test.zlc汇编代码文件部分。从这里可以看出函数和函数调用的实现原理(一般原理或者真理性质的东东都是在汇编或者脚本语言生成的中间代码中可以看到)。
对于函数,比较难理解的地方就是函数调用时的虚拟栈结构(虚拟栈<简称栈>和虚拟内存都是run.c实现的虚拟机在运行时创建的动态数组空间。) ,一个函数调用的栈存储情况如下:
上图为函数调用时的栈空间的情况,在函数调用跳转到函数内部后,当前的ARG寄存器就指向图中的压入的参数区域的顶部,即指向第一个参数,LOC寄存器就指向图中的函数局部变量的顶部,即指向第一个局部变量。
通过上面的介绍,应该对zengl脚本语言的函数原理比较清楚了,其实汇编语言里在函数调用时的栈结构也差不了多少,都是将参数,局部变量和返回地址以及原来调用环境下的寄存器值都保存在栈中,在函数调用返回时,再将这些值进行恢复。
其他更多的细节部分,请结合源代码,git log -p 和前面提到过的开发调试工具进行分析。
最后是些老生常谈的话题:
windowsXP压缩包中的代码包括test.zl测试脚本都是采用GBK的编码,Linux压缩包中的代码包括测试文件以及git里的信息都是UTF8的编码,所以如果哪些地方出现了乱码,请自行调整。
对于windows用户,请确保在项目属性的配置里,命令行参数配置的是test.zl(对于zengl_lang_v0.0.12的项目)或test.zlc(对于zenglrun的项目),好像每一节都提到过。
另外对于vs2008的用户,我在项目属性里:[配置属性>>>>C/C++ >>>> 高级] 部分设置了禁用特定警告:4013,4715,4996 ,这几个警告会显示一些某某函数是非安全的函数,或者函数没有返回值等,这里禁用掉,防止出现过多的警告。另外还有个警告是显示某某变量没被使用过的, 这个警告我没禁用,可以不用管它。我最开始是使用Linux系统开发的zengl ,在我的GCC下面并没有显示过这些讨厌的警告,所以就没处理,不过还好这些警告都无关痛痒,无需理会。
linux系统下的用户请结合usage.txt的说明,先运行make clean 将原来生成的zengl zenglrun 和 main.o parser.o assemble.o ld.o func.o run.o symbol.o文件删除。
再运行make all (单纯的make只能生成zengl,所以需要make all来生成所有的目标)
生成zengl zenglrun 和 main.o parser.o assemble.o ld.o func.o run.o symbol.o。(在生成过程中如果出现一些警告,暂不管他)
最后运行 ./zengl test.zl 查看printASTnodes函数打印抽象语法树节点的结果,以及符号表输出的变量信息以及本节新增的函数信息等。(例如变量的内存地址,以及在源文件的行列号,函数的唯一标识ID等)。
接着运行./zenglrun test.zlc (注意是.zlc结尾的文件名,因为zenglrun虚拟机只能运行.zlc里的汇编代码)。
本节涉及到的很多高级的编译原理都可以在《龙书》中找到。
最后的最后,如果转载请注明来源 http://www.zengl.com , OK , 先到这里,休息,休息一下 O(∩_∩)O~