c语言注释的正规文法 编译原理,编译原理1:词法分析与语法分析

编译原理实验1:词法分析与语法分析

1 简介

本实验的目标是,对一份输入的源代码,进行词法分析与语法分析,若有错误则输出相关信息,否则输出语法树。

词法分析的思路:编写正则表达式匹配词法单元;将正则表达式转化为不确定的有穷自动机(NFA);将不确定的有穷自动机转化为确定的有穷自动机。

语法分析的思路:从开始状态开始,利用有限状态机理论,根据语言的文法展开式,进行状态分析。

如果从零开始实现完整的词法分析与语法分析,一个学期恐怕是做不完编译器的。所幸,我们可以借助构造词法分析器的工具flex与构造语法分析器的工具bison来实现。(软院选择不借助工具,但是他们一个学期只做了语法分析和词法分析。编译器已经是轮子了,在轮子的轮子上耗费太多精力,感觉不太值得。)

1.1 完成的功能

必做功能

识别词法错误:出现任何未定义字符与任何不符合词法单元定义的字符

识别语法错误

选做功能

识别8进制与16进制数

识别指数形式的浮点数

识别“//”与“/*…*/”形式的注释

1.2 编译步骤

bison -d ex1.y

flex ex1.l

gcc main.c ex1.tab.c tree.c -lfl -ly -o parser

1.3 程序结构

main.c

程序入口,读入文件,调用bison内部函数进行语法分析

tree.h与tree.c

定义了多叉树的节点类型;定义了插入节点与打印语法树的函数。

lexx.yy.c

由flex编译ex1.l得到,进行词法分析

ex1.tab.h与ex1.tab.c

由bison编译ex1.y得到,进行语法分析

2 关键功能与实现方法

2.1 词法分析

2.1.1 综述

词法分析部分依据实验参考书中附录A c–语言文法,A.1.1 Tokens,按flex规则编写正则表达式匹配各种词法单元。

2.1.2 普通tokens

普通的tokens正则表达式较为简单,不再赘述。这里主要讲识别tokens之后的动作。

一个普通的规则如下:

";" {yylval.type_Node_ptr=(struct Node *)malloc(sizeof(struct Node));

yylval.type_Node_ptr->lineNum=yylineno;yylval.type_Node_ptr->type=1;return SEMI;}

匹配一个token之后的动作包括:

* 新建多叉树节点

* 记录当前token的行号

* 指定当前的token类型,tree.h中定义了48个type与数字1~48分别对应。

* 返回当前词法单元

其中,yylval是flex的内部变量,它的类型是我在bison代码中定义的union类型。

%union{

TreeNode* type_Node_ptr;

}

需要注意的是,不能直接将strut Node *赋值给yylval,需要赋值给yylval.type_Node_ptr,否则编译不过。

2.1.3 8进制数/16进制数与科学计数法

匹配10进制/8进制/16进制数的正则表达式:

(0|[1-9]{digit}{0,31})|(0[xX](0|[1-9a-fA-F]{digit_16}*))|(0(0|[1-7][0-7]*))

匹配float与科学计数法的正则表达式:

((0|[1-9]{digit}*)\.{digit}*)|([0-9]*\.[0-9]*[eE][+-]?[0-9]+)

匹配token后,要将它们的数值存储进结点中。

首先将char* 转换为int或float。此时,常用的atoi()与atof()功能就不够了。

查阅资料,找到了下面两个更加灵活的转换函数:

long int strtol(const char *nptr,char **endptr,int base);

float strtof(const char *nptr, char **endptr);

其中,strtol的参数base表示进制,设置base为0,将默认为10进制,并自动识别0x开头的16进制,0开头的8进制。

2.1.4 注释

两种风格的注释本来都是比较难的部分。

不过//风格注释的处理,作为input函数的例子出现在了实验指导书P20,不再赘述。

/* */形式的注释

第一反应是写出简单的非贪婪匹配\/\*(.|\n)*?\*\/但这是无效的。

这样的写法无法识别p11的例1.10的嵌套注释的错误,这与c–的语法要求不一致。

显然,这里代表非贪婪模式的*?没有被识别。

查阅资料发现,flex的正则表达式是不支持非贪婪模式的,这里必须使用比较复杂的方法来解决。

基于一个简单的思路:"/*"(非(*/))*"*/"即可解决这一问题。

难点在于,非(*/) 的表示。

这里用到了一个小技巧:将其分解为两种情况:

1. 任何非*字符

2. *后面连接一个非/字符

于是,非(*/)可以表示为[^*]|(\*[^"/"]

完整的正则表达式可写成:\/\*([^*]|(\*[^"/"]))*\*\/

2.1.5 其它

2.1.5.1 isError与error type A

在定义部分声明变量int isError=0;

在所有tokens的最后,添加如下代码:

. {

isError=1;

printf("Error type A at line %d:Mysterious charasters \'%s\'\n",yylineno,yytext);

}

进行错误类型A的处理,并对isError进行标记。

这样就不会遇到error后还打印语法树的信息了。

2.1.5.2 YY_USER_ACTION

YY_USER_ACTION宏表示在执行每一个动作之前需要被执行的一段代码,默认为空,这里依据实验指导书P31,使其维护位置信息。

需要注意两点:

#define YY_USER_ACTION后面如果换行,需要在后面加上\。实验指导书的排版具有误导性。

bison有一个选项%locations,如果在bison中使用了@n访问位置信息,这个选项会被默认开启。但如果没有@n,就要手动开启这个选项才能使用内置变量yylloc。

2.1.5.3 空格,不可见字符,tab的处理

由于这些字符不应当进入语法分析器,因此不作处理。

但完全忽视的话,它们会被当成A类型error。

因此,需要在规则部分对这些字符进行匹配,但不进行处理。

2.1.5.4 token的顺序

因为flex优先匹配更长/靠前的token,例如ID写到TYPE前面,int/float将被匹配为ID。因此,需要适当调整token的顺序,保证每个token都能被匹配。

2.2 语法分析

2.2.1 综述

词法分析部分依据实验参考书中附录A c–语言文法,A.1.2~A.1.7,按上下文无关文法编写规则匹配各种语法单元。

2.2.2 普通语法单元

一个普通的语法单元结构如下:

ExtDefList:{$$=createNode(0);$$->lineNum=@$.first_line;$$->type=29;}

|ExtDef ExtDefList{$$=createNode(2,$1,$2);$$->lineNum=@$.first_line;$$->type=29;}

;

将构成它的语法单元作为子节点,构造新的结点。并记录行号以及结点类型。

2.2.3 结点类型YYSTYPE

每个终结符或非终结符的类型由宏YYSTYPE定义。

我们使用bison内置的机制对YYSTYPE进行定义:

在定义部分添加如下代码

%union{ TreeNode* type_Node_ptr; }

其实用union主要是为了让不同的语法单元具有不同的属性值类型,在这里我全部定义为TreeNode*,本来没必要使用union。但我试图将YYSTYPE直接定义为TreeNode*时,flex中的内置变量yylval出现错误。

2.2.4 错误处理

在bison的用户函数部分重定义函数yyerror:

yyerror(char* str){

isError=1;

printf("Error type B at line %d:%s\n",yylineno,str);

}

按要求输出Error type B的相关信息。

将isError置为1。

通过查阅bison manul,

4 Parser C-Language Interface

4.3 The Error Reporting Function yyerror

了解到,在定义部分添加%error-verbose,bison将提供更加详细的错误信息。

添加此选项前后,输出的错误信息分别是:

Error type B at line *:syntax error

Error type B at line *:syntax error, unexpected ****

阅读源码发现,在一定条件下,bison将提供更多信息,如missing ****。

2.2.5 打印语法树

在开始符号program对应的语义动作中,判断isError,如果为0,则调用printTree()打印语法树。

Program:ExtDefList {$$=createNode(1,$1);$$->lineNum=@$.first_line;$$->type=28;if(!isError) printTree($$);}

2.3 语法树

2.3.1 定义的数据结构

结点的结构体中定义了以下属性:行号,子结点数目,结点类型,数值,字符串属性(ID,TYPE,RELOP需要用到),以及指向子结点的指针。

二维数组Node_types用于表明结点类型序号与字符的关系,具体定义如下:

char Node_types[50][20]={"","SEMI","COMMA","ASSIGNOP","PLUS","MINUS",

"STAR","DIV","AND","OR","DOT","NOT","LP","RP","LB","RB","LC","RC",

"RELOP","IF","ELSE","WHILE","STRUCT","type","TYPE","INT","FLOAT","ID",

"Program","ExtDefList","ExtDef","ExtDecList","Specifier","StructSpecifier",

"OptTag","Tag","VarDec","Fundec","VarList","ParamDec","CompSt","StmtList",

"Stmt","DefList","Def","DecList","Dec","Exp","Args"};

共48个类型,其中1~27为终结符,28~48为非终结符,这样便于判断。

2.3.2 创建新结点

创建新结点:createNode(int childNum,...)

这是一个可变参数的函数,在#include 后使用。这样在创建新结点的时候更加灵活。

2.3.3 打印语法树-逻辑

定义全局变量tabcnt,初始化为0。用来指示打印空格的数量。

打印语法树的逻辑如下:

* 先判断当前结点,如果是非终结符,且子结点数量为0,直接返回。

否则,打印2倍tabCnt数量的空格。

* 打印当前结点类型

* 判断,如果是非终结符,打印行号

* 判断,如果是RELOP,TYPE,ID,打印字符信息

* 判断,如果是INT或FLOAT,打印数值

* 打印换行

* tabcnt++

* 依次打印子结点信息

* tabcnt--

3 结语

通过本次实验,更加深刻地理解了词法分析与语法分析的过程。

提高了c语言应用能力。

4 参考文献

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值