前几节实现了if条件控制语句,这节v0.0.11版本就实现了for循环控制结构。     本节v0.0.11版本的源代码下载地址为:http://pan.baidu.com/share/link?shareid=141552&amp...

    前几节实现了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.rarzengl_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~

上下篇

下一篇: zengl编程语言v0.0.12函数的实现

上一篇: zengl编程语言v0.0.10实现流程嵌入结构

相关文章

zengl v1.6.0-v1.7.1, zenglServer v0.1.0

zengl v1.2.4 脚本加密,单目负号,VC6编译,新增API,BUG修复

zengl v1.8.3 修复ReUse接口缺陷及段错误,完善语法检测,使用Android Studio开发android项目

zengl编程语言v0.0.13函数中实现global和return

zengl v1.8.0 缓存内存中的编译数据,跳过编译过程

zengl编程语言v0.0.8第二代语法解析函数