编译原理Lab1-用FLEX构造C-Minus-f词法分析器

HNU编译原理lab1实验--根据cminux-f的词法补全lexical_analyer.l文件,完成词法分析器。

本文没有添加任何图片,但是以复制输出的形式展现出来了实验结果。

实验要求:

根据cminux-f的此法补全lexical_analyer.l文件,完成词法分析器,能够输出识别出的token,type,line(刚出现的行数),pos_start(该行的开始位置),post_end(结束位置 不包含)

例如:

文本输入:

int a;

则识别结果应为:

int     280     1       2       5
a       285     1       6       7
;       270     1       7       8

cminus-f词法

C MINUS是C语言的一个子集,该语言的语法在《编译原理与实践》第九章附录中有详细的介绍。而cminus-f则是在C MINUS上追加了浮点操作。

  1. 关键字else if int return void while float

  2. 专用符号+ - * / < <= > >= == != = ; , ( ) [ ] { } /* */

  3. 标识符ID和整数NUM,通过下列正则表达式定义:

    letter = a|...|z|A|...|Z
    digit = 0|...|9
    ID = letter+
    INTEGER = digit+
    FLOAT = (digit+. | digit*.digit+)

  4. 注释用/*...*/表示,可以超过一行。注释不能嵌套。

    /.../

  • 注:[, ], 和 [] 是三种不同的token。[]用于声明数组类型,[]中间不得有空格。
    • a[]应被识别为两个token: a[]
    • a[1]应被识别为四个token: a, [, 1, ]

实验难点

git相关操作:常用的命令在实验文档中

将实验仓库克隆到本地:打开本地的工作目录,在命令行中输入
`git clone https://gitee.com/你的gitee用户名/cminus_compiler-2022-fall.git`
打开本地的工作目录,在命令行中输入
git add *
git commit -m "注释语句"
然后push到仓库
git push

实验环境配置

sudo apt-get install llvm bison flex

输入:flex --version和bison --version

FLEX工具的简单使用

首先,FLEX从输入文件*.lex或者stdio读取词法扫描器的规范,从而生成C代码源文件lex.yy.c。然后,编译lex.yy.c并与-lfl库链接,以生成可执行的a.out。最后,a.out分析其加入的输入流,将其转换为一系列token。

举例:

%{
//在%{和%}中的代码会被原样照抄到生成的lex.yy.c文件的开头,您可以在这里书写声明与定义
#include <string.h>
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;
}

lex中的字符规定:

格式 含义
a 字符
"a" 元字符
\a 转义
a* a的零次或者多次重复
a+ a的一次或者多次重复
a? 一个可选的a
a b
(a) a本身
[abc] 字符abc中的任意一个
[a-d] 字符abcd的任意一个
{xxxxx} 名字xxx表示的正则表达式
. 除了新行之外的任意一个字符

实验设计

找到Token符号对应的字符

cminux_compiler-2023-fall/include/lexical_analyzer.h

有定义cimux_token_type(附录)

对应正则表达式

根据cminus-f词法

1. 关键字
else if int return void while float

2. 专用符号
+ - * / < <= > >= == != = ; , ( ) [ ] { } /* */

3. 标识符ID和整数NUM,通过下列正则表达式定义:
letter = a|...|z|A|...|Z
digit = 0|...|9
ID = letter+
INTEGER = digit+
FLOAT = (digit+. | digit*.digit+)

4. 注释用`/*...*/`表示,可以超过一行。注释不能嵌套。
/*...*/
- 注:`[`, `]`, 和 `[]` 是三种不同的token。`[]`用于声明数组类型,`[]`中间不得有空格。
    - `a[]`应被识别为两个token: `a`、`[]`
    - `a[1]`应被识别为四个token: `a`, `[`, `1`, `]`

写出对应的正则表达式和指定匹配对应的动作

C minus的词法单元规则有:

关键字:else if int return void while float

专用符号:`+ - * / < <= > >= == != = ; , ( ) [ ] { } /* */``

标识符ID和整数NUM,通过下列正则表达式定义:

letter = a|...|z|A|...|Z
digit = 0|...|9
ID = letter+
INTEGER = digit+
FLOAT = (digit+. | digit*.digit+)

注释用/*...*/表示,可以超过一行。注释不能嵌套。

此部分用于定义C Minus的词法单元的规则,模式采用正则表达式表示,注意当词法单元的pattern中包含特殊字符时,需要使用转义字符\。动作使用C语言描述,确定每个Token在每行的开始位置和结束位置,并且返回该词法单元类型。该返回值为yylex()的返回值。

动作分为两步:第一步,更新lines、pos_start、post_end。第二步:将识别结果token返回,return。

运算:

cpp 复制代码
\+ {pos_start=pos_end;pos_end=pos_start+1;return ADD;}
\- {pos_start=pos_end;pos_end=pos_start+1;return SUB;}
\* {pos_start=pos_end;pos_end=pos_start+1;return MUL;}
\/ {pos_start=pos_end;pos_end=pos_start+1;return DIV;}
\< {pos_start=pos_end;pos_end=pos_start+1;return LT;}
"<=" {pos_start=pos_end;pos_end=pos_start+2;return LTE;}
\> {pos_start=pos_end;pos_end=pos_start+1;return GT;}
">=" {pos_start=pos_end;pos_end=pos_start+2;return GTE;}
"==" {pos_start=pos_end;pos_end=pos_start+2;return EQ;}
"!=" {pos_start=pos_end;pos_end=pos_start+2;return NEQ;}
\= {pos_start=pos_end;pos_end=pos_start+1;return ASSIN;}

符号:

cpp 复制代码
\; {pos_start=pos_end;pos_end=pos_start+1;return SEMICOLON;}
\, {pos_start=pos_end;pos_end=pos_start+1;return COMMA;}
\( {pos_start=pos_end;pos_end=pos_start+1;return LPARENTHESE;}
\) {pos_start=pos_end;pos_end=pos_start+1;return RPARENTHESE;}
\[ {pos_start=pos_end;pos_end=pos_start+1;return LBRACKET;}
\] {pos_start=pos_end;pos_end=pos_start+1;return RBRACKET;}
\{ {pos_start=pos_end;pos_end=pos_start+1;return LBRACE;}
\} {pos_start=pos_end;pos_end=pos_start+1;return RBRACE;}

关键字:

cpp 复制代码
else {pos_start=pos_end;pos_end=pos_start+4;return ELSE;}
if {pos_start=pos_end;pos_end=pos_start+2;return IF;}
int {pos_start=pos_end;pos_end=pos_start+3;return INT;}
float {pos_start=pos_end;pos_end=pos_start+5;return FLOAT;}
return {pos_start=pos_end;pos_end=pos_start+6;return RETURN;}
void {pos_start=pos_end;pos_end=pos_start+4;return VOID;}
while {pos_start=pos_end;pos_end=pos_start+5;return WHILE;}

标识符和整数NUM

cpp 复制代码
[a-zA-Z]+ {pos_start=pos_end;pos_end=pos_start+strlen(yytext);return IDENTIFIER;}
[0-9]+ {pos_start=pos_end;pos_end=pos_start+strlen(yytext);return INTEGER;}
[0-9]*\.[0-9]+ {pos_start=pos_end;pos_end=pos_start+strlen(yytext);return FLOATPOINT;}
"[]" {pos_start=pos_end;pos_end=pos_start+2;return ARRAY;}
[a-zA-Z] {pos_start=pos_end;pos_end=pos_start+1;return LETTER;}
[0-9]+\. {pos_start=pos_end;pos_end=pos_start+strlen(yytext);return FLOATPOINT;}

其他的

  • 当词法分析器扫描到换行符时(Windows下为\r\n,Linux下为\n,Mac下为\r),行数lines自增,pos_startpos_end更新
  • 由于flex生成的词法分析器采用最长匹配策略,且注释/**/包含正则的通配符,正则规范较为复杂。当识别到一个注释时,需要考虑词法单元开始位置和结束位置变化,且多行注释要修改lines.
  • 错误的词法单元 ,当扫描到错误的词法单元,仅返回ERROR
cpp 复制代码
\n {return EOL;} #换行
\/\*([^\*]|(\*)*[^\*\/])*(\*)*\*\/ {return COMMENT;} #注释
" " {return BLANK;} #空格
\t {return BLANK;} # 空格
. {pos_start=pos_end;pos_end=pos_start+strlen(yytext);return ERROR;} #错误

最终的添加

cpp 复制代码
/******************TODO*********************/
 /****请在此补全所有flex的模式与动作  start******/

 //STUDENT TO DO

\+ {pos_start=pos_end;pos_end=pos_start+1;return ADD;}
\- {pos_start=pos_end;pos_end=pos_start+1;return SUB;}
\* {pos_start=pos_end;pos_end=pos_start+1;return MUL;}
\/ {pos_start=pos_end;pos_end=pos_start+1;return DIV;}
\< {pos_start=pos_end;pos_end=pos_start+1;return LT;}
"<=" {pos_start=pos_end;pos_end=pos_start+2;return LTE;}
\> {pos_start=pos_end;pos_end=pos_start+1;return GT;}
">=" {pos_start=pos_end;pos_end=pos_start+2;return GTE;}
"==" {pos_start=pos_end;pos_end=pos_start+2;return EQ;}
"!=" {pos_start=pos_end;pos_end=pos_start+2;return NEQ;}
\= {pos_start=pos_end;pos_end=pos_start+1;return ASSIN;}
\; {pos_start=pos_end;pos_end=pos_start+1;return SEMICOLON;}
\, {pos_start=pos_end;pos_end=pos_start+1;return COMMA;}
\( {pos_start=pos_end;pos_end=pos_start+1;return LPARENTHESE;}
\) {pos_start=pos_end;pos_end=pos_start+1;return RPARENTHESE;}
\[ {pos_start=pos_end;pos_end=pos_start+1;return LBRACKET;}
\] {pos_start=pos_end;pos_end=pos_start+1;return RBRACKET;}
\{ {pos_start=pos_end;pos_end=pos_start+1;return LBRACE;}
\} {pos_start=pos_end;pos_end=pos_start+1;return RBRACE;}
else {pos_start=pos_end;pos_end=pos_start+4;return ELSE;}
if {pos_start=pos_end;pos_end=pos_start+2;return IF;}
int {pos_start=pos_end;pos_end=pos_start+3;return INT;}
float {pos_start=pos_end;pos_end=pos_start+5;return FLOAT;}
return {pos_start=pos_end;pos_end=pos_start+6;return RETURN;}
void {pos_start=pos_end;pos_end=pos_start+4;return VOID;}
while {pos_start=pos_end;pos_end=pos_start+5;return WHILE;}
[a-zA-Z]+ {pos_start=pos_end;pos_end=pos_start+strlen(yytext);return IDENTIFIER;}
[0-9]+ {pos_start=pos_end;pos_end=pos_start+strlen(yytext);return INTEGER;}
[0-9]*\.[0-9]+ {pos_start=pos_end;pos_end=pos_start+strlen(yytext);return FLOATPOINT;}
"[]" {pos_start=pos_end;pos_end=pos_start+2;return ARRAY;}
[a-zA-Z] {pos_start=pos_end;pos_end=pos_start+1;return LETTER;}
[0-9]+\. {pos_start=pos_end;pos_end=pos_start+strlen(yytext);return FLOATPOINT;}
\n {return EOL;}
\/\*([^\*]|(\*)*[^\*\/])*(\*)*\*\/ {return COMMENT;}
" " {return BLANK;}
\t {return BLANK;}
. {pos_start=pos_end;pos_end=pos_start+strlen(yytext);return ERROR;}
 /****请在此补全所有flex的模式与动作  end******/

和补充C语言代码

注释可以分为多行,所以在识别到注释的时候要进行额外的分析,识别到换行符\n的时候,要lines+1,重置pos_end.

cpp 复制代码
           case COMMENT:
                //STUDENT TO DO
                {
                pos_start=pos_end;
                pos_end=pos_start+2;
                int i=2;
      			while(yytext[i]!='*' || yytext[i+1]!='/')
      			{  			
      				if(yytext[i]=='\n')
      				{
						lines=lines+1;
						pos_end=1;
					}
					else
						pos_end=pos_end+1;
					i=i+1;
				}
				pos_end=pos_end+2;
				break;
               }
            case BLANK:
                //STUDENT TO DO
                {
                	pos_start=pos_end;
                	pos_end=pos_start+1;
                	break;
                }
            case EOL:
                //STUDENT TO DO
                {
                	lines+=1;
                	pos_end=1;
                	break;
                }

实验结果验证

实验结果

根据实验指导书上的流程输入命令并且得到反馈结果

sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall$ mkdir build
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall$ cd build
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ cmake ../
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found FLEX: /usr/bin/flex (found version "2.6.4") 
-- Found BISON: /usr/bin/bison (found version "3.5.1") 
-- Found LLVM 10.0.0
-- Using LLVMConfig.cmake in: /usr/lib/llvm-10/cmake
-- Configuring done
-- Generating done
-- Build files have been written to: /home/sunny2004/lab1/cminus_compiler-2023-fall/build
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ make lexer
[ 20%] [FLEX][lex] Building scanner with flex 2.6.4
lexical_analyzer.l:60: warning, 无法匹配规则
Scanning dependencies of target flex
[ 40%] Building C object src/lexer/CMakeFiles/flex.dir/lex.yy.c.o
lexical_analyzer.l: In function 'analyzer':
lexical_analyzer.l:92:5: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
lexical_analyzer.l: At top level:
/home/sunny2004/lab1/cminus_compiler-2023-fall/build/src/lexer/lex.yy.c:1320:17: warning: 'yyunput' defined but not used [-Wunused-function]
     static void yyunput (int c, char * yy_bp )
                 ^
/home/sunny2004/lab1/cminus_compiler-2023-fall/build/src/lexer/lex.yy.c:1363:16: warning: 'input' defined but not used [-Wunused-function]
     static int input  (void)
                ^
[ 60%] Linking C static library ../../libflex.a
[ 60%] Built target flex
Scanning dependencies of target lexer
[ 80%] Building C object tests/lab1/CMakeFiles/lexer.dir/main.c.o
[100%] Linking C executable ../../lexer
[100%] Built target lexer
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall/build$ cd ..
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall$ ./build/lexer
usage: lexer input_file output_file
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall$ ./build/lexer ./tests/lab1/testcase/1.cminus out
[START]: Read from: ./tests/lab1/testcase/1.cminus
[END]: Analysis completed.
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall$ head -n 5 out
int	280	1	1	4
gcd	285	1	5	8
(	272	1	9	10
int	280	1	10	13
u	285	1	14	15
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall$ python3 ./tests/lab1/test_lexer.py
Find 6 files
[START]: Read from: ./tests/lab1/testcase/3.cminus
[END]: Analysis completed.
[START]: Read from: ./tests/lab1/testcase/2.cminus
[END]: Analysis completed.
[START]: Read from: ./tests/lab1/testcase/6.cminus
[END]: Analysis completed.
[START]: Read from: ./tests/lab1/testcase/1.cminus
[END]: Analysis completed.
[START]: Read from: ./tests/lab1/testcase/5.cminus
[END]: Analysis completed.
[START]: Read from: ./tests/lab1/testcase/4.cminus
[END]: Analysis completed.
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall$ diff ./tests/lab1/token ./tests/lab1/TA_token
sunny2004@sunny2004-VirtualBox:~/lab1/cminus_compiler-2023-fall$ 

如果正确的话,diff不会返回任何输出,如果返回了,就出错了

gitee上传

git commit -m "lab1-result"

如果是第一次提交,Ubuntu会告诉你这样:

请告诉我您是谁,运行
git config --global user.email "you@example.com"
git config --global user.name "your Name"

来自设置您账号的缺省身份标识。

如果仅在本地仓库设置身份标识,则省略 --global参数

这个时候你就运行git config那两行命令之后再运行git commit -m "lab1-result"就可以了

然后:git push 上传工作到gitee仓库(这一部分忘记复制了,实验指导书里写的很详细,就按照那个来就行)

实验反馈

学习和巩固了正则表达式

熟悉了gitee的操作

一路磕磕绊绊,调试,赶在验收之前完成了,编译原理好难┭┮﹏┭┮

附录1:cmius_token_type

typedef num cminus_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

//ID和NUM
IDENTIFIER = 285,	变量名,例如a,low,high
INTEGER = 286, 		整数,例如10,1
FLOATPOINT = 287,	浮点数,例如11.1
ARRAY = 288,		数组,例如[]
LETTER = 289,		单个字母,例如a,z	
    
//others
EOL = 290,			换行符,\n或\0	
COMMENT = 291,		注释
BLANK = 292,		空格
ERROR = 258			错误
} Token;

typedef struct{
	char text[256];
    int token;
    int lines;
    int pos_start;
    int pos_end;
} Token_Node;
相关推荐
&岁月不待人&12 分钟前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
StayInLove15 分钟前
G1垃圾回收器日志详解
java·开发语言
TeYiToKu17 分钟前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm
无尽的大道23 分钟前
Java字符串深度解析:String的实现、常量池与性能优化
java·开发语言·性能优化
互联网打工人no124 分钟前
每日一题——第一百二十四题
c语言
爱吃生蚝的于勒26 分钟前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~30 分钟前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
binishuaio36 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE38 分钟前
【Java SE】StringBuffer
java·开发语言
就是有点傻42 分钟前
WPF中的依赖属性
开发语言·wpf