From 5b2c429fd5df7e147542cd7ddf38add3d5e73d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?202108010313-=E7=8E=8B=E6=98=A5=E5=AE=87?= <3329146162@qq.com> Date: Fri, 17 Nov 2023 16:08:46 +0000 Subject: [PATCH] update Reports/lab1/report.md. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 202108010313-王春宇 <3329146162@qq.com> --- Reports/lab1/report.md | 363 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 360 insertions(+), 3 deletions(-) diff --git a/Reports/lab1/report.md b/Reports/lab1/report.md index cd75c3c..19261a4 100755 --- a/Reports/lab1/report.md +++ b/Reports/lab1/report.md @@ -1,8 +1,365 @@ # lab1实验报告 -学号 姓名 + ## 实验要求 + +根据cminux-f的词法补全lexical_analyer.l文件,完成词法分析器,能够输出识别出的token,type ,line(刚出现的行数),pos_start(该行开始位置),pos_end(结束的位置,不包含) + +文本输入: + +``` +int a; +``` + +则识别结果应为: + +``` +int 280 1 2 5 +a 285 1 6 7 +; 270 1 7 8 +``` + +对于部分token,我们只需要进行过滤,即只需被识别,但是不应该被输出到分析结果中。因为这些token对程序运行不起到任何作用。 + ## 实验难点 + +实验环境的搭建以及配置,flex编译环境的配置等。 +学习并掌握cminus-f语法,提供其词法的正则表达式。 +学会利用FLEX并使用正则表达式来编写正确的词法分析器程序,能够识别各种词语 + ## 实验设计 + +### 环境搭建: + +#### 安装flex + +首先需要安装flex: + +``` +sudo apt install flex +``` + +#### 安装cmake: + +``` +sudo apt install cmake +``` + +#### 安装bison: + +``` +sudo apt install bison +``` + +#### 安装LLVM: + +``` +sudo apt-get install llvm +``` + +#### 安装ZLIB: + +``` +sudo apt install zlib1g-dev +``` + +### 1.实验要求能够识别出所有的输入token,但并不是所有的内容都需要识别。具体需要识别的token在 lexical_analyzer.h 定义了,打开lexical_analyzer.h 并查看具体要识别的token以及其对应的字符和含义,符号的编号和具体内容如下: + +//运算 +ADD = 259, /* 加号:+ */ +SUB = 260, /* 减号:- */ +MUL = 261, /* 乘号:* */ +DIV = 262, /* 除法:/ */ +LT = 263, /* 小于:< */ +LTE = 264, /* 小于等于:<= */ +GT = 265, /* 大于:> */ +GTE = 266, /* 大于等于:>= */ +EQ = 267, /* 相等:== */ +NEQ = 268, /* 不相等:!= */ +ASSIN = 269,/* 单个等于号:= */ + +//符号 +SEMICOLON = 270, /* 分号:; */ +COMMA = 271, /* 逗号:, */ +LPARENTHESE = 272, /* 左括号:( */ +RPARENTHESE = 273, /* 右括号:) */ +LBRACKET = 274, /* 左中括号:[ */ +RBRACKET = 275, /* 右中括号:] */ +LBRACE = 276, /* 左大括号:{ */ +RBRACE = 277, /* 右大括号:} */ + +//关键字 +ELSE = 278, /* else */ +IF = 279, /* if */ +INT = 280, /* int */ +FLOAT = 281, /* float */ +RETURN = 282, /* return */ +VOID = 283, /* void */ +WHILE = 284, /* while */ + +//ID和NUM +IDENTIFIER = 285, /* 变量名,例如a,b */ +INTEGER = 286, /* 整数,例如1,2 */ +FLOATPOINT = 287, /* 浮点数,例如1.1,1.2 */ +ARRAY = 288, /* 数组,例如[] */ +LETTER = 289, /* 单个字母,例如a,z */ + +//others +EOL = 290, /* 换行符,\n或\0 */ +COMMENT = 291, /* 注释 */ +BLANK = 292, /* 空格 */ +ERROR = 258 /* 错误 */ + + + +### 2.知道了要识别的token的具体含义,就可以写出其对应的正则表达式。参考 C-Minus文法 来写出各个要识别的token的正则表达式。 + +因此,token的type和对应的正则表达式如下,其中: + +对于运算符,符号等token的正则表达式即为其对应的英文状态下的半角符号。 + +``` ++ ; +``` + +对于关键字等token,在c-minus-f中的语法中的正则表达式为其对应的小写状态。 + +``` +else if +``` + +接着ID和NUM的正则表达式比较复杂,使用语法糖可以有效来简化构造。 + +``` +[a-zA-Z]+ [0-9]+\.[0-9]+ +``` + +特别的是:在示例2中,在代码中用到了"x=72.",也就是说希望可以在整数后加小数点,所以在float类型中,有两种情况: + +"72."和"72.55",即 + +``` +[0-9]+\.|[0-9]+\.[0-9]+ +``` + +对于变量名,可以是a-z或A到Z之间的一个或多个字母的组合,从a-z和A-Z中选一个的正则表达式为[a-zA-Z],选一个或多个的话加一个+号即可,即[a-zA-Z]+。 + +对于整数,就是从0-9里选一个或多个数,则正则表达式为[0-9]+。 + +对于浮点数,有一个问题就是算不算正负数,这里我觉得应该是不算的,如果是负的浮点数前面的符号应该当做‘-’来匹配。要注意的一点就是.号前要加\转义符,否则会将.当做任意字符来匹配。小数点前应该有1或多个数组,不然比如.123这样也不叫浮点数。 + +数组这里也有一个坑,就是[和]和[]是三个东西(题目也给出了),如果要匹配数组[],则两个框都要加转义符从而可以匹配。 + +其他符号 + +``` +[\n]+ \/\*([^\*]|(\*)*[^\*\/])*(\*)*\*\/ +``` + +换行符的正则表达式就是\n,由于可以实现一到多行的换行,所以要在后面加个+(实现一个或多个换行)。 + +注释符的正则表达式是最复杂的,题目要求注释直接不能有嵌套,可能比如类似/* * */* * /这样。首先注释的开头和结尾为/* 和* */,由于都是特殊字符,所以都需要加上转义符用于匹配。在中间部分,想法就是如果开始注释符 /*之后的注释内容中要么就不包含/*,要么就如果包含了一个或多个 * 的话,其后面的注释内容不能跟着* /(因为最末尾还有着*/作为注释的结束符作为匹配)。所以中间注释内容部分,要么不含 * ,可以用[ ^ \ *]表示,\是转义符,代表着可以匹配除 * 以外的所有内容。如果包含 * ,可以是一个或多个,那么可以用 * 的闭包表示,然后其后接的符号不能为* / + +![https://blog.csdn.net/qq_45795586/article/details/122309981](https://img-blog.csdnimg.cn/591388e16e654796a01462c9bda5cd28.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASE5V6I2S54Of55im6YeO,size_20,color_FFFFFF,t_70,g_se,x_16) + +空白符的正则表达式可以有很多种,使用[ \f\n\r\t\v]作为空白符的正则表达式,除此以外" "也能当做空白符的正则表达式 + + +对于error错误,源文档已经用.来表示错误。 + +token type 正则表达式 +ADD 259 + +SUB 260 - +MUL 261 * +DIV 262 / +LT 263 < +LTE 264 <= +GT 265 > +GTE 266 >= +EQ 267 == +NEQ 268 != +ASSIN 269 = +SEMICOLON 270 ; +COMMA 271 , +LPARENTHESE 272 ( +RPARENTHESE 273 ) +LBRACKET 274 [ +RBRACKET 275 ] +LBRACE 276 { +RBRACE 277 } +ELSE 278 else +IF 279 if +INT 280 int +FLOAT 281 float +RETURN 282 return +VOID 283 void +WHILE 284 while +IDENTIFIER 285 [a-zA-Z]+ +INTEGER 286 [0-9]+ +FLOATPOINT 287 [0-9]+.[0-9]+ +ARRAY 288 \[\] +LETTER 289 [a-zA-Z] +EOL 290 [\n]+ +COMMENT 291 `/*([^*] +BLANK 292 [ \f\n\r\t\v] +ERROR 258 . + +### 3.写出指定模式匹配时对应的动作 + +实验文档的基础知识中给出了如何写一个简单的单词数量统计的程序 + +``` +%{ +//在%{和%}中的代码会被原样照抄到生成的lex.yy.c文件的开头,您可以在这里书写声明与定义 +#include +int chars = 0; +int words = 0; +%} + +%% + /*你可以在这里使用你熟悉的正则表达式来编写模式*/ + /*你可以用C代码来指定模式匹配时对应的动作*/ + /*yytext指针指向本次匹配的输入文本*/ + /*左部分([a-zA-Z]+)为要匹配的正则表达式, + 右部分({ chars += strlen(yytext);words++;})为匹配到该正则表达式后执行的动作*/ +[a-zA-Z]+ { chars += strlen(yytext);words++;} + + +. {} + /*对其他所有字符,不做处理,继续执行*/ + +%% + +int main(int argc, char **argv){ + //yylex()是flex提供的词法分析例程,默认读取stdin + yylex(); + printf("look, I find %d words of %d chars\n", words, chars); + return 0; +} +``` + + +可以看到,在第二部分要求我们使用熟悉的正则表达式来编写模式,这也是题目要求补全lexical_analyer.l文件中的一部分内容,那么可以仿照上面的程序来编写出对应的动作 + +yytext指针指向本次匹配的输入文本,我们在左部分写要匹配的正则表达式,右部分为匹配到该正则表达式后执行的动作。 除了一些特殊的字符要在analyzer函数中实现外,其余部分的实现都是差不多的。题目要求能够输出识别出的token,type ,line(刚出现的行数),pos_start(该行开始位置),pos_end(结束的位置,不包含) 。首先将开始位置和结束位置设置成一样,代表当前识别的字符从上一识别完的字符的末尾开始。接着设置pos_end+=strlen(yytext) ,strlen(yytext)为这次识别到的长度,然后返回识别出的token即可。 + +代码如下: + +```c + /******** 运算 ********/ +\+ {pos_start=pos_end;pos_end+=strlen(yytext);return ADD;} +\- {pos_start=pos_end;pos_end+=strlen(yytext);return SUB;} +\* {pos_start=pos_end;pos_end+=strlen(yytext);return MUL;} +\/ {pos_start=pos_end;pos_end+=strlen(yytext);return DIV;} +\< {pos_start=pos_end;pos_end+=strlen(yytext);return LT;} +\<\= {pos_start=pos_end;pos_end+=strlen(yytext);return LTE;} +\> {pos_start=pos_end;pos_end+=strlen(yytext);return GT;} +\>\= {pos_start=pos_end;pos_end+=strlen(yytext);return GTE;} +\=\= {pos_start=pos_end;pos_end+=strlen(yytext);return EQ;} +\!\= {pos_start=pos_end;pos_end+=strlen(yytext);return NEQ;} +\= {pos_start=pos_end;pos_end+=strlen(yytext);return ASSIN;} + + /******** 符号 ********/ +\; {pos_start=pos_end;pos_end+=strlen(yytext);return SEMICOLON;} +\, {pos_start=pos_end;pos_end+=strlen(yytext);return COMMA;} +\( {pos_start=pos_end;pos_end+=strlen(yytext);return LPARENTHESE;} +\) {pos_start=pos_end;pos_end+=strlen(yytext);return RPARENTHESE;} +\[ {pos_start=pos_end;pos_end+=strlen(yytext);return LBRACKET;} +\] {pos_start=pos_end;pos_end+=strlen(yytext);return RBRACKET;} +\{ {pos_start=pos_end;pos_end+=strlen(yytext);return LBRACE;} +\} {pos_start=pos_end;pos_end+=strlen(yytext);return RBRACE;} + + /******** 关键字 ********/ +else {pos_start=pos_end;pos_end+=strlen(yytext);return ELSE;} +if {pos_start=pos_end;pos_end+=strlen(yytext);return IF;} +int {pos_start=pos_end;pos_end+=strlen(yytext);return INT;} +float {pos_start=pos_end;pos_end+=strlen(yytext);return FLOAT;} +return {pos_start=pos_end;pos_end+=strlen(yytext);return RETURN;} +void {pos_start=pos_end;pos_end+=strlen(yytext);return VOID;} +while {pos_start=pos_end;pos_end+=strlen(yytext);return WHILE;} + + /******** ID和NUM ********/ +[a-zA-Z]+ {pos_start=pos_end;pos_end+=strlen(yytext);return IDENTIFIER;} +[0-9]+ {pos_start=pos_end;pos_end+=strlen(yytext);return INTEGER;} +[0-9]+\.|[0-9]+\.[0-9]+ {pos_start=pos_end;pos_end+=strlen(yytext);return FLOATPOINT;} +\[\] {pos_start=pos_end;pos_end+=strlen(yytext);return ARRAY;} +[a-zA-Z] {pos_start=pos_end;pos_end+=strlen(yytext);return LETTER;} + + /******** others ********/ +[\n]+ {pos_start = 1;pos_end = 1;lines+=strlen(yytext);return EOL;} +\/\*([^\*]|(\*)*[^\*\/])*(\*)*\*\/ {return COMMENT;} +[ \f\n\r\t\v] {pos_start = pos_end;pos_end+=strlen(yytext);return BLANK;} +. {return ERROR;} +``` + +4.补充 analyzer函数 + +在上一步已经写了关于空格BLANK以及换行EOL后执行的动作,所以这里只用写注释COMMENT的动作即可,其他留空就行。因为在步骤2时已经return ,所以这里只需要写关于pos_start,pos_end以及lines的相关变化 + +对于注释COMMENT,先获yytext的长度为len,然后通过while循环判断注释中是否存在换行符,即判断yytext[i]是否为换行符,如果是换行\n的话,将pos_start和pos_end设置为1,lines加1;否则pos_end++;当循环结束则break; + +``` +case COMMENT: + //STUDENT TO DO + for(int i = 0;i < strlen(yytext);i++) + { + pos_end++; + if( yytext[i] == '\n' ) + { + lines += 1;pos_end = 1; + } + } + break; +``` + ## 实验结果验证 -请提供部分自己的测试样例 -## 实验反馈 \ No newline at end of file + +1.创建build文件夹,配置编译环境, 执行指令make lexer运行代码,开始编译,如图所示,编译成功 + +![image-20231110200813075](C:\Users\王春宇\AppData\Roaming\Typora\typora-user-images\image-20231110200813075.png) + +2.执行指令python3 ./tests/lab1/test_lexer.py来查看是否通过6个测试样例 。可以看到,通过了6个测试样例 + +![image-20231110200833259](C:\Users\王春宇\AppData\Roaming\Typora\typora-user-images\image-20231110200833259.png) + +3.验证结果正确性。输入diff ./tests/lab1/token ./tests/lab1/TA_token 将自己的生成结果和助教提供的TA_token进行比较。如果结果完全正确,则没有任何输出结果。如果有不一致,则会汇报具体哪个文件哪部分不一致。如图所示,是正确的。 + +![image-20231110200842986](C:\Users\王春宇\AppData\Roaming\Typora\typora-user-images\image-20231110200842986.png) + +4.自行设计testcase进行测试。 + +代码如下 + +``` ++-*/<<=>>====!=;()[]{} +if else float return +int a[10]; +int a[]; +void while + +0.123 +/*/*test*/*/ +``` + +运行结果如下./build/lexer ./tests/lab1/testcase/test.cminus out: + +![image-20231117235354938](C:\Users\王春宇\AppData\Roaming\Typora\typora-user-images\image-20231117235354938.png) + +![image-20231117235846373](C:\Users\王春宇\AppData\Roaming\Typora\typora-user-images\image-20231117235846373.png) + +这个测试样例几乎把所有的token都测试了一遍,主要测试的地方如下: + +1. 对于第一行中的<=>>====部分,按照从左往右识别的顺序应该是<=,>,>=,==,=。通过测试结果可以看到可以正确识别出来 +2. 第二个要测试的地方是关于数组。 [, ], 和 [] 是三种不同的token。[]用于声明数组类型,[]中间不得有空格。 可以看到也能正确识别出来。 +3. 第三个测试的地方是注释的嵌套。对于/*/*test*/*/这个语句,如果支持嵌套注释的话那么/*test*/是外层注释的注释内容,识别结果应该不会任何内容。但是测试结果显示最后识别出来了*和/,说明不支持嵌套注释。 +4. 对于其他的部分测试结果也能正确查询出来,一些关键字也不会识别成变量名等。有一个问题就是关于浮点型,对于1.或0.这样的数也算作浮点型,这是有点疑问的地方。 + +## 实验反馈 + +1.首先对于正则表达式的各种写法有了更深的理解。比如对于数字的正则表达式,就有[0-9]+和\d等几种不同的写法。 + +2.对于一些比较复杂但有有规律的正则表达式,使用语法糖可以有效的简化结构,比如英文字符,正则表达式为a|b|c|d……|z,使用语法糖可以[a-z]直接表示,十分简便。 + +3.学习了如何简单使用FLEX,学会了如何利用FLEX生成一个生成词法分析器,补充完了一个识别token的程序,从而实现对输入的词法进行识别和分析的功能,并能准确给出token所在行,开始位置和结束位置。 + + + -- Gitee