编译原理-词法分析器

该博客详细介绍了如何设计并实现一个词法分析器,包括根据正规式创建正规文法,绘制状态图,设计词法分析函数`int scan()`,以及识别不同类型的单词如标识符、整数、运算符、界符和关键字。实验分析部分讨论了正规式描述、状态图变换以及词法分析程序的数据结构和算法。实验代码展示了如何读取输入文件并调用`scan()`函数进行分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

词法分析器

实验要求

1、根据以下的正规式,编制正规文法,画出状态图;
标识符 <字母>(<字母>|<数字字符>)*
十进制整数 0 | ((1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)
八进制整数 0(1|2|3|4|5|6|7)(0|1|2|3|4|5|6|7)

十六进制整数 0x(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f)(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f)*
运算符和界符 + - * / > < = ( ) ;
关键字 if then else while do
2、根据状态图,设计词法分析函数int scan( ),完成以下功能:
1) 从文本文件中读入测试源代码,根据状态转换图,分析出一个单词,
2) 以二元式形式输出单词<单词种类,单词属性>
其中单词种类用整数表示:
0:标识符
1:十进制整数
2:八进制整数
3:十六进制整数
运算符和界符,关键字采用一字一符,不编码
其中单词属性表示如下:
标识符,整数由于采用一类一符,属性用单词表示
运算符和界符,关键字采用一字一符,属性为空

实验分析

1、 词法的正规式描述;
标识符 <字母>(<字母>|<数字字符>)*
十进制整数 0 | ((1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)
八进制整数 0(1|2|3|4|5|6|7)(0|1|2|3|4|5|6|7)

十六进制整数 0x(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f)(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f)*
运算符和界符 + - * / > < = ( ) ;
关键字 if then else while do
由于实验描述中已经包含相关的正规式描述,所以我们并不需要做过多处理。
2、 变换后的状态图;
状态图
3、 词法分析程序的数据结构与算法。
在这里插入图片描述
以上为整体代码框架部分,在代码的构造函数中读入输入文件中的数据,单行读入,将读入的串作为参数调用scan函数扫描。通过判断每一个单词是iscal(运算符界符),是isKey(关键字),是isTen(十进制),是isEight(八进制),是isSixteen(十六进制)来区分单词,而标识符直接使用isalpha()实现判断。同时还有一个结构体Twotuples用于记录结果,内含有两个分量, 就是结果显示左右部分。

实验代码

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iomanip>
using namespace std;
const int maxn = 1e4;
const int maxlen = 1e4;
class lexical {
private:
	char operate[10][10] = { "+","-","*","/",">","<","=","(",")",";" };
	const int rowope = 10;
	char keyword[5][10] = { "if","then","else","while","do" };
	const int rowkey = 5;
	struct Twotuples {
		char kind[10];
		char proper[10];
	}tuples[maxlen];
	string filename;
public:
	bool isCal(char *s, int &length) {
		char sub[100];
		for (int i = 0; i < rowope; i++) {
			int len = strlen(operate[i]);
			strncpy(sub, s, len);
			sub[len] = '\0';
			if (strcmp(sub, operate[i]) == 0) {
				length = len;
				return true;
			}
		}
		return false;
	}
	bool isKey(char *s, int &length) {
		char sub[100];
		int num = 0;
		while (isalpha(*(s + num))) {
			num++;
		}
		length = num;
		for (int i = 0; i < rowkey; i++) {
			strncpy(sub, s, length);
			sub[length] = '\0';
			if (strcmp(sub, keyword[i]) == 0 && !isalpha(*(s + length))) {
				return true;
			}
		}
		return false;
	}
	bool isTen(char *s, int &length) {
		if (s[0] == '0'&&!isdigit(s[1]) && s[1] != 'x') {
			length = 1;
			return true;
		}
		else
			if (s[0] >= '1'&&s[0] <= '9') {
				int num = 0;
				while (isdigit(*(s + num))) {
					num++;
				}
				length = num;
				return true;
			}
		return false;
	}
	bool isEight(char *s, int &length) {
		if (s[0] == '0' && (s[1] >= '1'&&s[1] <= '7')) {
			int num = 0;
			while (s[num] >= '0'&&s[num] <= '7') {
				num++;
			}
			length = num;
			return true;
		}
		return false;
	}
	bool isSixteen(char *s, int &length) {
		if (s[0] == '0' && s[1] == 'x' && ((s[2] >= '0'&&s[2] <= '9') || (s[2] >= 'a'&&s[2] <= 'f'))) {
			int num = 2;
			while ((s[num] >= '0'&&s[num] <= '9') || (s[num] >= 'a'&&s[num] <= 'f')) {
				num++;
			}
			length = num;
			return true;
		}
		return false;
	}
	void scan(char *str, int &p1, int &p2) {
		int len = 0;
		char str_[] = "-";
		char sub[100];
		//运算符和界符
		if (isCal(str + p1, len)) {

			strncpy(sub, str + p1, len);
			sub[len] = '\0';
			strcpy(tuples[p2].kind, sub);
			strcpy(tuples[p2].proper, str_);
			p1 += len;
			p2++;
		}
		//关键字
		if (isKey(str + p1, len)) {
			strncpy(sub, str + p1, len);
			sub[len] = '\0';

			strcpy(tuples[p2].kind, sub);
			strcpy(tuples[p2].proper, str_);
			p1 += len;
			p2++;
		}
		//标识符
		if (isalpha(*(str + p1))) {
			int len = 0;
			while (isalpha(*(str + p1 + len)) || isdigit(*(str + p1 + len))) {
				len++;
			}
			strncpy(sub, str + p1, len);
			sub[len] = '\0';
			strcpy(tuples[p2].kind, "0");
			strcpy(tuples[p2].proper, sub);
			p1 += len;
			p2++;
		}
		//十进制数字

		if (isTen(str + p1, len)) {
			strncpy(sub, str + p1, len);
			sub[len] = '\0';

			strcpy(tuples[p2].kind, "1");
			strcpy(tuples[p2].proper, sub);
			p1 += len;
			p2++;
		}
		if (isEight(str + p1, len)) {
			strncpy(sub, str + p1 + 1, len - 1);
			sub[len - 1] = '\0';
			strcpy(tuples[p2].kind, "2");
			strcpy(tuples[p2].proper, sub);
			p1 += len;
			p2++;
		}
		if (isSixteen(str + p1, len)) {
			strncpy(sub, str + p1 + 2, len - 2);
			sub[len - 2] = '\0';

			strcpy(tuples[p2].kind, "3");
			strcpy(tuples[p2].proper, sub);
			p1 += len;
			p2++;
		}

	}
	lexical(string inputfile , string outputfile) {
		this->filename = inputfile;
		char *buffer = new char[maxlen];
		ifstream in(filename);
		if (!in.is_open()) {
			cout << "文件打开失败" << endl;
			exit(1);
		}
		in.getline(buffer, maxlen, '#');
		int len = strlen(buffer);
		bool flagend = false;
		for (int i = 0; i < strlen(buffer); i++) {
			if (buffer[i] == '#') {
				flagend = true;
				break;
			}
		}
		if (!flagend)buffer[len++] = '#';
		buffer[len] = '\0';
		cout << buffer << endl;
		int buf_ptr = 0;
		int tup_ptr = 0;


		while (true) {
			if (buffer[buf_ptr] == '#')break;
			if (buffer[buf_ptr] == ' ' || buffer[buf_ptr] == '\n') {
				buf_ptr++;
				continue;
			}
			if (buffer[buf_ptr] == '\t') {
				buf_ptr += 4;
				continue;
			}
			scan(buffer, buf_ptr, tup_ptr);
		}
		cout.setf(std::ios::left);
		ofstream out(outputfile);
		for (int i = 0; i < tup_ptr; i++) {
			out  << "<" << setw(5) << tuples[i].kind << "," << setw(5) << tuples[i].proper << ">" << endl;
			cout << "<" << setw(5) << tuples[i].kind << "," << setw(5) << tuples[i].proper << ">" << endl;
		}
		
		
	}
};
int main()
{
	string filename1 = "D:\\c++Project\\fundamentals_of_compiling\\Parsing\\test.txt";
	string filename2= "D:\\c++Project\\fundamentals_of_compiling\\Parsing\\out.txt";
	lexical *text = new lexical(filename1,filename2);
	system("pause");
	return 0;
}

词法分析 一、实验目的: 通过设计编制调试一个具体的词法分析程序,加深对词法分析原理的理解。并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。 编制一个读单词过程,从输入的源程序中,识别出各个具有独立意义的单词,即基本保留字、标识符、常数、运算符、分隔符五大类。并依次输出各个单词的内部编码及单词符号自身值。(遇到错误时可显示“Error”,然后跳过错误部分继续显示) 二、实验说明 1、 词法分析器的功能和输出格式 词法分析器的功能是输入源程序,输出单词符号。词法分析器的单词符号常常表示成以下的二元式(单词种别码,单词符号的属性值)。本实验中,采用的是一类符号一种别码的方式。 2、 单词的BNF表示 -> ->|| |ε -> -> |ε -> + -> - -> > -> >= 三、实验要求 (一)准备: 1.阅读课本有关章节,明确语言的语法,写出基本保留字、标识符、常数、运算符、分隔符和程序例。 2.初步编制好程序。 3.准备好多组测试数据。 (二)上课上机: 将源代码拷贝到机上调试,发现错误,再修改完善。 第二次上机调试通过。 (三)程序要求: 程序输入/输出示例: 如源程序为C语言。输入如下一段: main() { int a,b; a = 10; b = a + 20; } 要求输出如下: (2,”main”) (5,”(“) (5,”)“) (5,”{“) (1,”int”) (2,”a”) (5,”,”) (2,”b”) (5,”;”) (2,”a”) (4,”=”) (3,”10”) (5,”;”) (2,”b”) (4,”=”) (2,”a”) (4,”+”) (3,”20”) (5,”;”) (5,”}“) 要求: 识别保留字:if、int、for、while、do、return、break、continue; 单词种别码为1。 其他的都识别为标识符;单词种别码为2。 常数为无符号整形数;单词种别码为3。 运算符包括:+、-、*、/、=、>、=、<=、!= ; 单词种别码为4。 分隔符包括:,、;、{、}、(); 单词种别码为5。 以上为参考,具体可自行增删。 (四)程序思路 这里以开始定义的C语言子集的源程序作为词法分析程序的输入数据。在词法分析中,自文件头开始扫描源程序字符,一旦发现符合“单词”定义的源程序字符串时,将它翻译成固定长度的单词内部表示,并查填适当的信息表。经过词法分析后,源程序字符串(源程序的外部表示)被翻译成具有等长信息的单词串(源程序的内部表示),并产生两个表格:常数表和标识符表,它们分别包含了源程序中的所有常数和所有标识符。 0.定义部分:定义常量、变量、数据结构。 1.初始化:从文件将源程序全部输入到字符缓冲区中。 2.取单词前:去掉多余空白。 3.取单词后:去掉多余空白(可选,看着办)。 4.取单词:利用实验一的成果读出单词的每一个字符,组成单词,分析类型。(关键是如何判断取单词结束?取到的单词是什么类型的单词?)
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

胖头鱼爱算法

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值