上一节虽然实现了if-elif-else-endif的条件控制结构,但是控制结构之间还无法相互嵌入,即if结构里还不能包含其他的if结构。本节通 过引入堆栈来实现流程之间的相互嵌入,同时还实现了逗号,以...

    上一节虽然实现了if-elif-else-endif的条件控制结构,但是控制结构之间还无法相互嵌入,即if结构里还不能包含其他的if结构。本节通 过引入堆栈来实现流程之间的相互嵌入,同时还实现了逗号,以及++ , --  , +=  ,  -=  , *=  ,  /= 的运算符。

    本节v0.0.10版本的源代码下载地址为:http://pan.baidu.com/share/link?shareid=132201&uk=940392313  (此为百度云盘的共享链接地址),访问该地址可以看到三个文件:zengl_lang_v0.0.10_forXP.rar (XP系统下的vs2008解决方案和源代码), zengl_language_v0.0.10_forLinux.tar.gz  (Linux系统下的源代码和makefile) ,v0.0.10-v0.0.9-diffs.txt  (v0.0.10和v0.0.9的代码变化情况)。

    SourceForge.net上的仓库地址为:https://sourceforge.net/projects/zengl/files/   从里面可以看到各个版本的代码压缩包,比如本节的zengl_lang_v0.0.10_forXP.rar ,zengl_language_v0.0.10_forLinux.tar.gz,v0.0.10-v0.0.9-diffs.txt 。

    先来看下本版本的描述 (在linux代码包里的usage.txt里有这段描述)

    v0.0.10版本,该版本增加了逗号,以及++,--,+=,-=,*=,/=的运算符,if可以嵌入其他if等语句,优化内存池,提高效率,消除段错误。
   
    逗号运算符具有最低优先级,所以可以将表达式分割为多个表达式,结果为最后一个表达式的值,++--运算符虽然比!运算符低,但是当!和++--在一起 时,哪个与变量近,哪个先执行,多个++--在一起时结合情况有些复杂,最好用括号来手动结合,而且多个++--在一起只能修改变量一次,其他的按表达式 处理,不改变变量值,因为多个++--完全可以由+=或-=来实现,当++ --与变量在一起时,当在变量前时先执行加减运算将变量值修改后再取值,当在变量后时则先取值后加减,当++ --和表达式在一起时,不论前后都是先加减再取值,因为表达式不像变量没有内存来存放加减的结果。
    这里还增加了+=,*=,-=,/=,含义和c中一样,优先级和结合情况也和赋值符‘=’一样。
    在parser.c(这里写错了,应该是assemble.c文件,压缩包已经上传上去了,所以linux压缩包里的usage.txt没有改,大家知道就可以了,parser.c中的堆栈作用前面章节已经提到过,是为了比较优先级用的。)中增加了压栈和出栈操作,而且总栈中还存放着多个栈,如ifadr跳转地址的栈和ifend跳转地址的栈,以后还可以存放while,for等的跳转地址栈,之所以要栈保存跳转地址是因为当if语句或以后的for语句中如果要嵌入别的if语句或其他语句时,必须先将当前if的跳转地址名压入栈保存,当嵌入的if语句处理完后才能恢复之前的跳转地址,才能实现嵌入控制语句的操作。
    另外还优化了内存池的操作函数,比如zl_malloc ,zl_realloc,zl_freeall等,同时还增加了zl_strcat函数来替代系统的strcat,因为strcat会破坏内存池,从而发生段错误。
    将所有id,num,float,str的token的字符串信息都保存到一个大的字符串池中,通过索引来访问需要的token的字符串信息,这样只要malloc一次,不像原来每个标示符,数字或字符串的token都要malloc一次为他们存放各自的字符串信息。提高了效率,减少了内存开销。防止段错误发生。
    在run.c中也将汇编中的所有数值和字符串等以字符串的形式保存到一个大的字符串池中,同样减少了内存开销。
    这次修改的地方较多,其他的改动可用git log -p来查看。

    作者:zenglong
    时间:2012年2月29日
    官方网站:www.zengl.com

    源代码请结合注释和相关的VS2008之类的调试工具进行分析。
    为了方便分析代码的变化情况,本节开始引入git ,版本控制系统。在Linux的压缩包解压后,在源代码目录中可以看到一个.git的目录,这个是git版本控制系统自动生成的文件夹。Linux下已经 默认安装了,因为git是linux创始人linus torvalds开发的。如果要在windows下使用git查看代码的版本变化情况,需要安装git的windows版本,我已经放在百度盘里了,是 1.7.10的版本,下载地址:https://www.dropbox.com/s/mjepyhwvgljagxw/Git-1.7.10-preview20120409.exe  下载安装后,使用方法如下:

    1,先右键单击linux源代码目录(本节为解压后的zengl_language_working_v0.0.10文件夹):

    如上图所示选择Git Bash,会弹出如下的命令行窗口:

    这个是MINGW32模拟出来的类似linux系统下的bash命令行。在这个命令行窗口里可以输入各种常见的linux命令,如cd ,ls 等。我们需要用的是git命令,如上图所示,在美元'$'提示符后面输入git log -p ,这条命令是用来打印git版本的日志信息的,里面可以看到各个版本之间的详细的代码变化情况。如下图所示:

    上图就是git log -p在开头输出的本节代码相关的描述性的内容。

    上图可以清楚的看到v0.0.10和v0.0.9版本之间代码减少了哪些行,又增加了哪些行,红色的(左边还有个减号的表示减少的代码行),绿色的(左边有个加号的表示增加的或改变的代码)。
    这样一来,就可以很清楚的知道版本升级的情况了。再结合源代码中的注释和调试工具,要分析源代码就不难了。

    接下来看下本节的测试脚本test.zl(下面的注释只是在这起说明作用,源代码里并没有,因为注释的功能要在后面的版本中才实现) :

 test = 114 , test1=12;  //将test变量赋值为整数114,test1赋值为12 ,这里可以看到逗号的用法和C语言中的一样,就是从左到右依次执行表达式。
 print 'test is ' + test;  //打印test变量信息
 print 'test1 is ' + test1;  //打印test1的变量信息
 lss = "40"; //将lss变量赋值为整数字符串“40”,字符串里包含数字时同样可以参与加减比较等表达式的运算,下面会看到这种用法。
 if(test < lss)  //第一层if结构,也是嵌套在最外层的if控制结构。如果test小于lss,就执行下面的打印操作,但是上面的test是114比lss的“40”要大,所以会继续判断下一条elif的条件判断。
   print "test < lss";
   print test = 67 + 89 - 6;
 elif (lss == test)  //判断lss是否和test相等。
   print "lss == test";
   print !!lss;
   test = test - lss;
 elif(lss == 40)  //如果lss等于40,就往下执行,前面的lss是整数字符串"40",值和40相等,所以程序会执行该elif块里的代码。目前zengl语言并没实现像PHP那样的===恒等于。
    print "lss:" + lss;  //打印lss变量信息
     print '++lss is ' + ++lss + " 14.5 ++ + ++ 13 = " + (14.5++ + ++13) +' and ++501.345 is ' + "501.345db"++; //测试加加运算符,"501.345db"这个字符串在进行加加运算时,会先转为浮点数501.345 ,然后再进行加加运算。
    print '!++lss is ' + !++lss; //任何非零值取反后,值都为0 。
    if(test > lss)  //第二层嵌套的if结构,如果test大于lss就执行下面的程序块。
      print 'test is big lss';
      if(test == 110) //第三层嵌套的if结构,如果test等于110,执行下面的打印语句,这里test为114所以不会执行。
        print 'test is 110';
      elif (test == 114 && test1 == 9)  //如果test为114并且test1为9则执行下面的打印语句,&&为且运算符。
        print 'test is 114 and test1 is 9';
      elif(test == 114 && test1 == 12)  //如果test为114并且test1为12则执行下面的打印语句,这里符合条件所以下面的语句会被执行。
        print 'test is 114 and test1 is 12';
        print '3层测试通过!';
      endif
      print '3层结束!'; //此处打印完后,直接跳到该层endif后面继续执行。
    else
      print 'hello test is small lss';
    endif
    print 'elif with if end';  //上面的if结构打印完后,就会执行此处的打印语句。执行完此处后,会跳转到该层的endif后面继续执行。
 else
   print "test > lss";
   test = test + lss;
 endif
 print "日元:3500 * 10000 / 12.6157 =" +
    3500 * 10000 / 12.6157; //打印日元汇率转换信息,假设汇率为12.6157,那么执行完后就会打印出3500万日元对应的人民币为2774320.885880 。

上面的例子可以看出if结构的嵌套使用情况。
下面看下if嵌套结构堆栈的作用:

第一个if在生成汇编代码时会生成JMP ifadr0之类的跳转地址,当条件判断失败时就会跳转到ifadr0的位置,在生成汇编代码的同时会将ifadr0这个跳转地址名压入栈(实际的C代码 并非用的ifadr0这个字符串压入栈而是用的一个数字和ASM_IF_ADDR枚举值压入栈的,这里为了方便说明,就直接写成ifadr0,汇编代码临 时文件也用的是ifadr加数字来表示一个跳转地址的)。
当这个if结构里面再嵌套一个if时,在生成汇编代码时会将该层的ifadr地址压入栈,结构图如下:

当嵌入的if语句块结束时,根据堆栈弹出的值,就会通过ld.c的ld_addAddress函数生成对应的ifadr1的地址值:

上图的ld_addAddress函数会将ifadr1和当前的汇编代码位置保存到ld.c链接器的数组中,到时候在链接器进行扫描替换ifadr这样的跳转地址时,就可以用前面保存的汇编代码位置进行替换。
当最外层if的语句块结束时,同理会将ifadr0弹出栈,并将对应的汇编代码位置保存到链接器的数组中:

以上就是if使用堆栈嵌套的原理。通过堆栈保证了各层if结构能JMP跳转到对应的正确的地址处。
前面提到过:本节开始所有id,num,float,str的token的字符串信息都保存在一个大的字符串池中,通过索引来访问需要的token的字符串信息,这样只要malloc一次。来看下面的例子:
  test + 'hello'; 这个语句有四个token分别是test ,加号 ,'hello'字符串以及分号。以前的版本需要为每个token的字面信息都malloc一次(test的字面信息就是test这个字符串,加号的字面 信息就是'+')。现在这些token的字面信息都存放在一个大的字符串池中:
 
    最后值得一提的是,windowsXP压缩包中的代码包括test.zl测试脚本都是采用GBK的编码,Linux压缩包中的代码包括测试文件以及git里的信息都是UTF8的编码,所以如果哪些地方出现了乱码,请自行调整。
   
    对于其他代码的分析,因为已经在C源代码里加了很多注释,请结合注释,以及git,再加上VS2008之类的调试开发环境进行调试分析。    
    对于windows用户,请确保在项目属性的配置里,命令行参数配置的是test.zl(对于zengl_lang_v0.0.10的项目)或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.11实现循环控制结构

上一篇: zengl编程语言v0.0.9流程控制语句的实现

相关文章

zengl v1.9.1 函数参数可以使用负数作为默认值

zengl v1.7.4 修复Bug

zengl v1.7.2, zenglServer v0.2.0

zengl v1.5.0 移植到zenglOX系统

zengl编程语言v0.0.18初始化数组函数

zengl v1.2.2 正式发布版及智能采集器源代码