上一节完成了单条语句的抽象语法树的构建,而且单条表达式里还只能使用变量标示符,不能使用数字,本节将完成多条语句的AST的构建工作,并可以在表达式里使用数字,还为语法树的每个节点元素增加了行...
上一节完成了单条语句的抽象语法树的构建,而且单条表达式里还只能使用变量标示符,不能使用数字,本节将完成多条语句的AST的构建工作,并可以在表达式里使用数字,还为语法树的每个节点元素增加了行列号,方便调试分析。
本节v0.0.4版本的代码下载地址: http://pan.baidu.com/share/link?shareid=68799&uk=940392313 (此为百度云盘的共享链接地址)访问该地址可以看到三个文件: zengl_language_v0.0.4_forLinux.tar.gz (此为Linux版的源代码,里面有makefile 和 usage.txt说明文档),v0.0.4-v0.0.3-diffs.txt (此为0.0.4版和0.0.3版源代码的比较文件,通过该文件可以知道两个版本的代码变化情况), zengl_lang_v0.0.4_forXP.rar (此为XP系统下的vs2008的解决方案和源代码)。 本来代码是打算放在sourceforge上的,但是sourceforge被墙的厉害,所以先放到百度盘上。
我在SourceForge.net上开了个project,地址为:https://sourceforge.net/projects/zengl/files/ 从里面可以看到各个版本的代码压缩包,例如本节的zengl_language_v0.0.4_forLinux.tar.gz,v0.0.4-v0.0.3-diffs.txt,zengl_lang_v0.0.4_forXP.rar
本节的代码文件和上一节一样,还是三个文件,先来看下main.c源代码的变化: TokenString增加了两个字段,在global.h中可以看到:
typedef struct{
char * str;
int size;
int count;
int cur;
int start_line; //每个token节点对应的第一个字符的行号
int start_col; //每个token节点应的第一个字符的列号
}TokenStr_Type;
在main.c的getToken函数里
case START:
makeTokenStrLineCol(line_no,col_no); //每次START开始扫描一个新的token时,就将当前的行列号line_no,col_no通过makeTokenStrLineCol函数来设置每个token的起始行列号。line_no 和 col_no 是两个记录着当前行列号的全局变量
if(ch==' ' || ch=='t' || ch=='n')
{
if(ch == 'n') //当遇到换行符时,就增加当前的行号,并将列号设为0
{
line_no++;
col_no=0;
}
continue;
}
在parser.c中
void AddNode(enum TOKENTYPE token)
{
...... //中间代码省略
AST_nodes.nodes[AST_nodes.count].line_no = TokenString.start_line; //在向语法树添加新节点时,设置新节点的行号
AST_nodes.nodes[AST_nodes.count].col_no = TokenString.start_col; //在向语法树添加新节点时,设置新节点的列号
AST_nodes.count++;
}
}
parser.c里增加了一个syntax_error函数用于打印语法错误的信息。
void syntax_error(char * err)
{
printf(err); //打印相关的语法错误信息
printf(": ");
printNode(AST_nodes.nodes + curnode,FALSE); //打印和错误相关的语法树节点信息
myexit(" exit n"); //退出并释放内存池等资源
}
parser.c 的printNode函数增加了打印行列号的程式:
void printNode(Node_Type *node,bool ischild)
{
...... //省略中间代码
printf("line:%d,col:%d n",node->line_no,node->col_no); //将节点的行列号打印出来。
...... //省略中间代码
}
parser.c 的buildAST函数通过循环来构建多个表达式语法树结构的链表结构。
void buildAST()
{
int p = -1;
while(curnode < AST_nodes.count-1)
{
if(curnode == -1)
{
AST_nodes.rootnode = express(); //ASTnodes.rootnode 指向语法树链表结构的根节点。
p = AST_nodes.rootnode;
}
else
{
AST_nodes.nodes[p].nextnode = express(); //通过每个节点的nextnode将多个语法树结构的顶端节点相互连接在一起来构建一个c语言的链表结构。
p = AST_nodes.nodes[p].nextnode;
}
}
}
例如在本节中的test.zl测试代码中有三条语句:
a = 5;
b = 3;
c = a + b * 15;
这三条语句通过buildAST函数的循环构建的语法树结构如下所示:
buildAST函数通过express函数来构建每个表达式的AST,再通过根节点和next节点相互连接来构建出AST的链表结构。
在本版本的express函数里,加入了NUM枚举,例如 if(ISTOKTYPEOR(ID,NUM)) 等,这样就既可以扫描变量标示符,又可以扫描数字。
该版本的源码就讲到这,其他函数的分析请自行研究源代码。
为了方便查看上个版本和当前版本的不同,所以从git提取了信息存放在v0.0.4-v0.0.3-diffs.txt文件里,从该文件里可以看出两个版本的代码变化情况。下载地址前面已经给出了。
最后linux下的用户在解压Linux版代码后,根据usage.txt的说明,先make clean 清理原来生成的二进制文件,再make生成当前环境下的可执行文件,最后./zengl test.zl 来测试,当然前提是安装了gcc编译器。
在./zengl test.zl测试时会打印输出详细的AST抽象语法树的组成详情,包括标示符等所在的行列号信息。
本例的抽象语法树等很多高级的原理都可以在《龙书》中找到。
最后的最后,如果转载请注明来源 http://www.zengl.com , OK , 先到这里,休息,休息一下 O(∩_∩)O~