【编译原理】【C语言】实验二:自动构造词法分析器


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所示。
在这里插入图片描述

图1-Lex编译系统

  描述词法分析器的文件*.l,经过lex编译后,生成一个lex.yy.c 的文件,然后由C编译器编译生成一个词法分析器scan。
  词法分析器,简单来说,其任务就是将输入的各种符号,转化成相应的标识符(token),转化后的标识符 很容易被后续阶段处理。
  Lex源程序文件由如下三部分组成,且每部分之间必须用“%%”分隔开才有效。
  其中,辅助定义式和用户子程序是任选的,而识别规则是必需的。

辅助定义式
%%
识别规则
%%
用户子程序

2.2 待分析的C语言子集的词法

  单词大概可分为的5类:标识符、保留字、常数、运算符、界符。

  1. 标识符:以字母开头的包含字母和数字的符号
  2. 保留字:while、if、else、switch、case
  3. 常数:数字0~9
  4. 运算符:+、-、*、/、?、<、<=、==、=
  5. 界符:;
  6. 需略除的:空格、制表符、换行符

2.3 C语言子集的单词符号表示表

  根据我们要构造的C语言子集的简单词法分析器的词法,列出所要实现的单词符号以及它们的种别编码。
  这里添加了助记符用于定义辅助定义式。具体如下表:

单词符号种别编码助记符
while1WHILE
if2IF
else3ELSE
switch4SWITCH
case5CASE
标识符6letter
常数7digit
+8ADD
-9DEL
*10MUL
/11DIV
?12QUESTION
<=13RELOP
<13RELOP
==13RELOP
=14EQUAL
;15SEMI

2.4 C语言子集对应的状态转换图

  根据需要构造的词法分析器的单词符号,设计了对应的状态转换图。其中,首先对输入串预处理,去除空格、制表符等,再根据单子符号表得到状态转换图,并分析每种状态应输出的单词二元组信息。
在这里插入图片描述

图2-状态转换图

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中使用了。

  1. 打开编写的文件所在路径:cd ……
  2. 编译编写的.l文件生成lex.yy.c文件:flex.exe sy2.l
  3. 编译c文件生成运行文件:gcc lex.yy.c -o scan
  4. 运行文件,添加参数为提前编写好的需分析的文档:scan input.txt,文件内容如下
Hello World?;
x=8;
y=13;
if 7<=9  
    x=6;   
else if x<y
    x=y/3;
  1. 得到词法分析结果如图3;
    在这里插入图片描述
图3-分析结果图

4、遇到的问题

(1) 在定义运算符时,对于“<”、“<=”、“==”的定义式编写有问题,编写成了如下形式:

RELOP [<=]|[<]|[\=\=]

  后查阅资料发现对于由多个字符构成的运算符,必须分开写成“[<][=]”的形式才可以被分析出来。
(2) 生成词法分析程序中的yywrap函数必须由用户亲自在*.l文件中编写,否则可能会发生yywrap未定义的错误。

参考文献:《编译原理教程 (第4版)》 胡元义 2016

实验 词法分析器 一、实验目的 掌握词法分析器的构造原理,掌握手工编程或LEX编程方法之一。 实验内容 编写一个LEX源程序,使之生成一个词法分析器,能够输入的源程序转换为单词序列输出。 三、实验环境 Flex+VC6.0 四、实验注意 1.Id正则表达式:{letter}({letter}|{digit})* 2.Num正则表达式:{digit}+(\.{digit}+)?(E[+-]?{digit}+)? 3.注释:(\/\*(.)*\*\/) 4.关键字再加上其他字符就又能编程id,所以在词法分析时,id的判断应该放在关键字前面,这样才不会误判 5.由于本程序知识简单的打印数字,因此没有考虑数字的转换 6.">="比">"多一个字符,它应该放在前面判断,其他类似的也应该如此安排 五、实验代码 ******************************************************************************* 实验文件:lex.l、lex.yy.c 实验结果:lex.exe 运行方式:打开lex.exe,弹出input.txt,在其中输入所要测试的程序,保存并关闭,即可在output.txt中看到所得结果 ******************************************************************************* %{ void Install(char *type); %} %option noyywrap delim [ \t] newline [\n] digit [0-9] num {digit}+(\.{digit}+)?(E[+-]?{digit}+)? letter [A-Za-z] id {letter}({letter}|{digit})* key ("if"|"while"|"do"|"break"|"true") basic ("int"|"float"|"bool"|"char") op (">="|""|"<"|"="|"!="|"+"|"-"|"*"|"/") comment (\/\*(.)*\*\/) %% delim {;} newline {printf("\n");} {num} {Install("Num");} {key} {Install("Key");} {basic} {Install("Basic");} {op} {Install("Op");} ";" {Install("Comma");} {id} {Install("ID");} {comment} {Install("Comment");} "(" | "[" | "{" {Install("lbracket");} ")" | "]" | "}" {Install("rbracket");} %% void Install(char *s) { fprintf(yyout, "%s:%s ", s, yytext); } int main() { printf("please input the test program in input.txt\n"); system("input.txt"); yyin = fopen("input.txt", "r"); yyout = fopen("output.txt", "w" ); yylex(); fclose(yyout); fclose(yyin); printf("analysis result in output.txt\n"); system("output.txt"); return 0; } 六、实验小结 本次的实验由于使用了flex,所以代码较短,麻烦的事flex的正则式表达,由于该使用规则只有简单介绍,而网上找的教程难免有比重就轻之嫌,所以得到上述表达式着实费力,且有的没有成功,例如bracket的(\ ((.)*\ ))或者("("(.)*")")使用时都没有成功,所以便单独写出,有点不伦不类。至于其他的,都较为简单,完。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zoxiii

越打赏越生长

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值