实验要求
本次实验需要各位同学根据cminux-f
的词法补全lexical_analyer.l文件,完成词法分析器,能够输出识别出的token
,type
,line(刚出现的行数)
,pos_start(该行开始位置)
,pos_end(结束的位置,不包含)
。
实验设计
lexical_analyer.l文件由三部分组成:定义部分,识别规则部分,辅助函数部分,每个部分由%%分隔开。
定义部分
我在定义部分写了相关头文件的引用和全局变量 lines,pos_start,pos_end 的定义,这些是C语言,所以需要用%{ %}括起来:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3T9AVBqF-1645607971927)(figs/image-20211030114207127.png)]
定义部分还有所有token的正则表达式定义,我根据.h文件知道了需要识别的token有五个部分:运算,符号,关键字,ID和NUM,others。其中前三个部分的正则表达式比较简单,后两个部分的正则表达式容易漏掉情况,比如:
标识符IDENTIFIER,它的第一个字符不能为数字,所以不能简单的写成一个-z_A-Z0-9的闭包,需要在闭包的前面加上[a-z_A-Z];
INTEGER 要分两种情况,一种是第一位为0,一种是第一位不为0;
FLOATPOINT的正则表达式中的**. 前要加转义符\** ,要不然不能识别出浮点数的小数点;浮点数要考虑小数点后面没有数字的情况;
注释COMMENT需要在/* 和*/间可以识别 “\\\” 和 “***” 这样的字符串;
BLANK的正则表达式除了识别空格外,还应该可以识别制表符\t;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hU1JE5kx-1645607971928)(figs/image-20211030115008766.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gU5bkeFB-1645607971929)(figs/image-20211030115938383.png)]
识别规则部分
这部分用来编写词法分析器要执行的操作,即识别到词法单元后需要干什么,它每一条的结构如下:
{词法单元} { 进行的操作;}
因为要求将识别到的token的type打印下来,所以当识别到词法单元时需要返回对应的type值(该值在**lexical_analyzer.h**文件中已经定义)即 return (词法单元),例如:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4vNq0fXd-1645607971930)(figs/image-20211030141737015.png)]
而识别到token后对全局变量 lines,pos_start,pos_end的操作我放到辅助函数里面。
注意最后一条规则为判断ERROR,它是 . {操作}形式,表示当前面的都不匹配时就判断为ERROR:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D5Ek85LH-1645607971931)(figs/image-20211030144052857.png)]
辅助函数部分
在这部分里实现了void analyzer函数,它对识别到的token进行了处理。我需要补充完函数里面的while循环,我的编写如下:
while(token = yylex()){
switch(token){
case COMMENT:
//STUDENT TO DO
for(int i=0;i<strlen(yytext);i++){
if(yytext[i]=='\n'){pos_start=1; pos_end=1;lines+=1;}
else {pos_end+=1;}
}
break;
case BLANK:
//STUDENT TO DO
pos_start=pos_end; pos_end+= strlen(yytext);
break;
case EOL:
//STUDENT TO DO
++lines; pos_start=1; pos_end=1;
break;
case ERROR:
pos_start=pos_end; pos_end+= strlen(yytext);
printf("[ERR]: unable to analysize %s at %d line, from %d to %d\n", yytext, lines, pos_start, pos_end);
break;
default :
pos_start=pos_end; pos_end+= strlen(yytext);
printf("%s %d %d %d %d\n",yytext,token,lines,pos_start, pos_end);
...
每次循环从yylex()中获取到token值,该值就是token对应的type。
然后通过switch语句对不同的token做处理:
- 当为 COMMENT时,通过for循环遍历获取到的字符串(字符串通过yytext获取,它是char*类型,指向识别规则中的一个正规式匹配的单词的首字符),当遇到\n时需要将行数加1,同时记录开始和结束位置的变量重置为1;
- 当为BLANK时,需要将开始位置变为上一次的结束位置,同时结束位置需要加上yytext(获取到的字符串)的长度;
- 当为EOL,即换行时,需要将行数加1,同时记录开始和结束位置的变量重置为1;
- 当为ERROR时,也要更新开始位置和结束位置,同时返回错误信息;
- 其它情况的token则是需要输出相应信息: yytext, token, lines, pos_start, pos_end;
实验结果验证
执行下面命令进行验证:
python3 ./tests/lab1/test_lexer.py
diff ./tests/lab1/token ./tests/lab1/TA_token
下面为我自己设计的测试样例:
第一行和第四行用于测试注释里面包含//和*时的识别情况,以及/**/y/**/中y是否会被识别成注释里面的内容;
第五行的下划线和第六行的!没有对应的正则表达式,用于测试error情况;
/**/
void main(void){
int x;
x = input(); /******/y/***/ y=input();
int res_9tado ;
if(x!y)output(resultado);
}
其结果如下:
第一行的注释成功识别(注释不打印出来),第四行的识别到两个注释/******/ ,/***/,两个注释中间的y被识别为标识符;
第五行的_和第六行的!被识别为ERROR,然后输出ERROR信息,可以看到在ERROR情况下,pos_end和pos_begin也会改变。
实验反馈
- 通过编写正则表达式我学到了语法糖的含义和使用,比如:
. | 匹配除换行符以外的任意字符 |
---|---|
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
[^x] | 匹配除了x以外的任意字符 |
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
以及学会使用一部分LEX函数,比如:
yytext | char*类型,指向识别规则中的一个正规式匹配的单词的首字符 |
---|---|
yylex() | 从该函数开始分析,可由lex自动生成 |