编译原理实验一 《词法分析程序设计与实现》

编译原理实验一 《词法分析程序设计与实现》

一、实验目的

​加深对词法分析器的工作过程的理解;加强对词法分析方法的掌握;能够采用一种编程语言实现简单的词法分析程序;能够使用自己编写的分析程序对简单的程序段进行词法分析。

二、实验内容

自定义一种程序设计语言,或者选择已有的一种高级语言,编制它的词法分析程序。词法分析程序的实现可以采用任何一种编程语言和编程工具。从输入的源程序中,识别出各个具有独立意义的单词,即关键字、标识符、常数、运算符、界符。并依次输出各个单词的内部编码及单词符号自身值(遇到错误时可显示“Error”,然后跳过错误部分继续显示)。

三、实验要求

1、对单词的构词规则有明确的定义;
2、编写的分析程序能够正确识别源程序中的单词符号;
3、识别出的单词以<种别码,值>的形式保存在符号表中,正确设计和维护符号表;
4、对于源程序中的词法错误,能够做出简单的错误处理,给出简单的错误提示,保证顺利完成整个源程序的词法分析。

四、实验步骤

1、定义目标语言的可用符号表和构词规则;
这里使用简化的C++语言作为编译对象的程序设计语言,考虑从关键字、标识符、常数、运算符、分隔符五个方面描述其构词规则。
(1) 假设一段简单的C++程序代码需要识别的词如下:
① 关键字:ifintforwhiledoreturnbreakcontinue;单词种别码为1
② 标识符;单词种别码为2
③ 常数为无符号整形数;单词种别码为3
④ 运算符包括:+-*/=><>=<===;单词种别码为4
⑤ 分隔符包括:,;{}(); 单词种别码为5

(2) 考虑该简单C++程序代码的文法的符号串集合 Σ Σ Σ 为(综合上面的词的构成):
Σ = { a , … z , A , … Z , 0 , 1 , … 9 , , + , − , ∗ , / , < , > , = , , , ; , { , } , ( , ) } \Sigma = \{a,…z, A,…Z, 0, 1,…9, _, +, -, *, /, <, >, =, ,, ;, \{, \}, (, )\} Σ={a,z,A,Z,0,1,9,,+,,,/,<,>,=,,,;,{,},(,)}

(3) 考虑该简单C++程序代码的LEX描述如下:
AUXILIARY DEFINITION /* 辅助定义 */
k e y → i f ∣ i n t ∣ f o r ∣ w h i l e ∣ d o ∣ r e t u r n ∣ b r e a k ∣ c o n t i n u e l e t t e r → a ∣ b ∣ … ∣ z ∣ A ∣ B ∣ … ∣ Z ∣ _ d i g i t → 0 ∣ 1 ∣ 2 ∣ … ∣ 9 o p e r a t o r → + ∣ − ∣ ∗ ∣ / s e p a r a t o r → , ∣ ; ∣ ∣ ∣ ( ∣ ) \begin{align*} & \mathbf { key → if | int | for | while | do | return | break | continue }\\ & \mathbf {letter → a|b|…|z|A|B|…|Z|\_ }\\ & \mathbf {digit → 0|1|2|…|9 }\\ & \mathbf {operator → + | - | * | / }\\ & \mathbf {separator → , | ; | { | } | ( | ) }\\ \end{align*} keyif∣int∣for∣while∣do∣return∣break∣continuelettera∣b∣∣z∣A∣B∣∣Z∣_digit0∣1∣2∣∣9operator+∣/separator,;()

RECOGNITION RULES /* 识别规则 */

词形动作
key{return(1, -)}
letter(letter|digit)*{return(2, -)}
digit(digit)*{return(3, -)}
operator|<|>|=|<=|>=|=={return(4, -)}
separator{return(5, -)}

注:考虑到<=、>=、==等复合操作符的特殊性,为方便使用类似超前搜索的方法,将<、>、=从operator中分离开来,作为一个单独的部分处理;为了方便实现,将下划线“_”归到letter当中。

2、构造相应的确定有限自动机DFA或者可以编程实现的状态转换图;
对于该简单C++程序代码,根据LEX描述构造状态转换图如下:(关键字 key 和一般标识符的识别方式相同,因此将 key 的识别归在标识符的识别当中,在具体编程实现时再进行区分)

在这里插入图片描述

3、为编译程序的具体实现进行必要的准备工作;(使用 C++、STL)
① 准备工作1:从.txt文件当中读取出代码(即文件全部内容)并形成缓存,即将其暂存于一个string对象当中。
实现由函数getSrcString(const string &path) 完成,代码如下。

 /* 读取文件所有内容 */
 string getSrcString(const string &path) {
 	ifstream myfile;
 	// path为目标源文件路径
   	myfile.open(path);
     
   	// 读取全部内容
 	string res((istreambuf_iterator<char>(myfile)), (istreambuf_iterator<char>()));
   	myfile.close();
   	return res;
 }

② 准备工作2:去除代码段首尾的空白字符;这里仅考虑空格、换行和制表符。
实现由函数StringTrim(string &s)完成,代码如下。

/* 删去字符串首尾空白符 */
void StringTrim(string &s) {
	// 首部的空白符
  	int index = 0;
  	if ( isBlank(s[index]) ) {
 		while ( isBlank(s[index]) ) {
 			index++;
 		}
 		s.erase(0, index);
 	}

  	// 尾部的空白符
  	index = s.size()-1;
  	if ( isBlank(s[index]) ) {
		while ( isBlank(s[index]) ) {
			index--;
		}
		s.erase(index+1);
  	}
}

其中函数bool isBlank(const char &c)用于判断传入的字符c是否为上述三个空白符。

③ 准备工作3:定义合适的数据结构。
识别出的单词种别码使用枚举类型以便维持可读性。

enum WordType {
   	BEGIN, KEYWORD, IDENTIFIER, CONSTANT, OPERATOR, SEPARATOR, ERROR
};

注:BEGIN是为了符合之前的规定而做出的填充,ERROR指识别出错的情况,也属于种别码。
需要输出的内容将被存储在符号表当中,符号表使用结构体作为每个单词的存储单位;

struct CompleteWord {
   	string word;
   	WordType type;
   
     CompleteWord(WordType type, string& word) {
   		this->type = type;
   		this->word = word;
   	}
};

整个符号表使用一个vector对象op_table存储以统一管理,该对象存储所有识别出的单词的结构体指针:vector <CompleteWord*> op_table;

④ 准备工作4:符号表内容的输出与符号表的销毁(所有结构体的内存空间释放),由以下两个函数完成。
void PrintOPTable(vector <CompleteWord*>& op_table);
void ClearOPTable(vector <CompleteWord*>& op_table);

4、根据上述DFA或者状态转换图,编写源程序,依次读入源程序符号,对源程序进行逐个字符的读入,然后判别再处理,将识别结果(包括识别成功的结果和识别错误的结果)存进新建的结构体并将其指针存进符号表对象op_table中,直到源程序结束;
参考思路(部分实现细节不同):
在这里插入图片描述

  1. 输出所得符号表的所有内容;
  2. 为符号表释放内存并完成销毁。
五、实验结果

​实验测试源代码保存在文件test.txt当中,其中包含关键字(int、if、return)、标识符(main、_a7x、c_)、常数(0、86、10)、运算符(=、+、>=、==)、分隔符(,、;、(、)、{、})以及错误输入(?=、50nb、%),并且首尾均有一定量的空白字符如换行(\n)、空格、制表(\t)等,对应自定义的目标可用符号表内容均包含在内,能够完整的展示程序功能。

​test.txt如下(部分空白位置为空白字符):

在这里插入图片描述
实验识别结果如下:

在这里插入图片描述

​可见识别结果正确的单词以(种别码,单词)形式出现,而错误结果则以ERROR:… 形式出现,后面会附加上大致的错误原因,如Unrecognized Character代表词法分析器不能识别该字符,INDENTIFIER and CONSTANT error代表标识符与常数混合错误。

六、实验结论

​实验利用自定义的源程序进行测试,结果正确,符合预期结果,测试源码及结果截图和说明如上所示。

七、实验小结

​对于词法分析器,最好还是通过状态转换图或DFA的构造来决定具体编程实现,如果不这样分析而是直接对程序运行过程进行分析也可以得到一个分析程序,但是这样可能会出现很多没有考虑到的情况(比如,部分单词识别错误造成连带识别,使得两个单词被错误识别为一个单词)。
​实验过程中也出现了一些问题,比如对于复合操作符<=、>=、==的识别较为困难,一开始如果直接把<、=、>简单的归于operator当中,则难以进行后续识别,因为operator当中除上述三个字符外的其他符号后面不能接“=”符号,需要对<、>、=符号进行超前搜索,因此将其单独处理比较合适。即当识别到<、>、=符号时,前进一个字符进行试探,试探成功则完成识别,试探失败则退回一个字符完成识别,类似于超前搜索;还有,针对C++的标识符组成结构,_符号也属于标识符的一部分,并且所起的作用与字母是相同的,因此将其归类到letter当中方便编程实现。

八、附录

实验源码如下。

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <cassert>
#include <vector>
using namespace std;

enum WordType
{
	BEGIN, KEYWORD, IDENTIFIER, CONSTANT, OPERATOR, SEPARATOR, ERROR
};

struct CompleteWord {
	string word;
	WordType type;

	CompleteWord(WordType type, string& word) {
		this->type = type;
		this->word = word;
	}
};

/* 判断是否为空白字符 */
bool isBlank(const char &c) {
	if (c == ' ' || c == '\n' || c == '\t') {
		return true;
	}
	else {
		return false;
	}
}

/* 删去字符串首尾空白符 */
void StringTrim(string &s) {
	// 首部的空白符
	int index = 0;
	if ( isBlank(s[index]) ) {
		while ( isBlank(s[index]) ) {
			index++;
		}
		s.erase(0, index);
	}
	
	// 尾部的空白符
	index = s.size()-1;
	if ( isBlank(s[index]) ) {
		while ( isBlank(s[index]) ) {
			index--;
		}
		s.erase(index+1);
	}
}

/* 判断是否为关键字 */
bool isKey(const string &s) {
	string keys[] = {
		"int", "if", "for", "while", "continue", "do", "return", "break"
	};
	int keys_size = sizeof(keys) / sizeof(string);
	for (int i = 0; i < keys_size; i++) {
		if (s == keys[i]){
            return true;
        }
	}
	return false;
}

/* 判断是否为数字 */
bool isDigit(const char &s) {
	if (s >= '0' && s <= '9') {
		return true;
	}
	return false;
}

/* 判断是否为指定字符 */
bool isLetter(const char &s) {
	if ((s >= 'a' && s <= 'z') || (s >= 'A' && s <= 'Z') || s == '_') {
		return true;
	}
	return false;
}

/* 判断是否为指定操作符 */
bool isOperator(const char &s) {
	if (s == '+' || s == '-' || s == '*' || s == '/' || s == '^') {
		return true;
	}
	return false;
}

/* 判断是否为分隔符 */
bool isSeparator(const char &s) {
	if (',' == s || ';' == s || '{' == s || '}' == s || '(' == s || ')' == s){
        return true;
    }
	else{
        return false;
    }
}

/* 读取文件所有内容 */
string getSrcString(const string &path) {
	ifstream myfile;
	myfile.open(path);

	// 读取全部内容
	string res((istreambuf_iterator<char>(myfile)), (istreambuf_iterator<char>()));
	myfile.close();
	return res;
}

/* 打印格式化函数 */
void Printer(WordType wordtype, const string &s) {
	if (wordtype == ERROR) {
		cout << "ERROR: " + s << endl;
	}
	else {
		cout << "( " + to_string(wordtype) + ", \"" + s + "\" )" << endl;
	}
}

/* 主处理函数 */
void dealProcedure(const string &s, vector <CompleteWord*> &op_table) {
	int index = 0;// 相当于指针
	char curChar;// getChar()
	string target;// 每次循环的临时识别结果保存
	while (index < s.size()) {
		// 准备工作
		curChar = s[index];
		target.clear();
		if (isLetter(curChar)) {
			while ((isLetter(curChar) || isDigit(curChar)) && index < s.size()) {
				// 将读到的字符加入形成识别结果
				target += curChar;
				index++;
				curChar = s[index];
			}
			// 回退一个字符,防止错读
			index--;
			curChar = s[index];
			// Reserve(),判断识别结果类型是否为关键字
			if (isKey(target)) {
				//Printer(KEYWORD, target);
				//CompleteWord* t = new CompleteWord();
				op_table.push_back(new CompleteWord(KEYWORD, target));
			}
			else {
				//Printer(IDENTIFIER, target);
				op_table.push_back(new CompleteWord(IDENTIFIER, target));
			}
		}
		else if (isDigit(curChar)) {
			while (isDigit(curChar) && index < s.size()) {
				// 将读到的字符加入形成识别结果
				target += curChar;
				index++;
				curChar = s[index];
			}
			// 检测出错
			if (isLetter(curChar)) {
				//Printer(ERROR, "IDENTIFIER and CONSTANT error of " + target);
				while ((isLetter(curChar) || isDigit(curChar)) && index < s.size()) {
					// 将读到的字符加入形成识别结果
					target += curChar;
					index++;
					curChar = s[index];
				}
				// 回退一个字符,防止错读
				index--;
				curChar = s[index];

				string msg = string("IDENTIFIER and CONSTANT error of ") + target;
				op_table.push_back(new CompleteWord(ERROR, msg));
				//break;
			}
			else {
				// 回退一个字符,防止错读
				index--;
				curChar = s[index];
				//Printer(CONSTANT, target);
				op_table.push_back(new CompleteWord(CONSTANT, target));
			}
		}
		else if (isOperator(curChar)) {
			target += curChar;
			//Printer(OPERATOR, target);
			op_table.push_back(new CompleteWord(OPERATOR, target));
		}
		else if (curChar == '<' || curChar == '>' || curChar == '=') {
			// 前进一个字符试探
			index++;
			if (s[index] == '=') {
				// 试探成功,将复合操作符形成结果
				target += curChar;
				curChar = s[index];
				target += curChar;
			}
			else {
				// 试探失败,指针回退一个字符
				index--;
				target += curChar;
			}
			//Printer(OPERATOR, target);
			op_table.push_back(new CompleteWord(OPERATOR, target));
		}
		else if (isSeparator(curChar)) {
			target += curChar;
			//Printer(SEPARATOR, target);
			op_table.push_back(new CompleteWord(SEPARATOR, target));
		}
		else {
			if (!isBlank(curChar)) {
				//Printer(ERROR, string("Unrecognized Character: \"")+curChar+'\"');
				string msg = string("Unrecognized Character: \"") + curChar + '\"';
				op_table.push_back(new CompleteWord(ERROR, msg));
			}
		}
		index++;// 前进一个字符
	}
}

/* 打印符号表中的所有内容 */
void PrintOPTable(vector <CompleteWord*>& op_table) {
	vector <CompleteWord*>::iterator it;
	for (it = op_table.begin(); it != op_table.end(); it++)
		Printer((*it)->type, (*it)->word);
}

/* 释放符号表占用的内存 */
void ClearOPTable(vector <CompleteWord*>& op_table) {
	vector <CompleteWord*>::iterator it;
	for (it = op_table.begin(); it != op_table.end(); it++)
		delete (*it);
	op_table.clear();
}

int main(){
	vector <CompleteWord*> op_table;
    // 文件位置根据各自需求修改即可,注意不要是相对路径,相对路径读不了,并且绝对路径的反斜杠要有两个
	string s = getSrcString("D:\\Projects\\Compiler Exps\\test.txt");
	StringTrim(s);
	dealProcedure(s, op_table);
	PrintOPTable(op_table);
	ClearOPTable(op_table);
	return 0;
}
  • 34
    点赞
  • 305
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
实验目的: 1.理解词法分析程序的基本原理和算法。 2.掌握Flex工具的使用方法。 3.掌握正则表达式的使用方法。 实验要求: 1.设计一个简单的词法分析程序,能够识别出以下关键字:if, else, for, while, do, break, continue, return。 2.能够识别出整数、浮点数、标识符、运算符、界符等。 3.能够对输入的代码进行词法分析并输出结果。 实验过程: 1.安装Flex工具。 2.编写词法分析器的规则。 3.编写测试程序。 4.使用Flex工具生成词法分析器。 5.编译并运行测试程序。 实验步骤: 1.安装Flex工具。 在Ubuntu系统中,可以使用以下命令安装Flex工具: ``` sudo apt-get install flex ``` 2.编写词法分析器的规则。 在本实验中,我们需要识别出以下关键字: if, else, for, while, do, break, continue, return 可以使用以下正则表达式进行匹配: ``` "if" {return IF;} "else" {return ELSE;} "for" {return FOR;} "while" {return WHILE;} "do" {return DO;} "break" {return BREAK;} "continue" {return CONTINUE;} "return" {return RETURN;} ``` 我们还需要识别出整数和浮点数。可以使用以下正则表达式进行匹配: ``` [0-9]+ {yylval.num = atoi(yytext); return INT;} [0-9]+"."[0-9]+ {yylval.fnum = atof(yytext); return FLOAT;} ``` 其中,yylval是Flex工具自带的一个全局变量,用于存储识别出的标识符、整数或浮点数的值。 我们还需要识别出运算符和界符。可以使用以下正则表达式进行匹配: ``` "+" {return ADD;} "-" {return SUB;} "*" {return MUL;} "/" {return DIV;} "%" {return MOD;} "=" {return ASSIGN;} ">" {return GT;} ">=" {return GE;} "<" {return LT;} "<=" {return LE;} "!=" {return NE;} "==" {return EQ;} ";" {return SEMICOLON;} "," {return COMMA;} "(" {return LPAREN;} ")" {return RPAREN;} "{" {return LBRACE;} "}" {return RBRACE;} ``` 最后,我们需要识别出标识符。可以使用以下正则表达式进行匹配: ``` [a-zA-Z][a-zA-Z0-9]* {yylval.id = strdup(yytext); return IDENTIFIER;} ``` 3.编写测试程序。 在本实验中,我们编写一个简单的测试程序,用于测试词法分析器是否正确。 ```c %{ #include "lex.yy.h" %} %option noyywrap %{ int yylex(); void yyerror(char *); %} %union { int num; float fnum; char *id; } %token IF ELSE FOR WHILE DO BREAK CONTINUE RETURN %token INT FLOAT %token ADD SUB MUL DIV MOD %token ASSIGN GT GE LT LE NE EQ %token SEMICOLON COMMA %token LPAREN RPAREN LBRACE RBRACE %token IDENTIFIER %% {if} {printf("IF\n");} {else} {printf("ELSE\n");} {for} {printf("FOR\n");} {while} {printf("WHILE\n");} {do} {printf("DO\n");} {break} {printf("BREAK\n");} {continue} {printf("CONTINUE\n");} {return} {printf("RETURN\n");} [0-9]+ {yylval.num = atoi(yytext); printf("INT %d\n", yylval.num);} [0-9]+"."[0-9]+ {yylval.fnum = atof(yytext); printf("FLOAT %f\n", yylval.fnum);} "+" {printf("ADD\n");} "-" {printf("SUB\n");} "*" {printf("MUL\n");} "/" {printf("DIV\n");} "%" {printf("MOD\n");} "=" {printf("ASSIGN\n");} ">" {printf("GT\n");} ">=" {printf("GE\n");} "<" {printf("LT\n");} "<=" {printf("LE\n");} "!=" {printf("NE\n");} "==" {printf("EQ\n");} ";" {printf("SEMICOLON\n");} "," {printf("COMMA\n");} "(" {printf("LPAREN\n");} ")" {printf("RPAREN\n");} "{" {printf("LBRACE\n");} "}" {printf("RBRACE\n");} [a-zA-Z][a-zA-Z0-9]* {yylval.id = strdup(yytext); printf("IDENTIFIER %s\n", yylval.id);} [ \t\n]+ { /* skip whitespace */ } %% int main(int argc, char *argv[]) { FILE *yyin = fopen(argv[1], "r"); if (!yyin) { printf("Cannot open input file!\n"); return -1; } yylex(); fclose(yyin); return 0; } void yyerror(char *s) { printf("%s\n", s); } ``` 4.使用Flex工具生成词法分析器。 可以使用以下命令生成词法分析器: ``` flex lexer.l ``` 该命令将生成一个名为lex.yy.c的文件,即词法分析器。 5.编译并运行测试程序。 可以使用以下命令编译测试程序: ``` gcc -o lexer lex.yy.c -lfl ``` 该命令将生成一个名为lexer的可执行文件。 可以使用以下命令运行测试程序: ``` ./lexer test.c ``` 其中,test.c是一个待分析的C程序。 实验结果: 假设我们有一个名为test.c的C程序,内容如下: ```c #include <stdio.h> int main() { int a = 10; float b = 3.14; if (a > 5) { printf("a is greater than 5\n"); } else { printf("a is less than or equal to 5\n"); } return 0; } ``` 使用词法分析器对该程序进行分析,输出如下: ``` #include <stdio.h> IDENTIFIER printf LPAREN STRING "a is greater than 5\n" RPAREN SEMICOLON INT IDENTIFIER main LPAREN RPAREN LBRACE INT IDENTIFIER a ASSIGN INT 10 SEMICOLON FLOAT IDENTIFIER b ASSIGN FLOAT 3.140000 SEMICOLON IF LPAREN IDENTIFIER a GT INT 5 RPAREN LBRACE IDENTIFIER printf LPAREN STRING "a is greater than 5\n" RPAREN SEMICOLON RBRACE ELSE LBRACE IDENTIFIER printf LPAREN STRING "a is less than or equal to 5\n" RPAREN SEMICOLON RBRACE RETURN INT 0 SEMICOLON RBRACE ``` 可以看到,词法分析器成功地识别出了关键字、标识符、整数、浮点数、运算符和界符等,并正确地输出了结果。 实验总结: 本实验中,我们学习了词法分析程序的基本原理和算法,并使用Flex工具和正则表达式编写了一个简单的词法分析器。词法分析器能够识别出关键字、标识符、整数、浮点数、运算符和界符等,并对输入的代码进行了正确的分析。通过本实验的学习,我们深入理解了编译原理中的词法分析过程,提高了对编译原理的理解和应用能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值