编译原理 C-Minus 语法分析(Flex / Bison)

C-Minus 源代码 语法分析

一、实现目标

  在上一篇文章(C-Minus词法分析)中,我们实现了对C-Minus语言(C语言子集)书写的源代码进行词法分析,并打印分析结果;关于C-Minus语法的详细内容请参考上篇文章。而在本篇文章中,我们将运用语法分析相关知识,编写一个程序对使用类C语言书写的源代码进行语法分析,并打印分析结果。
  至少实现最低要求:输出语法分析树,并能定位错误信息。
  实现方式:可以选择手工编写(递归下降 / LL 分析 / LR 分析法)或者使用类似 Bison/Yacc 工具编写。

二、实现过程

1. 综述

  本篇文章采用了Flex/Bison工具实现语法分析。
  Flex是一个生成词法分析器的工具,可以将输入按照正则表达式识别为定义的Token,当Flex词法分析器与Bison连用时,Flex的返回值包括两部分:Token以及对应的Value,通过在 .l 文件中对 yylval 赋值并返回Token,Bison就可以将对应的值压入栈中,进行规约操作。
  Bison可以生成语法分析器,解析输入流中的Token,采用自底向上LALR的分析方法。当Bison读入一个终结符(Token),它会将该终结符及其语义值一起压入堆栈,这个堆栈叫做分析器堆栈。把一个Token压入堆栈通常叫做移进。在Bison分析文件的过程中,我们能够通过编写自己的代码并在action部分调用函数实现分析语法树的构建。

2. 实现功能介绍

(1)检测词法错误
 /*错误*/
{AERROR} {
	hasFault=1;
	printf("Error type A at line %d: Mystirious charachter '%s'\n",yylineno,yytext);
}

  由于Flex的匹配规则是从上到下、按照匹配长度决定优先级,通过在syntax_tree.l文件中action的最后部分添加对于任意字符的匹配AERROR,可以实现对未定义字符的检测,并将该错误字符输出。

(2)检测文法错误
// 错误处理
void yyerror(char *msg)
{
    hasFault = 1;
    fprintf(stderr, "Error type B at Line %d, %s ,before %s\n", yylineno, msg, yytext);
}

  Bison在对定义的语法和指定文件进行分析过程中,会调用Flex,进行移进、规约操作,在分析过程中,如果产生了语法错误,Bison会调用yyerror函数并停止分析。这时我们可以自己定义yyerror函数,输出语法错误的具体位置。

(3)生成语法分析树
// 抽象语法树
typedef struct treeNode{
    // 行数
    int line;
    // Token类型
    char* name;
    // fchild是第一个孩子节点,next是兄弟节点,使用孩子兄弟表示法
    struct treeNode *fchild,*next;
    // 联合体,同一时间只能保存一个成员的值,分配空间是其中最大类型的内存大小
    union{
        // id内容或者type类型(int/float)
        char* id_type;
        // 具体的数值
        int intval;
        float fltval;
    };
}* Ast,* tnode;

  通过自定义的语法分析树节点结构体以及构造函数,我们可以实现语法分析过程中构建语法分析树。

// 所有节点数量
int nodeNum;
// 存放所有节点
tnode nodeList[5000];
int nodeIsChild[5000];
// 设置节点打印状态
void setChildTag(tnode node);

  通过存储分析过程中构造的节点以及该节点的类型(子节点/根节点),可以在遍历完文件、构造完语法分析树后从根节点进行先序遍历,从而打印树结构。

/*syntax_tree.l 文件*/
{TYPE} {yylval.type_tnode=newAst("TYPE",0,yylineno);return TYPE;}
{STRUCT} {yylval.type_tnode=newAst("STRUCT",0,yylineno);return STRUCT;}
{RETURN} {yylval.type_tnode=newAst("RETURN",0,yylineno); return RETURN;}
{IF} {yylval.type_tnode=newAst("IF",0,yylineno);return IF;}
{ELSE} {yylval.type_tnode=newAst("ELSE",0,yylineno); return ELSE;}
/*syntax_tree.y 文件*/
/*High-level Definitions*/
Program:ExtDefList {$$=newAst("Program",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
    ;
ExtDefList:ExtDef ExtDefList {$$=newAst("ExtDefList",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
	| {$$=newAst("ExtDefList",0,-1);nodeList[nodeNum]=$$;nodeNum++;}
	;

  词法分析过程中,在.l文件中生成叶节点(终结符号),语法分析过程中,在.y文件中生成其他节点(非终结符),构建语法分析树。

3. 代码详解

(1)syntax_tree.l

该文件是Flex文件格式,定义了词法分析器的规则,和实验一不同的是头文件部分和antion部分,同时由于flex只是被bison调用,所以无需在该文件中定义main函数。

/* bison语法分析,对每条规则 按照孩子兄弟表示法建立语法结点 */
%{
#include<unistd.h>
#include<stdio.h>   
#include "syntax_tree.h"
%}

  头文件中,由于在语法分析中我们需要检测到终结符,所以需要构造语法分析树的叶节点,同时为了将结果传给Bison,需要引入.y文件编译生成的Token表头文件。

/*第三部分 操作 action 这里面的注释必须顶格一个空格*/
%%
 /*跳过空白和注释*/
{SPACE} {}
{COMMENT} {}
{EOL} {}
 /*关键字*/
{TYPE} {yylval.type_tnode=newAst("TYPE",0,yylineno);return TYPE;}
{STRUCT} {yylval.type_tnode=newAst("STRUCT",0,yylineno);return STRUCT;}
{RETURN} {yylval.type_tnode=newAst("RETURN",0,yylineno); return RETURN;}
{IF} {yylval.type_tnode=newAst("IF",0,yylineno);return IF;}
{ELSE} {yylval.type_tnode=newAst("ELSE",0,yylineno); return ELSE;}
{WHILE} {yylval.type_tnode=newAst("WHILE",0,yylineno); return WHILE;}
 /*数字类型错误*/
{INT_HEX_ERROR} {printf("INT_HEX_ERROR at line %d: charachters \"%s\"\n",yylineno,yytext);}
{INT_OCT_ERROR} {printf("INT_OCT_ERROR at line %d: charachters \"%s\"\n",yylineno,yytext);}
{INT_BIN_ERROR} {printf("INT_BIN_ERROR at line %d: charachters \"%s\"\n",yylineno,yytext);}
 /*数字类型表示*/
{INT} {yylval.type_tnode=newAst("INT",0,yylineno); return INT;}
{FLOAT} {yylval.type_tnode=newAst("FLOAT",0,yylineno); return FLOAT;}

  在action部分,我们需要将词法分析的结果返回给语法分析器,因此需要用yylval传递节点变量(该记号的语义值),同时将解析的Token类型也告诉Bison。

(2)syntax_tree.y
%union{
    tnode type_tnode;
	// 这里声明double是为了防止出现指针错误(segmentation fault)
	double d;
}

/*声明记号*/
%token <type_tnode> INT FLOAT
%token <type_tnode> TYPE STRUCT RETURN IF ELSE WHILE ID COMMENT SPACE SEMI COMMA ASSIGNOP PLUS
%token <type_tnode> MINUS STAR DIV AND OR DOT NOT LP RP LB RB LC RC AERROR RELOP EOL

%type  <type_tnode> Program ExtDefList ExtDef ExtDecList Specifire StructSpecifire 
%type  <type_tnode> OptTag Tag VarDec FunDec VarList ParamDec Compst StmtList Stmt DefList Def DecList Dec Exp Args

  在%union中我们需要定义除了int外的类型,因为在声明记号时,该记号的类型决定了我们用yyval传值的类型,而在之前的.l文件中我将叶节点(tnode)类型作为传值的内容,因此我在这里将Token的类型声明为tnode。非终结符同理,用来构造语法分析树。

/*优先级*/
/*C-minus中定义的运算符的优先级,并没有包括所有C语言的*/
%right ASSIGNOP
%left OR
%left AND
%left RELOP
%left PLUS MINUS
%left STAR DIV
%right NOT 
%left LP RP LB RB DOT

%nonassoc LOWER_THAN_ELSE 
%nonassoc ELSE
Stmt:Exp SEMI {$$=newAst("Stmt",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
	|Compst {$$=newAst("Stmt",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
	|RETURN Exp SEMI {$$=newAst("Stmt",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
    |IF LP Exp RP Stmt %prec LOWER_THAN_ELSE {$$=newAst("Stmt",5,$1,$2,$3,$4,$5);nodeList[nodeNum]=$$;nodeNum++;}
    |IF LP Exp RP Stmt ELSE Stmt {$$=newAst("Stmt",7,$1,$2,$3,$4,$5,$6,$7);nodeList[nodeNum]=$$;nodeNum++;}
	|WHILE LP Exp RP Stmt {$$=newAst("Stmt",5,$1,$2,$3,$4,$5);nodeList[nodeNum]=$$;nodeNum++;}
	;

  为了避免C-minus语法中的二义性、移进规约冲突,我定义了运算符的优先级、嵌套if-else的优先级。%left表示左结合,%right表示右结合,%nonassoc表示不可结合,解决了运算、if-else嵌套的冲突。

/*High-level Definitions*/
Program:ExtDefList {$$=newAst("Program",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
    ;
ExtDefList:ExtDef ExtDefList {$$=newAst("ExtDefList",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
	| {$$=newAst("ExtDefList",0,-1);nodeList[nodeNum]=$$;nodeNum++;}
	;
ExtDef:Specifire ExtDecList SEMI    {$$=newAst("ExtDef",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}    
	|Specifire SEMI	{$$=newAst("ExtDef",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
	|Specifire FunDec Compst	{$$=newAst("ExtDef",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
	;
ExtDecList:VarDec {$$=newAst("ExtDecList",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
	|VarDec COMMA ExtDecList {$$=newAst("ExtDecList",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
	;

  根据C-minus文件中给出的文法生成式,我对Bison文件产生式部分进行了定义。该部分可以用来进行规约操作,当进行规约时,在action部分构造新的语法分析树节点。$$表示左边的非终结符,$1、$2表示产生式右边第一个、第二个终结符/非终结符。
  为了输出最后的分析树结构,我定义了nodeNum变量表示节点数量,定义了nodeList数组存放节点指针。

(3)syntax_tree.h

该文件定义了一些结构体、全局变量、函数声明。

// 行数
extern int yylineno;
// 文本
extern char* yytext;
// 错误处理
void yyerror(char *msg);

  定义了全局变量,声明了错误处理函数。

// 抽象语法树
typedef struct treeNode{
    // 行数
    int line;
    // Token类型
    char* name;
    // fchild是第一个孩子节点,next是兄弟节点,使用孩子兄弟表示法
    struct treeNode *fchild,*next;
    // 联合体,同一时间只能保存一个成员的值,分配空间是其中最大类型的内存大小
    union{
        // id内容或者type类型(int/float)
        char* id_type;
        // 具体的数值
        int intval;
        float fltval;
    };
}* Ast,* tnode;

  抽象语法树节点结构体,当节点为TYPE、ID、INT、FLOAT时,我们需要另外存储该节点的属性值,这里用到了Union,每个节点最多只有一个联合体中的属性值会被赋值。

// 构造抽象语法树(节点)
Ast newAst(char* name,int num,...);
// 先序遍历语法树
void Preorder(Ast ast,int level);

  由于分析树采用的是孩子兄弟表示法,不清楚每个节点有几个子节点,因此采用变长参数,需要引入头文件 stdarg.h

// bison是否有词法语法错误
int hasFault;

  由于Bison在检测到错误时会调用yyerror并停止分析,而我们需要在分析完文件并确定没有错误后输出分析树,所以定义该变量,如果在分析过程中遇到错误,那么在文件扫描结束后就不会输出分析树结构。

(4)syntax_tree.c

该文件实现了syntax_tree.h中定义的函数以及主函数main

树节点构造函数

Ast newAst(char *name, int num, ...)
{
    // 生成父节点
    tnode father = (tnode)malloc(sizeof(struct treeNode));
    // 添加子节点
    tnode temp = (tnode)malloc(sizeof(struct treeNode));
    if (!father)
    {
        yyerror("create treenode error");
        exit(0);
    }
    father->name = name;

    // 参数列表,详见 stdarg.h 用法
    va_list list;
    // 初始化参数列表
    va_start(list, num);

    // 表示当前节点不是终结符号,还有子节点
    if (num > 0)
    {
        // 第一个孩子节点
        temp = va_arg(list, tnode);
        setChildTag(temp);
        father->fchild = temp;
        // 父节点行号为第一个孩子节点的行号
        father->line = temp->line;

        // 多个节点,继续添加
        if (num >= 2)
        {
            for (i = 0; i < num - 1; ++i)
            {
                temp->next = va_arg(list, tnode);
                temp = temp->next;
                // 该节点为其他节点的子节点
                setChildTag(temp);
            }
        }
    }
    else //表示当前节点是终结符(叶节点)或者空的语法单元,此时num表示行号(空单元为-1),将对应的值存入union
    {
        father->line = va_arg(list, int);
        // strcmp()==0 表示相同
        if ((!strcmp(name, "ID")) || (!strcmp(name, "TYPE")))
        {
            char *str;
            str = (char *)malloc(sizeof(char) * 40);
            strcpy(str, yytext);
            father->id_type = str;
            // father->id_type = (char*)malloc(sizeof(char)*40);
            // strcpy(father->id_type,yytext);
        }
        else if (!strcmp(name, "INT"))
        {
            father->intval = atoi(yytext);
        }
        else
        {
            father->fltval = atof(yytext);
        }
    }
    return father;
}

  Ast构造函数采用变长参数,对于构建叶节点、构建父节点能够进行不同的处理,同时判断Token类型,将TYPE、ID、INT、FLOAT的属性值存入结构体的union中。如果有多个子节点,则进行子节点的添加。兄弟孩子表示法,左子树为第一个孩子节点,右子树为该节点的兄弟节点。

语法树遍历函数

// 父节点->左子节点->右子节点....
void Preorder(Ast ast, int level)
{
    // printf(" into ");
    if (ast != NULL)
    {
        // 层级结构缩进
        for (i = 0; i < level; ++i)
        {
            printf(" ");
        }
        // printf(" rline ");
        if (ast->line != -1)
        {
            // printf(" prnt ");
            // 打印节点类型
            printf("%s", ast->name);
            // 根据不同类型打印节点数据
            if ((!strcmp(ast->name, "ID")) || (!strcmp(ast->name, "TYPE")))
            {
                printf(": %s", ast->id_type);
            }
            else if (!strcmp(ast->name, "INT"))
            {
                printf(": %d", ast->intval);
            }
            else if (!strcmp(ast->name, "FLOAT"))
            {
                printf(": %f", ast->fltval);
            }
            else
            {
                // 非叶节点打印行号
                printf("(%d)", ast->line);
            }
        }
        printf("\n");
        // printf(" fchild ");
        Preorder(ast->fchild, level + 1);
        // printf(" next ");
        Preorder(ast->next, level);
    }
    // printf(" return ");
}

  采用先序遍历,先遍历根节点,再遍历左子树、右子树,并且对不同的节点类型进行不同内容的输出。

设置节点类型(根节点/其他节点)

// 设置节点打印状态 该节点为子节点
void setChildTag(tnode node)
{
    int i;
    for (i = 0; i < nodeNum; i++)
    {
        if (nodeList[i] == node)
        {
            nodeIsChild[i] = 1;
        }
    }
}

  在C语言中,比较两个指针是否相等时,比较的是指针指向的内存地址是否相同,因此可以用指针数组来存放节点,并且进行比较。在构造函数中,对于添加的子节点,设置该节点标签为子节点类型。

主函数

// 主函数 扫描文件并且分析
// 为bison会自己调用yylex(),所以在main函数中不需要再调用它了
// bison使用yyparse()进行语法分析,所以需要我们在main函数中调用yyparse()和yyrestart()
int main(int argc, char **argv)
{
    int j;
    printf("start analysis\n");
    if (argc < 2)
    {
        return 1;
    }
    for (i = 1; i < argc; i++)
    {
        // 初始化节点记录列表
        nodeNum = 0;
        memset(nodeList, 0, sizeof(tnode) * 5000);
        memset(nodeIsChild, 0, sizeof(int) * 5000);
        hasFault = 0;

        FILE *f = fopen(argv[i], "r");
        if (!f)
        {
            perror(argv[i]);
            return 1;
        }
        yyrestart(f);
        yyparse();
        fclose(f);

        // 遍历所有非子节点的节点
        if (hasFault)
            continue;
        for (j = 0; j < nodeNum; j++)
        {
            if (nodeIsChild[j] != 1)
            {
                Preorder(nodeList[j], 0);
            }
        }
    }
}

  在主函数中体现了节点存储、寻找根节点的过程。如果词法语法分析没有错误的话,就进行树结构输出。对于节点数组中唯一的非子节点,就一定是语法分析树的根节点,对该节点进行先序遍历输出。

三、测试结果

1. 工程文件以及编译过程

在这里插入图片描述
  syntax_tree.c syntax_tree.h 是语法分析树的C文件,包含主函数
  base_false.cmm base_true.cmm input.c是测试文件
  parser 是gcc编译的可执行文件

在这里插入图片描述
(1) syntax_tree.y 是bison文件
  用 bison -d filename 编译
  syntax_tree.tab.c syntax_tree.tab.h 是编译bison生成的
(2) syntax_tree.l 是flex文件
  用 flex filename 编译
  lex.yy.c 是编译flex生成的
(3) 总体编译过程
  bison -d syntax_tree.y
  flex syntax_tree.l
  gcc syntax_tree.tab.c syntax_tree.c lex.yy.c -lfl -ly -o parser

2. 执行测试程序

(1)base_true.cmm
struct Complex
{
  float real, image;
};

int main()
{
  struct Complex x;
  x.image = 1.5;
}

执行结果如下:
在这里插入图片描述
  没有词法、语法错误,输出了完整的语法分析树,并且能够识别出数字的值。

(2)Base_false.cmm
struct Complex
{
  float real, image;
};

int main()
{
  struct Complex x;
  float a[10] = 1.5;
  int i = 100;
  x.image = ~i;

  if (a[1][2] == 0) i =1 else i =0;
}

在这里插入图片描述
  能够识别词法错误、语法错误,不输出语法分析树。

(3)input.c
int inc()
{
    int i = 1;
    i = i+1;
}

分析结果:
在这里插入图片描述
  对比可知分析结果正确

3. 遇到的问题及解决

  (1) 在实现语法树输出时,我最开始的实现方法是在.y文件的action部分,当规约到Program类型时就进行先序遍历输出,但是我发现这样只能输出函数的语法树。后来采用定义全局变量数组存放所有节点,最后判断节点类型只遍历根节点
  (2) 在.y文件union中我一开始只定义了tnode类型的别名,但是这样会出现段错误segmentation fault,我查阅了相关资料也不清楚原因,但是当我增加了另一个变量声明double后这个错误就消失了,个人理解是因为tnode是指针类型,占用内存较小,但是却被赋予了较大的内存地址,而加了double后union开辟的内存空间是按照较大的变量类型(double类型)来计算的,所以避免了段错误。


本文为原创,如有错误欢迎指正,感谢阅读。
下文是完整源码,建议阅读并理解上文部分,下文只作为参考。


四、源码放送

1. syntax_tree.l

/*
	按照C-Tokens文件中要求定义
    对终结符建立叶子结点,返回Token
	19-10-26
*/


/*第一部分 头文件和变量*/
%{
	#include <stdlib.h>
	#include <stdio.h>
    #include "syntax_tree.h"
    #include "syntax_tree.tab.h"
%}

/*flex属性,记录符号所在行号*/
%option yylineno

/*第二部分 定义正则表达式*/
/*十进制*/
INT_DEC 0|[1-9][0-9]*
/*十六进制*/
INT_HEX 0[xX][a-fA-F0-9]+
/*八进制*/
INT_OCT 0[1-7][0-7]*
/*二进制*/
INT_BIN 0[bB][01]+
/*INT类型汇总*/
INT {INT_HEX}|{INT_DEC}|{INT_OCT}|{INT_BIN}|{INT_HEX_ERROR}|{INT_OCT_ERROR}|{INT_BIN_ERROR}
/*浮点数-科学计数法*/
FLOAT ((([0-9]+\.[0-9]*)|([0-9]*\.[0-9]+)|INT)[Ee][-+]?[0-9]+)|({INT}\.[0-9])
/*词法分析输出错误,但是语法分析当做INT进行处理*/
/*十六进制错误*/
INT_HEX_ERROR 0[xX][a-fA-F0-9]*[g-zG-Z]+[a-fA-F0-9]*
/*八进制错误*/
INT_OCT_ERROR 0[0-7]*[89]+[0-7]*
/*二进制错误*/
INT_BIN_ERROR 0[bB][01]*[2-9]+[01]*

/*标识符*/
ID [a-z_A-Z][a-z_A-Z0-9]*

/*关键字*/
STRUCT struct
RETURN return
IF if
ELSE else
WHILE while
TYPE int|float

/*符号*/
SEMI  ;
COMMA ,
ASSIGNOP  =
PLUS  \+
MINUS \-
STAR  \*
DIV   \/
AND   &&
DOT   \.
NOT   \!
LP    \(
RP    \)
LB    \[
RB    \]
LC    \{
RC    \}
RELOP >|<|>=|<=|==|!=

/*注释*/
COMMENT ("//".*)|("/*"([*]*(([^*/])+([/])*)*)*"*/")
/*空白符*/
SPACE [ \f\r\t\v]+
/*换行*/
EOL \n
/*未定义字符*/
AERROR .

/*第三部分 操作 action 这里面的注释必须顶格一个空格*/
%%
 /*跳过空白和注释*/
{SPACE} {}
{COMMENT} {}
{EOL} {}
 /*关键字*/
{TYPE} {yylval.type_tnode=newAst("TYPE",0,yylineno);return TYPE;}
{STRUCT} {yylval.type_tnode=newAst("STRUCT",0,yylineno);return STRUCT;}
{RETURN} {yylval.type_tnode=newAst("RETURN",0,yylineno); return RETURN;}
{IF} {yylval.type_tnode=newAst("IF",0,yylineno);return IF;}
{ELSE} {yylval.type_tnode=newAst("ELSE",0,yylineno); return ELSE;}
{WHILE} {yylval.type_tnode=newAst("WHILE",0,yylineno); return WHILE;}
 /*数字类型错误*/
{INT_HEX_ERROR} {printf("INT_HEX_ERROR at line %d: charachters \"%s\"\n",yylineno,yytext);}
{INT_OCT_ERROR} {printf("INT_OCT_ERROR at line %d: charachters \"%s\"\n",yylineno,yytext);}
{INT_BIN_ERROR} {printf("INT_BIN_ERROR at line %d: charachters \"%s\"\n",yylineno,yytext);}
 /*数字类型表示*/
{INT} {yylval.type_tnode=newAst("INT",0,yylineno); return INT;}
{FLOAT} {yylval.type_tnode=newAst("FLOAT",0,yylineno); return FLOAT;}
 /*符号*/
{SEMI} {yylval.type_tnode=newAst("SEMI",0,yylineno); return SEMI;}
{COMMA} {yylval.type_tnode=newAst("COMMA",0,yylineno); return COMMA;}
{ASSIGNOP} {yylval.type_tnode=newAst("ASSIGNOP",0,yylineno); return ASSIGNOP;}
{PLUS} {yylval.type_tnode=newAst("PLUS",0,yylineno); return PLUS;}
{MINUS} {yylval.type_tnode=newAst("MINUS",0,yylineno); return MINUS;}
{STAR} {yylval.type_tnode=newAst("STAR",0,yylineno); return STAR;}
{DIV} {yylval.type_tnode=newAst("DIV",0,yylineno); return DIV;}
{AND} {yylval.type_tnode=newAst("AND",0,yylineno); return AND;}
{DOT} {yylval.type_tnode=newAst("DOT",0,yylineno); return DOT;}
{NOT} {yylval.type_tnode=newAst("NOT",0,yylineno); return NOT;}
{LP} {yylval.type_tnode=newAst("LP",0,yylineno); return LP;}
{RP} {yylval.type_tnode=newAst("RP",0,yylineno); return RP;}
{LB} {yylval.type_tnode=newAst("LB",0,yylineno); return LB;}
{RB} {yylval.type_tnode=newAst("RB",0,yylineno); return RB;}
{LC} {yylval.type_tnode=newAst("LC",0,yylineno); return LC;}
{RC} {yylval.type_tnode=newAst("RC",0,yylineno); return RC;}
{RELOP} {yylval.type_tnode=newAst("RELOP",0,yylineno); return RELOP;}
 /*标识符*/
{ID} {yylval.type_tnode=newAst("ID",0,yylineno); return ID;}
 /*错误*/
{AERROR} {
	hasFault=1;
	printf("Error type A at line %d: Mystirious charachter '%s'\n",yylineno,yytext);
}
%%

/*第四部分 函数 function*/
int yywrap()
{
	/*此函数必须由用户提供,或者声明 %option noyywrap
	当词法分析程序遇到文件结尾时,它调用例程yywrap()来找出下一步要做什么
	如果返回0,扫描程序继续扫描,如果返回1,扫描程序就返回报告文件结尾*/
    return 1;
}

2. syntax_tree.y

/*
*bison语法分析,对每条规则 按照孩子兄弟表示法建立语法结点
*/
%{
#include<unistd.h>
#include<stdio.h>   
#include "syntax_tree.h"
%}

%union{
    tnode type_tnode;
	// 这里声明double是为了防止出现指针错误(segmentation fault)
	double d;
}

/*声明记号*/
%token <type_tnode> INT FLOAT
%token <type_tnode> TYPE STRUCT RETURN IF ELSE WHILE ID COMMENT SPACE SEMI COMMA ASSIGNOP PLUS
%token <type_tnode> MINUS STAR DIV AND OR DOT NOT LP RP LB RB LC RC AERROR RELOP EOL

%type  <type_tnode> Program ExtDefList ExtDef ExtDecList Specifire StructSpecifire 
%type  <type_tnode> OptTag Tag VarDec FunDec VarList ParamDec Compst StmtList Stmt DefList Def DecList Dec Exp Args

/*优先级*/
/*C-minus中定义的运算符的优先级,并没有包括所有C语言的*/
%right ASSIGNOP
%left OR
%left AND
%left RELOP
%left PLUS MINUS
%left STAR DIV
%right NOT 
%left LP RP LB RB DOT

%nonassoc LOWER_THAN_ELSE 
%nonassoc ELSE

/*产生式*/
/*$$表示左表达式 ${num}表示右边的第几个表达式*/
%%
/*High-level Definitions*/
Program:ExtDefList {$$=newAst("Program",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
    ;
ExtDefList:ExtDef ExtDefList {$$=newAst("ExtDefList",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
	| {$$=newAst("ExtDefList",0,-1);nodeList[nodeNum]=$$;nodeNum++;}
	;
ExtDef:Specifire ExtDecList SEMI    {$$=newAst("ExtDef",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}    
	|Specifire SEMI	{$$=newAst("ExtDef",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
	|Specifire FunDec Compst	{$$=newAst("ExtDef",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
	;
ExtDecList:VarDec {$$=newAst("ExtDecList",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
	|VarDec COMMA ExtDecList {$$=newAst("ExtDecList",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
	;
/*Specifire*/
Specifire:TYPE {$$=newAst("Specifire",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
	|StructSpecifire {$$=newAst("Specifire",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
	;
StructSpecifire:STRUCT OptTag LC DefList RC {$$=newAst("StructSpecifire",5,$1,$2,$3,$4,$5);nodeList[nodeNum]=$$;nodeNum++;}
	|STRUCT Tag {$$=newAst("StructSpecifire",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
	;
OptTag:ID {$$=newAst("OptTag",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
	|{$$=newAst("OptTag",0,-1);nodeList[nodeNum]=$$;nodeNum++;}
	;
Tag:ID {$$=newAst("Tag",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
	;
/*Declarators*/
VarDec:ID {$$=newAst("VarDec",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
	|VarDec LB INT RB {$$=newAst("VarDec",4,$1,$2,$3,$4);nodeList[nodeNum]=$$;nodeNum++;}
	;
FunDec:ID LP VarList RP {$$=newAst("FunDec",4,$1,$2,$3,$4);nodeList[nodeNum]=$$;nodeNum++;}
	|ID LP RP {$$=newAst("FunDec",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
	;
VarList:ParamDec COMMA VarList {$$=newAst("VarList",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
	|ParamDec {$$=newAst("VarList",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
	;
ParamDec:Specifire VarDec {$$=newAst("ParamDec",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
    ;

/*Statement*/
Compst:LC DefList StmtList RC {$$=newAst("Compst",4,$1,$2,$3,$4);nodeList[nodeNum]=$$;nodeNum++;}
	;
StmtList:Stmt StmtList{$$=newAst("StmtList",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
	| {$$=newAst("StmtList",0,-1);nodeList[nodeNum]=$$;nodeNum++;}
	;
Stmt:Exp SEMI {$$=newAst("Stmt",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
	|Compst {$$=newAst("Stmt",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
	|RETURN Exp SEMI {$$=newAst("Stmt",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
    |IF LP Exp RP Stmt %prec LOWER_THAN_ELSE {$$=newAst("Stmt",5,$1,$2,$3,$4,$5);nodeList[nodeNum]=$$;nodeNum++;}
    |IF LP Exp RP Stmt ELSE Stmt {$$=newAst("Stmt",7,$1,$2,$3,$4,$5,$6,$7);nodeList[nodeNum]=$$;nodeNum++;}
	|WHILE LP Exp RP Stmt {$$=newAst("Stmt",5,$1,$2,$3,$4,$5);nodeList[nodeNum]=$$;nodeNum++;}
	;
/*Local Definitions*/
DefList:Def DefList{$$=newAst("DefList",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
	| {$$=newAst("DefList",0,-1);nodeList[nodeNum]=$$;nodeNum++;}
	;
Def:Specifire DecList SEMI {$$=newAst("Def",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
	;
DecList:Dec {$$=newAst("DecList",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
	|Dec COMMA DecList {$$=newAst("DecList",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
	;
Dec:VarDec {$$=newAst("Dec",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
	|VarDec ASSIGNOP Exp {$$=newAst("Dec",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
	;
/*Expressions*/
Exp:Exp ASSIGNOP Exp{$$=newAst("Exp",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
        |Exp AND Exp{$$=newAst("Exp",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
        |Exp OR Exp{$$=newAst("Exp",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
        |Exp RELOP Exp{$$=newAst("Exp",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
        |Exp PLUS Exp{$$=newAst("Exp",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
        |Exp MINUS Exp{$$=newAst("Exp",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
        |Exp STAR Exp{$$=newAst("Exp",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
        |Exp DIV Exp{$$=newAst("Exp",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
        |LP Exp RP{$$=newAst("Exp",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
        |MINUS Exp {$$=newAst("Exp",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
        |NOT Exp {$$=newAst("Exp",2,$1,$2);nodeList[nodeNum]=$$;nodeNum++;}
        |ID LP Args RP {$$=newAst("Exp",4,$1,$2,$3,$4);nodeList[nodeNum]=$$;nodeNum++;}
        |ID LP RP {$$=newAst("Exp",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
        |Exp LB Exp RB {$$=newAst("Exp",4,$1,$2,$3,$4);nodeList[nodeNum]=$$;nodeNum++;}
        |Exp DOT ID {$$=newAst("Exp",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
        |ID {$$=newAst("Exp",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
        |INT {$$=newAst("Exp",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
        |FLOAT{$$=newAst("Exp",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
        ;
Args:Exp COMMA Args {$$=newAst("Args",3,$1,$2,$3);nodeList[nodeNum]=$$;nodeNum++;}
        |Exp {$$=newAst("Args",1,$1);nodeList[nodeNum]=$$;nodeNum++;}
        ;
%%

3. syntax_tree.h

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>  // 变长参数函数 头文件

// 行数
extern int yylineno;
// 文本
extern char* yytext;
// 错误处理
void yyerror(char *msg);

// 抽象语法树
typedef struct treeNode{
    // 行数
    int line;
    // Token类型
    char* name;
    // fchild是第一个孩子节点,next是兄弟节点,使用孩子兄弟表示法
    struct treeNode *fchild,*next;
    // 联合体,同一时间只能保存一个成员的值,分配空间是其中最大类型的内存大小
    union{
        // id内容或者type类型(int/float)
        char* id_type;
        // 具体的数值
        int intval;
        float fltval;
    };
}* Ast,* tnode;

// 构造抽象语法树(节点)
Ast newAst(char* name,int num,...);

// 先序遍历语法树
void Preorder(Ast ast,int level);

// 所有节点数量
int nodeNum;
// 存放所有节点
tnode nodeList[5000];
int nodeIsChild[5000];
// 设置节点打印状态
void setChildTag(tnode node);

// bison是否有词法语法错误
int hasFault;

4. syntax_tree.c

#include "syntax_tree.h"

// 用于遍历
int i;

Ast newAst(char *name, int num, ...)
{
    // 生成父节点
    tnode father = (tnode)malloc(sizeof(struct treeNode));
    // 添加子节点
    tnode temp = (tnode)malloc(sizeof(struct treeNode));
    if (!father)
    {
        yyerror("create treenode error");
        exit(0);
    }
    father->name = name;

    // 参数列表,详见 stdarg.h 用法
    va_list list;
    // 初始化参数列表
    va_start(list, num);

    // 表示当前节点不是终结符号,还有子节点
    if (num > 0)
    {
        // 第一个孩子节点
        temp = va_arg(list, tnode);
        setChildTag(temp);
        father->fchild = temp;
        // 父节点行号为第一个孩子节点的行号
        father->line = temp->line;

        // 多个节点,继续添加
        if (num >= 2)
        {
            for (i = 0; i < num - 1; ++i)
            {
                temp->next = va_arg(list, tnode);
                temp = temp->next;
                // 该节点为其他节点的子节点
                setChildTag(temp);
            }
        }
    }
    else //表示当前节点是终结符(叶节点)或者空的语法单元,此时num表示行号(空单元为-1),将对应的值存入union
    {
        father->line = va_arg(list, int);
        // strcmp()==0 表示相同
        if ((!strcmp(name, "ID")) || (!strcmp(name, "TYPE")))
        {
            char *str;
            str = (char *)malloc(sizeof(char) * 40);
            strcpy(str, yytext);
            father->id_type = str;
            // father->id_type = (char*)malloc(sizeof(char)*40);
            // strcpy(father->id_type,yytext);
        }
        else if (!strcmp(name, "INT"))
        {
            father->intval = atoi(yytext);
        }
        else
        {
            father->fltval = atof(yytext);
        }
    }
    return father;
}

// 父节点->左子节点->右子节点....
void Preorder(Ast ast, int level)
{
    // printf(" into ");
    if (ast != NULL)
    {
        // 层级结构缩进
        for (i = 0; i < level; ++i)
        {
            printf(" ");
        }
        // printf(" rline ");
        if (ast->line != -1)
        {
            // printf(" prnt ");
            // 打印节点类型
            printf("%s", ast->name);
            // 根据不同类型打印节点数据
            if ((!strcmp(ast->name, "ID")) || (!strcmp(ast->name, "TYPE")))
            {
                printf(": %s", ast->id_type);
            }
            else if (!strcmp(ast->name, "INT"))
            {
                printf(": %d", ast->intval);
            }
            else if (!strcmp(ast->name, "FLOAT"))
            {
                printf(": %f", ast->fltval);
            }
            else
            {
                // 非叶节点打印行号
                printf("(%d)", ast->line);
            }
        }
        printf("\n");
        // printf(" fchild ");
        Preorder(ast->fchild, level + 1);
        // printf(" next ");
        Preorder(ast->next, level);
    }
    // printf(" return ");
}

// 错误处理
void yyerror(char *msg)
{
    hasFault = 1;
    fprintf(stderr, "Error type B at Line %d, %s ,before %s\n", yylineno, msg, yytext);
}

// 设置节点打印状态 该节点为子节点
void setChildTag(tnode node)
{
    int i;
    for (i = 0; i < nodeNum; i++)
    {
        if (nodeList[i] == node)
        {
            nodeIsChild[i] = 1;
        }
    }
}

// 主函数 扫描文件并且分析
// 为bison会自己调用yylex(),所以在main函数中不需要再调用它了
// bison使用yyparse()进行语法分析,所以需要我们在main函数中调用yyparse()和yyrestart()
int main(int argc, char **argv)
{
    int j;
    printf("start analysis\n");
    if (argc < 2)
    {
        return 1;
    }
    for (i = 1; i < argc; i++)
    {
        // 初始化节点记录列表
        nodeNum = 0;
        memset(nodeList, 0, sizeof(tnode) * 5000);
        memset(nodeIsChild, 0, sizeof(int) * 5000);
        hasFault = 0;

        FILE *f = fopen(argv[i], "r");
        if (!f)
        {
            perror(argv[i]);
            return 1;
        }
        yyrestart(f);
        yyparse();
        fclose(f);

        // 遍历所有非子节点的节点
        if (hasFault)
            continue;
        for (j = 0; j < nodeNum; j++)
        {
            if (nodeIsChild[j] != 1)
            {
                Preorder(nodeList[j], 0);
            }
        }
    }
}
  • 11
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
编译原理是计算机科学中的一门重要课程,涉及了很多与编译器设计和实现相关的概念和技术。而C-minus语法分析则是编译原理中的一个重要部分,它使用了flexbison这两个工具来实现。接下来我将详细介绍C-minus语法分析的相关内容。 C-minus是一种简化版的C语言,它具有类似于C语言的语法和语义。语法分析编译器的第二个阶段,主要负责通过解析输入的源代码来构建抽象语法树。在C-minus语法分析中,我们使用flexbison这两个工具来实现词法分析和语法分析flex是一个用于生成词法分析器的工具,它通过定义一系列正则表达式规则来识别源代码中的各种词法单元,如关键字、标识符、常量等。在C-minus语法分析中,我们可以使用flex来识别源代码中的词法单元,并将它们传递给bison进行后续的语法分析bison是一个用于生成语法分析器的工具,它通过定义一系列文法规则来分析词法单元之间的语法关系,同时生成一个由这些规则构成的抽象语法树。在C-minus语法分析中,我们可以使用bison来定义C-minus语言的文法规则,并将其与词法单元进行匹配,从而生成抽象语法树。 在C-minus语法分析中,我们需要定义C-minus语言的文法规则,如声明语句、函数定义、循环语句等。通过使用flexbison这两个工具,我们可以将这些规则转化为相应的词法和语法规则,并实现一个完整的C-minus语法分析器。 总而言之,C-minus语法分析编译原理中的一个重要环节,通过使用flexbison这两个工具,我们可以实现一个功能完善的C-minus语法分析器,从而为后续的语义分析和代码生成打下基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值