cminus_compiler-2021-fall
前言
实验文档来源湖南大学编译原理课程,实验报告是笔者写的。
Lab1 实验文档
0. 基础知识
在本次实验中我们讲重点用到FLEX
和以C-
为基础改编的cminus-f
语言。这里对其进行简单介绍。
0.1 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
,]
0.2 FLEX简单使用
FLEX
是一个生成词法分析器的工具。利用FLEX
,我们只需提供词法的正则表达式,就可自动生成对应的C代码。整个流程如下图:
首先,FLEX
从输入文件*.lex
或者stdio
读取词法扫描器的规范,从而生成C代码源文件lex.yy.c
。然后,编译lex.yy.c
并与-lfl
库链接,以生成可执行的a.out
。最后,a.out
分析其输入流,将其转换为一系列token。
我们以一个简单的单词数量统计的程序wc.l为例:
%{
//在%{和%}中的代码会被原样照抄到生成的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;
}
使用Flex生成lex.yy.c
[TA@TA example]$ flex wc.l
[TA@TA example]$ gcc lex.yy.c -lfl
[TA@TA example]$ ./a.out
hello world
^D
look, I find 2 words of 10 chars
[TA@TA example]$
注: 在以stdin为输入时,需要按下ctrl+D以退出
至此,你已经成功使用Flex完成了一个简单的分析器!
1. 实验要求
本次实验需要各位同学根据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参考lexical_analyzer.h
特别说明对于部分token,我们只需要进行过滤,即只需被识别,但是不应该被输出到分析结果中。因为这些token对程序运行不起到任何作用。
注意,你所需修改的文件应仅有[lexical_analyer.l]…/…/src/lexer/lexical_analyzer.l)。关于
FLEX
用法上文已经进行简短的介绍,更高阶的用法请参考百度、谷歌和官方说明。
1.1 目录结构
整个repo
的结构如下
.
├── CMakeLists.txt
├── Documentations
│ └── lab1
│ └── README.md <- lab1实验文档说明
├── README.md
├── Reports
│ └── lab1
│ └── report.md <- lab1所需提交的实验报告(你需要在此提交实验报告)
├── include <- 实验所需的头文件
│ └── lexical_analyzer.h
├── src <- 源代码
│ └── lexer
│ ├── CMakeLists.txt
│ └── lexical_analyzer.l <- flex文件,lab1所需完善的文件
└── tests <- 测试文件
└── lab1
├── CMakeLists.txt
├── main.c <- lab1的main文件
├── test_lexer.py
├── testcase <- 助教提供的测试样例
└── TA_token <- 助教提供的关于测试样例的词法分析结果
1.2 编译、运行和验证
lab1
的代码大部分由C
和python
构成,使用cmake
进行编译。
-
编译
# 进入workspace $ cd cminus_compiler-2021-fall # 创建build文件夹,配置编译环境 $ mkdir build $ cd build $ cmake ../ # 开始编译 # 如果你只需要编译lab 1,请使用 make lexer $ make
编译成功将在
${WORKSPACE}/build/
下生成lexer
命令 -
运行
$ cd cminus_compiler-2021-fall # 运行lexer命令 $ ./build/lexer usage: lexer input_file output_file # 我们可以简单运行下 lexer命令,但是由于此时未完成实验,当然输出错误结果 $ ./build/lexer ./tests/lab1/testcase/1.cminus out [START]: Read from: ./tests/lab1/testcase/1.cminus [ERR]: unable to analysize i at 1 line, from 1 to 1 ...... ...... $ head -n 5 out [ERR]: unable to analysize i at 1 line, from 1 to 1 258 1 1 1 [ERR]: unable to analysize n at 1 line, from 1 to 1 258 1 1 1 [ERR]: unable to analysize t at 1 line, from 1 to 1 258 1 1 1 [ERR]: unable to analysize at 1 line, from 1 to 1 258 1 1 1 [ERR]: unable to analysize g at 1 line, from 1 to 1 258 1 1 1
我们提供了
./tests/lab1/test_lexer.py
python脚本用于调用lexer
批量完成分析任务。# test_lexer.py脚本将自动分析./tests/lab1/testcase下所有文件后缀为.cminus的文件,并将输出结果保存在./tests/lab1/token文件下下 $ python3 ./tests/lab1/test_lexer.py ··· ··· ··· #上诉指令将在./tests/lab1/token文件夹下产生对应的分析结果 $ ls ./tests/lab1/token 1.tokens 2.tokens 3.tokens 4.tokens 5.tokens 6.tokens
-
验证
我们使用
diff
指令进行验证。将自己的生成结果和助教提供的TA_token
进行比较。$ diff ./tests/lab1/token ./tests/lab1/TA_token # 如果结果完全正确,则没有任何输出结果 # 如果有不一致,则会汇报具体哪个文件哪部分不一致
请注意助教提供的
testcase
并不能涵盖全部的测试情况,完成此部分仅能拿到基础分,请自行设计自己的testcase
进行测试。
实验报告
实验要求
根据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对程序运行不起到任何作用。
实验难点
- 实验环境的搭建以及配置,比如ubuntu20.04的安装以及flex编译环境的配置等。
- 学习并掌握cminus-f语法,提供其词法的正则表达式。
- FLEX是一个生成词法分析器的工具,我们需要学会其简单应用。
实验设计
1.首先需要编写正则表达式,类似于C语言中的宏定义,如下图所示是对cminus-f语法中的关键字,专用符号,标识符ID等正则表达式的定义,例如我们可以定义0|[1-9][0-9]*
为INTEGER,这样只要读出大于等于0的数字,就识别为INTEGER,关键字else就识别为ELSE。EOL [\n]+
是因为换行符可能不止一个,[\n]+则是语法糖里表示一个或一个以上换行。
2.编写指定模式匹配时对应的动作,yytext指针指向本次匹配的输入文本,我们在左部分写要匹配的正则表达式,右部分为匹配到该正则表达式后执行的动作。除了一些需要特殊的正则表达式如COMMENT要在analyzer
函数中实现外,大部分的都是一样的,先设置pos_start为pos_end即上一次识别结束的位置,然后设置pos_end+=strlen(yytext),strlen(yytext)为这次识别到的长度,最后进行return即可。最后的.{return ERROR;}
表示如果识别到其他未定义的字符则返回错误
3.在analyzer
函数中补充关于匹配到注释COMMENT,空格BLANK以及换行EOL后执行的动作。因为在步骤2时已经return ,所以这里只需要写关于pos_start,pos_end以及lines的相关变化
(1)对于注释COMMENT,先获yytext的长度为len,然后通过while循环判断注释中是否存在换行符,即判断yytext[i]是否为换行符,如果是换行\n的话,将pos_start和pos_end设置为1,lines加1;否则pos_end++;当循环结束则break;
(2)对于空格BLANK,我们只需要设置其pos_start以及pos_end的值变化,pos_start=pos_end,pos_end+=strlen(yytext)如下代码所示:
(3)对于换行EOL,只需要设置其pos_start和pos_end都为1,并让lines加上strlen(yytext),表示行数变化了多少
实验结果验证
1.创建build文件夹,配置编译环境, 执行指令make lexer运行代码,然后执行指令python3 ./tests/lab1/test_lexer.py
生成对应的token。
2.如图所示,6个token与助教提供的TO_token比较时,没有任何输出结果,表示结果完全相同,正确。
3.自己编写一个特殊的代码,对COMMENT注释的正则定义进行测试,判断其是否可以识别 /*//*/
这样的注释,同时检查数组a[],a[n]。
使用指令./build/lexer ./tests/lab1/testcase/7.mytest out
,得到如下输出:
结果分析:/*//*/
部分可以正确识别(没有输出因为直接过滤了),结果正确。同时数组的识别也正确。