回顾上一节,完成了加减乘除和字符串的打印,不过print还无法直接将加减乘除的表达式结果显示出来。这一节,将实现这个功能,并且修复几个代码文件里的BUG。   本节v0.0.7版本的源代码下载地址为...

  回顾上一节,完成了加减乘除和字符串的打印,不过print还无法直接将加减乘除的表达式结果显示出来。这一节,将实现这个功能,并且修复几个代码文件里的BUG。

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

  我在SourceForge.net上开了个project,地址为:https://sourceforge.net/projects/zengl/files/  从里面可以看到各个版本的代码压缩包,比如本节的zengl_lang_v0.0.7_forXP.rar ,zengl_language_v0.0.7_forLinux.tar.gz,v0.0.7-v0.0.6-diffs.txt (不过版本没按顺序上传,所以需要找一下,^_^)。

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

  v0.0.7版本,该版本实现了print可以直接将表达式打印出来,同时两个int数相除时按照常规的浮点数进行运算。
    
  assemble.c在生成print指令时,如果是表达式,则递归调用gen_codes将表达式进行解析,再将结果print打印出来。同时改进了gencode_error函数,这样错误信息可以很明了的显示出来。
  parser.c中改进了printNode函数,这样接收参数只需要节点号就可以打印节点信息了。同时还加入了is_printNode_force全 局变量,这样就可以即使该节点被打印过了也可以强制打印出来。global.h中加入了该全局变量和printNode函数的新的声明。
  main.c中修复了一个BUG:当字符串等Token中含有"\n"回车换行符时,行号也会对应增加,这样才能得到正确的Token的行列号信息。
  run.c中在除法运算时,如果两边都是整数,在原来就是按照C语言的方式,结果只取整数,比如1/2结果为0,现在采取浮点的方式1/2结果为0.5,这样更符合常规思维模式。
    
  作者:zenglong
  时间:2012年2月7日

  在源代码文件里包含了很多注释,方便调试分析研究,先来看下几个主要文件源代码的变化情况。

  在assemble.c文件里,主要是当print和表达式在一起时,先gen_codes生成表达式的汇编码,再将结果打印出来。

/**
    该函数根据AST抽象语法树的节点索引将某节点转为汇编代码,并通过outcode函数输出到汇编代码文件里。
    参数nodenum为节点在语法树动态数组里的节点索引。
*/

void gen_codes(int nodenum)
{
          ....... //省略N行代码

          case INPRINT:  //打印语句的汇编代码输出
            if(ISTOKCOUNT(nodenum,1))
            {
                chnum = nodes[nodenum].childs.childnum;
                if(ISTOKTYPEX(chnum[0],ID))  //打印变量等标示符
                {
                    sym = lookupSym(chnum[0]);
                    outcode("print (%d)",sym->memloc);
                }
                else if(ISTOKTYPEXOR(chnum[0],NUM,FLOAT))  //打印整数,浮点数
                    outcode("print %s",GETTOKSTR(chnum[0]));
                else if(ISTOKTYPEX(chnum[0],STR))  //打印字符串
                    outcode("print \"%s\"",GETTOKSTR(chnum[0]));
                else if(ISTOKEXPRESS(chnum[0])) //如果是表达式,就先生成表达式的汇编代码,表达式的结果一般存放在AX中,所以最后用print AX来打印表达式的值。
                {
                    gen_codes(chnum[0]);  //递归调用gen_codes,生成表达式的汇编代码。
                    outcode("print AX");
                }
                else
                    gencode_error("gen code in print err: print first child node is not id,num,float,string,express (打印关键词暂时只能打印变量,数字,浮点数,字符串)",nodenum);  //这里的注释忘记修改了,应该是除了变量,数字,浮点数,字符串,还可以打印表达式。
                state = DOWN;
            }
            else
                gencode_error("gen code err: print node must have 1 child-node (打印节点必须包含一个子节点)",nodenum);
            break;

          ....... //省略N行代码

}

  在main.c中修复了当字符串中含有'\n'换行符时,全局行列号没增加的BUG。

void makeTokenStr(char ch)
{
    .....  //省略N行代码
    TokenString.str[TokenString.cur++] = ch;
    TokenString.str[TokenString.cur] = STRNULL;
    TokenString.count++;
    if(ch == '\n')  //当字符串中存在换行符号时,也对应的增加全局行列号,这样就不会出现符号表的行列号与代码文件不符的情况。
    {
        line_no++;  //增加全局行号
        col_no=0;  //将列号重置为0
    }
}

  run.c中在除法运算时,如果两边都是整数,在原来就是按照C语言的方式,结果只取整数,比如1/2结果为0,现在采取浮点的方式1/2结果为0.5,这样更符合常规思维模式:

/**
    虚拟机执行汇编指令的主程式。
*/

void RunInsts()
{

          .......  //省略N行代码

          case DIVIDE:
            if(REG(AX).idtype == IDINT && REG(BX).idtype == IDINT)  //除法指令,AX,BX整数,转成double浮点数,再相除,结果以浮点数的形式存放在AX的qword里。这样1/2结果就是0.5,比C语言里的除法更符号人们的习惯。
            {
                REG(AX).val.qword = (double)REG(AX).val.dword / (double)REG(BX).val.dword; //将dword里的整数转为double类型,以浮点方式相除,结果存放在浮点成员qword中。
                REG(AX).idtype = IDFLOAT;  //将AX类型设为浮点数类型
            }

          .......  //省略N行代码

}

  本节的test.zl测试脚本就用到了print的打印表达式新特性,从而不需要像上一节那样还需增加一个str中间变量。(代码注释是这里为了说明而加的,在脚本文件里并没有,因为目前zengl脚本还没加入注释功能,在以后版本里会加入该功能):

a = 5;
b = a * 2 + 116;
print 'a is ' + a + ' b is ' + b;  //直接打印表达式
print 1/2;  //直接打印表达式,1和2相除将会得到浮点值0.5

前两条语句的语法树前一节已经介绍过了。这里就看下最后两条打印语句的语法树:

  语法树经过assemble.c生成的汇编代码如下(注释在这里只是起说明作用,在原汇编代码文件里并没有):

0 MOV AX 5;
1 MOV (0) AX;
2 MOV AX (0);
3 MOV BX 2;
4 TIMES;
5 MOV BX 116;
6 PLUS;
7 MOV (1) AX;
8 MOV AX "a is ";
9 MOV BX (0);
10 PLUS;
11 MOV BX " b is ";
12 PLUS;
13 MOV BX (1);
14 PLUS;           //到这里完成'a is ' + a + ' b is ' + b,结果存放在AX中
15 print AX;    //将表达式的结果AX打印出来。
16 MOV AX 1;
17 MOV BX 2;
18 DIVIDE;  //将1和2相除,结果存放在AX中。
19 print AX; //将相除的结果0.5打印出来。
20 END;  //退出虚拟机

  最后说下编译代码的注意事项:

  对于其他代码的分析,因为已经在C源代码里加了很多注释,请结合注释,加上VS2008之类的调试开发环境进行调试分析。 

  对于windows用户,请确保在项目属性的配置里,命令参数配置的是test.zl(对于zengl_lang_v0.0.7的项目)或test.zlc(对于zenglrun的项目),上一节提到过。

  linux系统下的用户请结合usage.txt的说明,先运行make clean 将原来生成的zengl zenglrun 和 main.o parser.o assemble.o func.o run.o  symbol.o文件删除。

  再运行make all (单纯的make只能生成zengl,所以需要make all来生成所有的目标)

  生成zengl zenglrun 和 main.o parser.o assemble.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.8第二代语法解析函数

上一篇: zengl编程语言v0.0.6创建小型计算器

相关文章

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

zengl编程语言v0.0.17单行多行注释

zengl v1.4.0 调试接口 zengl_SDL项目

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

开发自己的编程语言-zengl编程语言v0.0.1词法扫描器的实现

zengl v1.3.0 位运算符 字符串脚本解析 缓存 Bug修复