从今天开始,我们一起来开发一款属于自己的编程语言。暂时命名为zengl编程语言。 任何一个编程语言都有自己的词法扫描器,用于将代码里的变量标示符,数字,操作符等准确的扫描出来。所以我们要做的第一件事情就是设计一个词法扫描器,然后利用该扫描器将代码里的变量,数字,加减乘除等扫描打印出来。
从今天开始,我们一起来开发一款属于自己的编程语言。暂时命名为zengl编程语言。
任何一个编程语言都有自己的词法扫描器,用于将代码里的变量标示符,数字,操作符等准确的扫描出来。所以我们要做的第一件事情就是设计一个词法扫描器,然后利用该扫描器将代码里的变量,数字,加减乘除等扫描打印出来。
代码暂时放在sourceforge上面。下载链接:http://sourceforge.net/projects/zengl/files/zengl_lang_v0.0.1_forXP.rar/download (此链接为zengl v0.0.1版本的xp版本,解压后将得到vs2008的项目解决方案,请使用火狐浏览器点开此链接并下载,IE浏览器可能会启动迅雷,而迅雷好像下载不 了,可能sourceforge屏蔽了吧)。http://sourceforge.net/projects/zengl/files/zengl_language_v0.0.1_forLinux.rar/download (此链接为zengl v0.0.1的Linux版本,解压后有usage.txt使用说明文档),本想发布到爱问等网盘上面去,不过这些网盘要审核,所以先放到 sourceforge上。
百度盘的v0.0.1版本的共享链接地址: http://pan.baidu.com/share/link?shareid=85543&uk=940392313
现在以XP版本为例进行说明,v0.0.1版本词法分析扫描器就一个main.c文件,先看下面的 getToken函数的代码,再来分析词法扫描器的原理:
enum STATES state = START; //设置起始状态为START
enum TOKENTYPE token;
while(state!=DOWN) //当state状态为DOWN时,表示找到一个变量或者别的token(一般变量,加减符号,数字等每个扫描出来的元素都称作token)
{
char ch = getNextchar(); //打开一个文件,并从中读取一个字符
switch(state)
{
case START:
if(ch==' ' || ch=='t' || ch=='n') //如果是换行回车之类的就跳过。
continue;
else if(isalpha(ch)) //判断读取的字符是否是英文字母。
{
state=INID; //如果是字母,我们就将state状态机设置为INID 。
makeTokenStr(ch); //然后将读取出来的ch字符通过函数makeTokenStr加入到TokenString动态字符串里
}
else if(isdigit(ch)) //判断读取的字符是否是数字
{
state=INNUM; //如果是数字,我们就将state状态机设置为INNUM 。
makeTokenStr(ch); //然后将读取出来的ch字符通过函数makeTokenStr加入到TokenString动态字符串里
}
else
{
switch(ch)
{
case '+':
state = DOWN; //如果字符是‘+’号就将state状态机设为DOWN,这样就可以结束循环,并把token设为PLUS枚举值,表示找到加号运算符。
token = PLUS;
break;
case '-':
state = DOWN; //和上面同理
token = MINIS;
break;
case '*':
state = DOWN; //和上面同理
token = TIMES;
break;
case '/':
state = DOWN; //和上面同理
token = DIVIDE;
break;
case '=':
state = DOWN; //和上面同理
token = ASSIGN;
break;
case EOF:
state = DOWN; //EOF字符表示读取到了文件的结尾,则返回ENDFILE的token,在外层main函数的主循环就会结束扫描。
token = ENDFILE;
break;
default:
state = DOWN; //其他情况下表示读取到了未定义的token,那么就返回ERROR。
token = ERROR;
break;
}//switch(ch)
makeTokenStr(ch);
}
break;
case INID:
if(isalpha(ch)) //在INID状态下,一直读取字符,直到该字符不是字母为止,并将读取的字母通过makeTokenStr构成完整的标示符。
makeTokenStr(ch);
else
{
state = DOWN;
token = ID; //将token设为ID表示读取到了一个标示符。ID即identifier 英文缩写。
ungetchar(); //因为多读取了一个非字母的字符,所以用ungetchar函数来回退一个字符,供下一次扫描使用。
}
break;
case INNUM: //在INID状态下,一直读取字符,直到该字符不是数字为止,并将读取的单个数字通过makeTokenStr构成完整的数值。
if(isdigit(ch))
makeTokenStr(ch);
else
{
state = DOWN;
token = NUM; //将token设为NUM表示读取到了一个数字。
ungetchar();
}
break;
}//switch(state)
}//while(state!=DOWN)
return token;
上面这个函数就是整个语法扫描器的核心部分,代码旁边的注释暂时没加入到源代码文件里面,只在这里有,以后会将注释将进去。
其实词法扫描器就是循环读取文件里的字符,并通过读取到的首字符判断是哪种类型的token,如果是字母,说明当前读到的部分是标示符,就将state状 态机由START开始读取的状态转为INID(在identifier的状态即处于标示符的读取状态),在INID的switch case里将后面的连续的字母和首字母一起构成一个完整的标示符,并将这些连续的字母存放到TokenString动态字符串里,这样就找到了一个 token了,其他的数字,运算符之类的Token也是同理查找,只要熟悉C语言的基本语法,都能看懂这段代码。
getNextchar函数是循环读取文件中的字符的函数。第一次调用该函数时,会打开source.filename里指定的文件,该文件由命令行的参 数来给定,打开文件后,就用fgets函数将文件里的1024个字符读取到source.buf缓存里,以后就直接从缓存里读取字符,当缓存读取完毕后, 再从文件中读取1024个字符到缓存里,直到读取到文件结尾为止。
另外,代码中有个makeTokenStr函数,该函数是用来组建动态字符串TokenString用的,因为C语言里的字符串默认都是静态的,也就是无 法动态增加长度,所以需要自己设计一套动态分配内存的函数来根据需要扩充TokenString字符串占用的内存。
在main.c文件里设计了一个内存池,将所有分配的内存指针存放在一个动态指针数组zl_points.points里,通过zl_malloc函数为 TokenString.str分配内存,并将分配的内存指针存放在zl_points.points里 , 当TokenString.str需要更多内存来存放字符时,就调用zl_realloc函数来扩充TokenString.str的动态内存空间,并更 新zl_points.points里对应的内存指针,当程序结束时,在调用myexit函数时,会通过zl_freeall函数来将 zl_points.points里的动态内存指针全部释放掉,这样就避免了内存泄露,有点像Java的垃圾回收机制。
其他的东东,请查看源代码,并通过调试来理解,注意调试的时候要指明词法解析的代码文件,在VS2008下,项目名右键-->属性--->配置属性--->调试--->右侧的命令行参数里进行设置 。本例中在main.c目录下放置了一个test.zl测试代码文件,可以在命令行参数中填入test.zl来测试效果。
在main.c最后还加入了一条system ("pause"); vs2008下为了能看到结果,需要暂停,否则就一闪而过,什么都看不到咯。
本例的词法扫描器以及以后的抽象语法树等很多高级的原理都可以在《龙书》中找到,我将龙书的源代码做了很多改动得到一个简单的zengl编程语言,让其可以用于实际的项目开发中。
网上可以搜索(龙书 编译原理) 来下载pdf电子档。
linux下的用户在解压Linux版代码后,根据usage.txt的说明,先make clean 清理原来生成的二进制文件,再make生成当前环境下的可执行文件,最后./zengl test.zl 来测试,当然前提是安装了gcc编译器。 OK , 先到这里,休息,休息一下 O(∩_∩)O~