编译原理 C-Minus词法分析(FLEX)

C–源代码词法分析

一、实现目标

  1. 编写一个程序对C–语言(C语言子集)书写的源代码进行词法分析,并打印分析结果。
  2. 程序要能够检查源代码中可能包含的词法错误:
    1. 最低要求1.1:能够识别词法中未定义的字符;
    2. 其他要求1.2:能识别指数形式的浮点数;
    3. 其他要求1.3:能识别8进制/16进制数。
  3. 词法分析程序能定位错误位置。

二、C-Minus语法

关键字

TOKENCONTENT
STRUCTstruct
RETURNreturn
IFif
ELSEelse
WHILEwhile
TYPE intfloat

特殊符号

TOKENCONTENT
SEMI;
COMMA,
ASSIGNOP=
PLUS+
MINUS-
STAR*
DIV/
AND&&
DOT.
NOT!
LP(
RP)
LB[
RB]
LC{
RC}

数字表示:十进制、二进制、八进制、十六进制、浮点数、科学计数

TOKENDESCRIBECONTENT
INT十进制:表示的是所有无符号的整型常数,除“0”外,首位数字不为00
INT_HEX十六进制:十六进制以0x或者0X开头(如0xFF32)0[xX][a-fA-F0-9]+
INT_OCT八进制:八进制整数以数字0开头(如:0237)0[1-7][0-7]*
INT_BIN二进制:二进制整数以0b或者0B开头0[bB][01]+
FLOAT浮点数:表示的是所有无符号的浮点型常数,小数点前后必须有数字出现{INT}.[0-9]+
SCIENCE科学计数法:包含基数、指数符号和指数三个部分,基数的小数点可以出现在数字串的任何位 置,指数符号用E或e表示,指数可带±符号或是不带。01.23E2、43.e-4、.5E03都是符合要求的浮点数(([0-9]+.[0-9]*)

标识符

TOKENDESCRIBECONTENT
ID标识符由下划线_、数字0-9、字母a-zA-Z组成,开头不能是数字[a-z_A-Z][a-z_A-Z0-9]*

其他字符

TOKENDESCRIBECONTENT
COMMENT注释://“/"([](([^/])+([/])))"/”
SPACE空白符[ \t\n\r\f\v]+
AERROR其他未匹配字符(用于错误处理).

三、Flex

Flex简介

Lex是LEXical compiler的缩写,是Unix环境下非常著名的工具,主要功能是生成一个词法分析器(scanner)的C源码,描述规则采用正则表达式(regular expression)。描述词法分析器的文件*.l,经过lex编译后,生成一个lex.yy.c 的文件,然后由C编译器编译生成一个词法分析器。词法分析器,简单来说,其任务就是将输入的各种符号,转化成相应的标识符(token),转化后的标识符 很容易被后续阶段处理。

Flex正则表达式

符号含义
|
[]括号中的字符取其一
-a-z表示ascii码中介于a-z包括a.z的字符
\转义(flex不能识别除字母外的字符)
*0或多个字符
?0或1个字符
+1或多个字符
^除此之外的其余字符
.除\n外的所有字符,等价于^\n

Flex安装与使用

实验环境:Ubantu 12,已安装gcc
在Linux命令行中安装flex、bison:

sudo apt-get install flex bison

查看版本号,检查是否安装成功:

flex -V
bison -V

写好词法分析的文件(以.l结尾),进入文件所在目录,使用flex进行编译:

flex 词法分析源文件名

编译过后会生成名为lex.yy.c的文件,使用gcc进行编译生成可执行文件:

gcc lex.yy.c -o 可执行文件名

使用编译后的可执行文件分析测试文件的词法:

./可执行文件名 测试文件名

在控制台中查看输出的分析结果

Flex文件编写

Flex的.l文件内容可以分为三个部分:定义、规则、用户代码

定义:definition
%%
规则:rules
%%
用户代码:code
定义

定义又可以分为四部分:添加头文件、变量声明、设置flex属性、正则表达式定义

  1. 头文件和变量声明一般包括在括号%{…%}中
/*第一部分 头文件和变量*/
%{
	#include <stdlib.h>
	#include <stdio.h>
	FILE* f;
	int i;
	int comment_flag;
	int comment_begin;
%}

该处声明的变量可以使用在用户代码定义的函数中,作为全局变量
如果变量声明不在%{%}之间,则需要使用tab来缩进

int a;
    int b;
  1. 设置flex属性

flex的选项影响最终生成的词法分析器的属性和行为。这些选项可以在运行flex命令时在终端输入,也可以在.l文件中使用%option指定。
本次实验中需要使用到当前分析的词所在的行数:

/*flex属性,记录符号所在行号*/
%option yylineno
  1. 正则表达式声明方式:
{表达式名称} 正则表达式

在定义正则表达式时可以直接使用已经定义过的表达式名称,需要加上{}
以本次实验中数字类型和标识符为例:

/*第二部分 定义正则表达式*/
/*十进制*/
INT 0|[1-9][0-9]*
/*十六进制*/
INT_HEX 0[xX][a-fA-F0-9]+
/*八进制*/
INT_OCT 0[1-7][0-7]*
/*二进制*/
INT_BIN 0[bB][01]+
/*浮点数*/
FLOAT {INT}\.[0-9]+
/*科学计数法*/
SCIENCE (([0-9]+\.[0-9]*)|([0-9]*\.[0-9]+)|INT)[Ee][-+]?[0-9]+
/*数字类型汇总*/
NUMBER {INT_HEX}|{INT}|{INT_OCT}|{INT_BIN}|{SCIENCE}|{FLOAT}

/*标识符*/
ID [a-z_A-Z][a-z_A-Z0-9]*

匹配关键字:

/*关键字*/
STRUCT struct
RETURN return
IF if
ELSE else
WHILE while
TYPE int|float

/*部分符号*/
/*标点*/
SEMI  ;
COMMA ,
/*运算*/
ASSIGNOP  =
PLUS  \+
MINUS \-
STAR  \*
DIV   \/
AND   &&
DOT   \.
NOT   \!
/*括号*/
LP    \(
RP    \)
LB    \[
RB    \]
LC    \{
RC    \}

空白符和注释部分正则表达式如下:

/*其它字符*/
/*注释*/
COMMENT ("//".*)|("/*"([*]*(([^*/])+([/])*)*)*"*/")
COMMENT_BEGIN "/*"
/*空白符*/
SPACE [ \f\n\r\t\v]+
/*未定义字符*/
AERROR .
/*十六进制错误*/
INT_HEX_ERROR 0[xX][a-fA-F0-9]*[g-zG-Z]+[a-fA-F0-9]*
/*八进制错误*/
INT_OCT_ERROR 0[0-7]*[89]+[0-7]*
/*二进制错误*/
INT_BIN_ERROR 0[bB][01]*[2-9]+[01]*

当所有的正则表达式都无法匹配当前词时,就会被匹配到AERROR(需要在规则部分的最后进行处理,优先匹配其他类型),从而做出匹配到为定义字符的错误处理

规则

整个规则部分需要包括在

%%
...
%%

并且规则部分中的注释行必须顶一个空格,例如:

/*...*/

编写规则时,一个规则一行,每行有两部分构成:

正则表达式 {动作函数}

也可以使用

正则表达式 |
正则表达式 {...}

此处 “正则表达式” 和 ’|’中间用一个空格隔开
动作函数可以调用在用户代码中定义的函数
以本实验为例:

/*第三部分 操作 action 这里面的注释必须顶格一个空格*/
%%
 /*跳过空白和注释*/
{SPACE} {}
{COMMENT} { printf("注释,行数: %d 字符:%s\n",yylineno,yytext);}
{COMMENT_BEGIN} {
	comment_flag = 1;
	comment_begin = yylineno;}
 /*未终结注释错误*/
<<EOF>> {
	if(comment_flag == 1){
		printf("UNTERMINATED_COMMENT at line %d\n",yylineno);
		comment_flag = 0;}
	yyterminate();
}
 /*关键字*/
{TYPE} |
{STRUCT} |
{RETURN} |
{IF} |
{ELSE} |
{WHILE} { printf("关键字,行数: %d 字符:%s\n",yylineno,yytext);}
 /*数字类型错误*/
{INT_HEX_ERROR} {if(comment_flag!=1) printf("INT_HEX_ERROR at line %d: charachters \"%s\"\n",yylineno,yytext);}
{INT_OCT_ERROR} {if(comment_flag!=1) printf("INT_OCT_ERROR at line %d: charachters \"%s\"\n",yylineno,yytext);}
{INT_BIN_ERROR} {if(comment_flag!=1) printf("INT_BIN_ERROR at line %d: charachters \"%s\"\n",yylineno,yytext);}
 /*数字类型表示*/
{NUMBER} {if(comment_flag!=1)  printf("数字,行数: %d 字符:%s\n",yylineno,yytext);}
 /*标点*/
{SEMI} |
{COMMA} {if(comment_flag!=1)  printf("标点,行数: %d 字符:%s\n",yylineno,yytext);}
 /*运算符*/
{ASSIGNOP} |
{PLUS} |
{MINUS} |
{STAR} |
{DIV} |
{AND} |
{DOT} |
{NOT} {if(comment_flag!=1)  printf("运算符,行数: %d 字符:%s\n",yylineno,yytext);}
 /*括号*/
{LP} |
{RP} |
{LB} |
{RB} |
{LC} |
{RC} {if(comment_flag!=1)  printf("括号,行数: %d 字符:%s\n",yylineno,yytext);}
 /*标识符*/
{ID} {if(comment_flag!=1)  printf("标识符,行数:%d 字符:%s\n",yylineno,yytext);}
 /*错误*/
{AERROR} {if(comment_flag!=1) printf("Error type A at line %d: mysterious charachter '%s'\n",yylineno,yytext);}
%%

为了展现词法分析效果,将所有匹配情况都进行了输出,实际上只输出AERROR类型即可
这里使用yylineno表示当前所分析的字符串在文件的第几行,yytext是lex内部已经定义好的指针变量,lex分析过程是将输入字符串按程序员预先设计好的正则表达式进行匹配,yytext总是指向当前获得匹配的字符串

用户代码

该部分必须定义main函数,用于指定需要扫描分析的文件,可以内部指定,也可以通过控制台传入参数:

int main(int argc,char** argv){
	if(argc<2){
		/*由lex创建的扫描程序的入口点yylex()
		调用yylex()启动或者重新开始扫描。
		如果lex动作执行讲数值传递给调用的程序return,那么对yylex()的下次调用就从它的停止地方继续。*/
		yylex();
		return 0;
	}
	for(i=1;i<argc;i++){
		f=fopen(argv[i],"r");
		if(!f){
			/*C 库函数 void perror(const char *str) 
			把一个描述性错误消息输出到标准错误 stderr。
			首先输出字符串 str,后跟一个冒号,然后是一个空格。*/
			perror(argv[i]);
			return 1;
		}
		comment_flag = 0;
		/*yyrestart使词法分析器从f中读取标准输入文件*/
        yyrestart(f);
        yylex();
        fclose(f);
	}
	return 0;
}

这段代码中实现了传入多个文件参数时的处理
yylex():由lex创建的扫描程序的入口点yylex(),调用*yylex()启动或者重新开始扫描。如果lex动作执行讲数值传递给调用的程序return,那么对yylex()*的下次调用就从它的停止地方继续
C 库函数 *void perror(const char str) :把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格
yyrestart():使词法分析器从f中读取标准输入文件

/*第四部分 函数 function*/
int yywrap()
{
	/*此函数必须由用户提供,或者声明 %option noyywrap
	当词法分析程序遇到文件结尾时,它调用例程yywrap()来找出下一步要做什么
	如果返回0,扫描程序继续扫描,如果返回1,扫描程序就返回报告文件结尾*/
    return 1;
}

除了定义main函数,用户还需要自己定义yywrap()函数,或者声明 %option noyywrap;当词法分析程序遇到文件结尾时,它调用例程yywrap()来找出下一步要做什么,如果返回0,扫描程序继续扫描,如果返回1,扫描程序就返回报告文件结尾。

四、总结

完成的内容

  • 能够识别C-Minus中定义的词法(按照C-Tokens文件实现)
  • 能识指数形式的整数和浮点数(科学计数法)
  • 能识别2进制/8进制/16进制数
  • 能识别注释内容、空白字符
  • 能识别未定义的字符,并定位错误位置

这里留下几个测试文件,读者可自行测试,本人已经测试过了,就不再贴结果

文件1

struct Complex
{
  float real, image;
}

int main()
{
  struct Complex x;
  float a[10] = 1.5;
  int i = 100;
  x.image = ~i;

  if (a[1][2] == 0) i =1 else i =0;
}

预测结果:
第11行存在未定义字符~,其他字符、字符串都可以正常识别

文件2

int main()
{
    int i = 0123;
    int j = 0x3F;
    int k = 0967;
}

预测结果
第五行八进制书写错误,词法分析程序会将0967分析为十进制0和十进制967,交给语法分析器进行进一步处理


本文为原创,如有错误欢迎指正,感谢阅读。

附上参考文献:
编译原理-用FLEX构造词法分析程序LEX/FLEX词法分析器

  • 19
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编译原理是计算机科学中的一门重要课程,涉及了很多与编译器设计和实现相关的概念和技术。而C-minus语法分析则是编译原理中的一个重要部分,它使用flex和bison这两个工具来实现。接下来我将详细介绍C-minus语法分析的相关内容。 C-minus是一种简化版的C语言,它具有类似于C语言的语法和语义。语法分析是编译器的第二个阶段,主要负责通过解析输入的源代码来构建抽象语法树。在C-minus语法分析中,我们使用flex和bison这两个工具来实现词法分析和语法分析。 flex一个用于生成词法分析器的工具,它通过定义一系列正则表达式规则来识别源代码中的各种词法单元,如关键字、标识符、常量等。在C-minus语法分析中,我们可以使用flex来识别源代码中的词法单元,并将它们传递给bison进行后续的语法分析。 bison是一个用于生成语法分析器的工具,它通过定义一系列文法规则来分析词法单元之间的语法关系,同时生成一个由这些规则构成的抽象语法树。在C-minus语法分析中,我们可以使用bison来定义C-minus语言的文法规则,并将其与词法单元进行匹配,从而生成抽象语法树。 在C-minus语法分析中,我们需要定义C-minus语言的文法规则,如声明语句、函数定义、循环语句等。通过使用flex和bison这两个工具,我们可以将这些规则转化为相应的词法和语法规则,并实现一个完整的C-minus语法分析器。 总而言之,C-minus语法分析是编译原理中的一个重要环节,通过使用flex和bison这两个工具,我们可以实现一个功能完善的C-minus语法分析器,从而为后续的语义分析和代码生成打下基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值