C语言
实验环境:Visual Studio Code、cmd.exe、flex.exe
author:zoxiii
词法分析器
1、实验内容
利用LEX或FLEX实验词法分析器,并测试输出。
2、前期准备
2.1 LEX原理
Lex是由美国Bell实验室的M.Lesk和Schmidt于1975年用C语言研制的一个词法分析程序的自动生成工具。对任何高级程序语言,用户必须用正规表达式描述该语言的各个词法类,Lex就可以自动生成该语言的词法分析程序。
Lex及其编译系统的作用如图1所示。
描述词法分析器的文件*.l,经过lex编译后,生成一个lex.yy.c 的文件,然后由C编译器编译生成一个词法分析器scan。
词法分析器,简单来说,其任务就是将输入的各种符号,转化成相应的标识符(token),转化后的标识符 很容易被后续阶段处理。
Lex源程序文件由如下三部分组成,且每部分之间必须用“%%”分隔开才有效。
其中,辅助定义式和用户子程序是任选的,而识别规则是必需的。
辅助定义式
%%
识别规则
%%
用户子程序
2.2 待分析的C语言子集的词法
单词大概可分为的5类:标识符、保留字、常数、运算符、界符。
- 标识符:以字母开头的包含字母和数字的符号
- 保留字:while、if、else、switch、case
- 常数:数字0~9
- 运算符:+、-、*、/、?、<、<=、==、=
- 界符:;
- 需略除的:空格、制表符、换行符
2.3 C语言子集的单词符号表示表
根据我们要构造的C语言子集的简单词法分析器的词法,列出所要实现的单词符号以及它们的种别编码。
这里添加了助记符用于定义辅助定义式。具体如下表:
单词符号 | 种别编码 | 助记符 |
---|---|---|
while | 1 | WHILE |
if | 2 | IF |
else | 3 | ELSE |
switch | 4 | SWITCH |
case | 5 | CASE |
标识符 | 6 | letter |
常数 | 7 | digit |
+ | 8 | ADD |
- | 9 | DEL |
* | 10 | MUL |
/ | 11 | DIV |
? | 12 | QUESTION |
<= | 13 | RELOP |
< | 13 | RELOP |
== | 13 | RELOP |
= | 14 | EQUAL |
; | 15 | SEMI |
2.4 C语言子集对应的状态转换图
根据需要构造的词法分析器的单词符号,设计了对应的状态转换图。其中,首先对输入串预处理,去除空格、制表符等,再根据单子符号表得到状态转换图,并分析每种状态应输出的单词二元组信息。
3、分析与运行
3.1 代码编写
(1) 辅助定义式
根据前面的词法的单词符号的助记符以及正规表达式的规则来辅助定义这些单词符号的组成形式。
例如,标识符就是以字母开头,加上不限制长度的字母或数字组成,所以得到标识符的定义式如下:
letter [A-Za-z][A-Za-z0-9]*
相应的其他单词符号也是按照这种方式定义,需要注意的是这里的空格符号、制表符等也需要定义。
(2) 识别规则
识别规则就是当识别到前面定义的单词符号应该有审美杨的活动,这里我们要求输出识别的单词符号及其对应的种别编码。
其中标识符的识别规则如下,其他的也类似:
{letter} {printf("(6,%s)\n",yytext);}
(3) 用户子程序
这里主要编写一个主程序,用于运行程序文件,对其进行词法分析。而要能将程序文件加入,就需要参数“argc”和“argv”。
int main(int argc,char** argv)
3.2 源代码
%{
#include<stdio.h>
#include<stdlib.h>
%}
delim [" "\n\t]
whitespace {delim}+
WHILE [w][h][i][l][e]
IF [i][f]
ELSE [e][l][s][e]
SWITCH [s][w][i][t][c][h]
CASE [c][a][s][e]
letter [A-Za-z][A-Za-z0-9]*
digit ([0-9])+
ADD [+]
DEL [-]
MUL [*]
DIV [/]
QUESTION [?]
RELOP [<][=]|[<]|[=][=]
EQUAL [=]
SEMI [;]
%%
{whitespace} {}
{WHILE} {printf("(1,%s)\n",yytext);}
{IF} {printf("(2,%s)\n",yytext);}
{ELSE} {printf("(3,%s)\n",yytext);}
{SWITCH} {printf("(4,%s)\n",yytext);}
{CASE} {printf("(5,%s)\n",yytext);}
{letter} {printf("(6,%s)\n",yytext);}
{digit} {printf("(7,%s)\n",yytext);}
{ADD} {printf("(8,%s)\n",yytext);}
{DEL} {printf("(9,%s)\n",yytext);}
{MUL} {printf("(10,%s)\n",yytext);}
{DIV} {printf("(11,%s)\n",yytext);}
{QUESTION} {printf("(12,%s)\n",yytext);}
{RELOP} {printf("(13,%s)\n",yytext);}
{EQUAL} {printf("(14,%s)\n",yytext);}
{SEMI} {printf("(15,%s)\n",yytext);}
%%
int yywrap()
{
return 1;
}
int main(int argc,char** argv)
{
if(argc>1)
{
if(!(yyin=fopen(argv[1],"r")))
{
perror(argv[1]);
return 1;
}
}
while(yylex());
return 1;
}
3.3 代码运行
首先构建运行环境,在网上下载了flex.exe文件,安装并添加路径到环境变量中,然后就可以在cmd.exe中使用了。
- 打开编写的文件所在路径:
cd ……
; - 编译编写的.l文件生成lex.yy.c文件:
flex.exe sy2.l
- 编译c文件生成运行文件:
gcc lex.yy.c -o scan
- 运行文件,添加参数为提前编写好的需分析的文档:
scan input.txt
,文件内容如下
Hello World?;
x=8;
y=13;
if 7<=9
x=6;
else if x<y
x=y/3;
- 得到词法分析结果如图3;
4、遇到的问题
(1) 在定义运算符时,对于“<”、“<=”、“==”的定义式编写有问题,编写成了如下形式:
RELOP [<=]|[<]|[\=\=]
后查阅资料发现对于由多个字符构成的运算符,必须分开写成“[<][=]”的形式才可以被分析出来。
(2) 生成词法分析程序中的yywrap函数必须由用户亲自在*.l文件中编写,否则可能会发生yywrap未定义的错误。
参考文献:《编译原理教程 (第4版)》 胡元义 2016