前几节实现了if条件控制语句,这节v0.0.11版本就实现了for循环控制结构。 本节v0.0.11版本的源代码下载地址为:http://pan.baidu.com/share/link?shareid=141552&...
前几节实现了if条件控制语句,这节v0.0.11版本就实现了for循环控制结构。
本节v0.0.11版本的源代码下载地址为:http://pan.baidu.com/share/link?shareid=141552&uk=940392313 (此为百度云盘的共享链接地址),访问该地址可以看到三个文件:zengl_lang_v0.0.11_forXP.rar (XP系统下的vs2008解决方案和源代码), zengl_language_v0.0.11_forLinux.tar.gz (Linux系统下的源代码和makefile) ,v0.0.11-v0.0.10-diffs.txt (v0.0.11和v0.0.10的代码变化情况)。
SourceForge.net上的仓库地址为:https://sourceforge.net/projects/zengl/files/ 从里面可以看到各个版本的代码压缩包,比如本节的zengl_lang_v0.0.11_forXP.rar ,zengl_language_v0.0.11_forLinux.tar.gz,v0.0.11-v0.0.10-diffs.txt 。
先来看下本版本的描述 (在linux代码包里的usage.txt里有这段描述)
v0.0.11版本,该版本增加了for循环控制结构,同时规范了语法树生成时的curnode游标的加减运算。
按照前面if条件控制语句编写出for循环控制语句,这里的for和c语言的for一样,有初始化部分,条件判断部分,后续执行部分以及循环体部分,以 endfor结尾,其实就是通过跳转地址循环跳到条件判断部分,当条件判断失败时再跳到endfor后面继续执行。
前面的commit中print_stmt和if_stmt及statement函数中由于express2函数返回时没有进行curnode++运算, 所以要手动进行curnode++和--,很容易出错,现在增加了express_count全局变量,用于追踪express2函数的递归深度,当 express_count为1时说明要返回express2的调用者了,这时再curnode++,这样其他的函数只需在特殊的情况下处理下 curnode游标,平时由express2自动处理了。这样开发for语句及其他语句就不容易出错。
作者:zenglong
时间:2012年3月5日
官方网站:www.zengl.com
来看下本节和for结构相关的主要代码:
void gen_codes(int nodenum)
{
........ //省略N行代码
case INFOR: //for...endfor..循环控制语句汇编代码输出
if(nodes[nodenum].childs.count == 4)
{
chnum = nodes[nodenum].childs.childnum; //chnum包含最开始的3个子节点。
extnum = nodes[nodenum].childs.extchilds; //extnum包含超过3个的扩展子节点。
if(chnum[0] != -1 && ISTOKEXPRESS(chnum[0])) //例如for(i=1;i<3;i++)其中的i=1初始化部分就是第一个子节点。
gen_codes(chnum[0]); //生成第一个子节点对应的表达式的汇编代码。
sprintf(addrname,"%s%d",gl_addrstr[ASM_FOR_ADDR],
push_stack_asm(gl_addrnum[ASM_FOR_ADDR]++,ASM_FOR_ADDR)); //记录下第二个比较子节点的开始汇编代码位置。循环体在执行到结束时会跳转到这里进行判断是否需要继续循环。
ld_addAddress(addrname,codenum); //将foradr和对应的汇编代码位置记录到ld.c的链接地址数组中。
if(chnum[1] != -1 && ISTOKEXPRESS(chnum[1])) //上个例子中i<3的用于判断是否需要继续循环的表达式就是第二个子节点。
{
gen_codes(chnum[1]);
outcode("JE %s%d",gl_addrstr[ASM_FOR_END],
push_stack_asm(gl_addrnum[ASM_FOR_END]++,ASM_FOR_END)); //如果判断为false就跳过循环。
}
i = extnum[0]; //扩展子节点部分就是for...endfor之间的循环体部分的代码。
while(i > 0) //如果节点号小于0,说明是条空语句。
{
gen_codes(i); //循环生成for...endfor之间的所有语句对应的汇编代码。
i = nodes[i].nextnode;
}
if(chnum[2] != -1 && ISTOKEXPRESS(chnum[2])) //第三个子节点就是上例中的i++,循环体执行完后需要执行的代码。
gen_codes(chnum[2]);
outcode("JMP %s%d",gl_addrstr[ASM_FOR_ADDR],
pop_stack_asm(ASM_FOR_ADDR,TRUE)); //执行完第三个子节点的代码后,跳转到第二个子节点的代码处进行判断是否需要继续循环。
sprintf(addrname,"%s%d",gl_addrstr[ASM_FOR_END],
pop_stack_asm(ASM_FOR_END,TRUE));
ld_addAddress(addrname,codenum);
state = DOWN;
}
else
gencode_error("gen code err: for must 4 childs (for语句至少需要4个子节点)",nodenum);
break;
............ //省略N行代码
}
上面的代码和注释都可以在assemble.c源文件中找到,这段是生成for循环结构汇编的代码,可以从中看出for语句的基本原理,其他源代码请使用 上一节中提到过的git bash来查看本版本的代码变化情况。并结合vs2008(windows环境下) 或 gcc,eclipse+cdt(linux环境下)进行调试分析。
这里主要看看for循环控制结构的语法树结构。
先来看下本节的示例脚本(test.zl):
for(i=1;i<10;i++)
print 'i is ' + i;
print 'test'; //这句是这里为了更好的显示语法树结构而添加的,在test.zl中没有。
endfor
这段脚本很好理解,就是循环打印"i is 1"到"i is 9" ,语法树示例图如下:
可以看出for语句的三个条件判断语句分别被设置为for的三个基本子节点,第一个子节点i=1是初始化子节点,第二个子节点i<10是条件判断子 节点,第三个i++是循环体执行完时的后续执行语句。循环体的print脚本代码被放置在扩展节点中,print 'test';语句则放置在第一个print语句的next node节点中,如果还有更多的循环体语句则这些语句也同理通过next node相互连接构成一个链表结构。
test.zl脚本生成的目标汇编代码如下:
0 MOV AX 1; //将1赋值给AX寄存器
1 MOV (0) AX; //将AX再赋值给变量i所在的内存0的位置。
2 MOV AX (0);
3 MOV BX 10;
4 LESS; //将变量i和10进行比较。
5 JE 12; //如果i大于10则跳到12的END位置处结束执行。
6 MOV AX "i is ";
7 MOV BX (0);
8 PLUS;
9 print AX; //执行print 'i is ' + i;语句。
10 GETADD (0); //将i++
11 JMP 2; //跳到2位置处进行i和10的大小判断。这样通过跳转实现了循环控制结构。
12 END;
for循环结构的原理就差不多讲完了,其他的细节部分,请自行分析源代码。
最后是些老生常谈的话题:
windowsXP压缩包中的代码包括test.zl测试脚本都是采用GBK的编码,Linux压缩包中的代码包括测试文件以及git里的信息都是UTF8的编码,所以如果哪些地方出现了乱码,请自行调整。
对于其他代码的分析,因为已经在C源代码里加了很多注释,请结合注释,以及git,再加上VS2008之类的调试开发环境进行调试分析。
对于windows用户,请确保在项目属性的配置里,命令行参数配置的是test.zl(对于zengl_lang_v0.0.11的项目)或test.zlc(对于zenglrun的项目),好像每一节都提到过。
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函数打印抽象语法树节点的结果,以及符号表输出的变量信息(例如变量的内存地址,以及在源文件的行列号等)。
接着运行./zenglrun test.zlc (注意是.zlc结尾的文件名,因为zenglrun虚拟机只能运行.zlc里的汇编代码)。
本节涉及到的很多高级的编译原理都可以在《龙书》中找到。
最后的最后,如果转载请注明来源 http://www.zengl.com , OK , 先到这里,休息,休息一下 O(∩_∩)O~