使用flex和bison,对c语言代码块进行词法分析,识别词法错误,按照c—语法规则进行文法分析,并形成c语言代码块的语法树(syntax tree),并将语法树按照特定的格式打印出来。
如何编译
两种方法:
(1)使用make命令:先将要执行的所有命令写入到Makefile文件中,然后执行make命令,这就相当于将Makefile中的所有命令都执行完毕了,在终端可以清楚地看到系统每执行一条命令的结果,如果有错或者有警告都会输出。make执行完之后,就生成a.out文件,使用cat filename|./a.out就可以对filename中的文件就行语义分析了。
当文件有更新时,只需要执行以下make即可。
(2)这种是最笨的方法,就是不写Makefile文件,每次都是逐条去执行编译命令,文件有更新之后也要逐条执行,这种效率是最低的。make命令的出现就是为了提高编译效率的,有关make的知识点可以自行百度。
下面是第一种编译方法的具体过程:
【实验程序】
0,Makefile——编译文件
程序总共包含4个文件gramtree_v1.h gramtree_v1.c gramtree.l gramtree_v1.y
gramtree_v1.h gramtree_v1.c定义和实现了创建语法树和遍历语法树的函数
gramtree.l是flex词法分析模块
gramtree_v1.y是bison语法分析模块
编译时使用Makefile文件,编写文件内容,vim Makefile
result:gramtree_v1.y gramtree.l gramtree_v1.h
bison -d gramtree_v1.y
flex gramtree.l
gcc gramtree_v1.tab.c lex.yy.c gramtree_v1.c
编译过程如下:
[root@localhost flex]# make
[root@localhost flex]# cat input3.c|./a.out
先执行make命令,这代表Makefile的编译步骤全都执行完毕了。然后使用
cat filename|./a.out就可以对filename中的代码进行语法分析,打印出语法树。
gramtree_v1.h—定义创建和遍历语法树函数的头文件
/*
*Name:gramtree_v1.h
*Author:WangLin
*Created on:2015-10-03
*Function:定义语法树结点结构体&变长参数构造树&遍历树函数
*/
/*来自于词法分析器*/
extern int yylineno;//行号
extern char* yytext;//词
void yyerror(char *s,...);//错误处理函数
/*抽象语法树的结点*/
struct ast
{
int line; //行号
char* name;//语法单元的名字
struct ast *l;//左孩子
struct ast *r;//右孩子
union//共用体用来存放ID/TYPE/INTEGER/FLOAT结点的值
{
char* idtype;
int intgr;
float flt;
};
};
/*构造抽象语法树,变长参数,name:语法单元名字;num:变长参数中语法结点个数*/
struct ast *newast(char* name,int num,...);
/*遍历抽象语法树,level为树的层数*/
void eval(struct ast*,int level);
gramtree_v1.c——实现创建和遍历语法树函数
/*
*Name:gramtree_v1.c
*Author:WangLin
*Created on:2015-10-03
*Function:实现变长参数构造树&遍历树函数&错误处理函数,yyparse()启动文法分析
*/
# include<stdio.h>
# include<stdlib.h>
# include<stdarg.h>//变长参数函数所需的头文件
# include"gramtree_v1.h"
int i;
struct ast *newast(char* name,int num,...)//抽象语法树建立
{
va_list valist; //定义变长参数列表
struct ast *a=(struct ast*)malloc(sizeof(struct ast));//新生成的父节点
struct ast *temp=(struct ast*)malloc(sizeof(struct ast));
if(!a)
{
yyerror("out of space");
exit(0);
}
a->name=name;//语法单元名字
va_start(valist,num);//初始化变长参数为num后的参数
if(num>0)//num>0为非终结符:变长参数均为语法树结点,孩子兄弟表示法
{
temp=va_arg(valist, struct ast*);//取变长参数列表中的第一个结点设为a的左孩子
a->l=temp;
a->line=temp->line;//父节点a的行号等于左孩子的行号
if(num>=2) //可以规约到a的语法单元>=2
{
for(i=0; i<num-1; ++i)//取变长参数列表中的剩余结点,依次设置成兄弟结点
{
temp->r=va_arg(valist,struct ast*);
temp=temp->r;
}
}
}
else //num==0为终结符或产生空的语法单元:第1个变长参数表示行号,产生空的语法单元行号为-1。
{
int t=va_arg(valist, int); //取第1个变长参数
a->line=t;
if((!strcmp(a->name,"ID"))||(!strcmp(a->name,"TYPE")))//"ID,TYPE,INTEGER,借助union保存yytext的值
{
char*t;t=(char*)malloc(sizeof(char* )*40);strcpy(t,yytext);a->idtype=t;}
else if(!strcmp(a->name,"INTEGER")) {a->intgr=atoi(yytext);}
else {}
}
return a;
}
void eval(struct ast *a,int level)//先序遍历抽象语法树
{
if(a!=NULL)
{
for(i=0; i<level; ++i)//孩子结点相对父节点缩进2个空格
printf(" ");
if(a->line!=-1){ //产生空的语法单元不需要打印信息
printf("%s ",a->name);//打印语法单元名字,ID/TYPE/INTEGER要打印yytext的值
if((!strcmp(a->name,"ID"))||(!