词法分析器设计

162 阅读6分钟

【实验目的】

1.熟悉词法分析的基本原理,词法分析的过程以及词法分析中要注意的问题。

2.复习高级语言,进一步加强用高级语言来解决实际问题的能力。

3.通过完成词法分析程序,了解词法分析的过程。

【实验内容】

用C语言编写一个PL/0词法分析器,为语法语义分析提供单词,使之能把输入的字符串形式的源程序分割成一个个单词符号传递给语法语义分析,并把分析结果(基本字,运算符,标识符,常数以及界符)输出。  

【实验要求】

1. 根据词法分析的目的以及内容,确定完成分析过程所需模块。

2. 写出每个模块的源代码,并给出注释。

3. 整理程序清单及所得结果。

实验分析

首先需要创建一个结构体,包括type(用于存储元素类型)value(用于存储元素的值)

typedef struct {
    char type[20];  // 存储元素类型(如"1.标识符"、"2.基本字"、"3.数字"、"4.运算符"、"5.界符"、"6.不合法")
    char value[100]; // 存储元素的值(字符串形式)
} Symbol;

然后定义一些全局变量

Symbol symbols[max];
FILE* fp, * output;// 打开文件指针
int ch;			// 读的词直接使用整形来存储
int buffer[4];	// 如果碰到超过一个字节的utf-8字符,用buffer保存
int size;		// 如果碰到超过一个字节的utf-8字符,用size保存字节长度
static int cnt=1;		// 计数器
int symbol_count = 0; 	// 当前存储的符号数量

我们这里定义一个存储保留字的模块,保留字按照ASCII码的定义顺序写入,便于对保留字进行二分查找

char ktt[][20] =
{
	"begin","blun","call","const","do","end","if","name",
	"odd","proceduce","read","then","var","while","write"
};//实验要求加入自己的姓名作为保留字,这里直接加入网名:blun
const long long studentID = 2065658487;//这里将学号改为企鹅号,欢迎大家交流讨论
预处理模块
void init()
{
	const char* in = "in.txt", * on = "out.txt";
	fp = fopen(in, "r");
	if (!fp)
	{
		puts("无法打开输入文件");
		exit(1);
	}
	output = fopen(on, "w");
	if (!output)
	{
		puts("无法打开输出文件");
		exit(1);
	}

}
读词模块
void getch()
{
	size = 0;
	int temp = fgetc(fp);
	if (temp == EOF)
	{
		ch = EOF;
		return;
	}
	if (!(temp & 0x80))
		ch = temp;// ASCII ַ 
	else
	{
		buffer[0] = temp;
		if ((temp & 0xE0) == 0xC0)	//0xE0 是一个掩码,二进制表示为 11100000。将 temp 与此掩码进行 AND 运算,结果只保留 temp 的最高三位。0xC0 是二进制 11000000,如果 temp 的最高三位是 110,说明该 UTF-8 字符需要两个字节。
			size = 2;
		else if ((temp & 0xF0) == 0xE0)	//0xF0 是掩码 11110000,用于保留 temp 的最高四位。0xE0 是二进制 11100000,如果 temp 的最高四位是 1110,说明该 UTF-8 字符需要三个字节。
			size = 3;
		else if ((temp & 0xF8) == 0xF0)	//0xF8 是掩码 11111000,用于保留 temp 的最高五位。0xF0 是二进制 11110000,如果 temp 的最高五位是 11110,说明该 UTF-8 字符需要四个字节。
			size = 4;
		for (int i = 1; i < size; ++i)
		{
			int next = fgetc(fp);
			if (next == EOF)
				size = i;
			buffer[i] = next;
		}
		ch = buffer[0];
	}
}

判断、存数组和文件模块
int getsym()
{
	while (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\f')
		getch();
	if (isalpha(ch))// 基本字或标识符
	{
		char tempSpace[21];
		int index;
		for (index = 0; isalnum(ch); getch())
			if (index < 20)
				tempSpace[index++] = ch;
		tempSpace[index] = '\0';
		int res = searchKey(0, 14, tempSpace);
		strcpy(symbols[symbol_count].type, res == -1 ? "标识符" : "基本字");
		strcpy(symbols[symbol_count++].value,tempSpace);
		fprintf(output,"%d %s\t%s\n", cnt++, tempSpace, res == -1 ? "标识符" : "基本字");
		return res == -1 ? 1 : 2;
	}
	else if (isdigit(ch))
	{
		long long num = 0;
		int bit = 0;
		for (bit = 0; isdigit(ch); ++bit, getch())
		{
			if (bit > 15)
				continue;
			num = num * 10 + ch - '0';
		}
		char str[20];
		sprintf(str,"%lld",num);
		strcpy(symbols[symbol_count].type,num == studentID ? "基本字" : "数字");
		strcpy(symbols[symbol_count++].value,str);
		fprintf(output,"%d %lld\t%s\n", cnt++, num, num == studentID ? "基本字" : "数字");
		return num == studentID ? 2 : 3;
	}
	else if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '#' || ch == '=' )
	{
		char str[2];
		str[0]=ch;str[1]='\0';
		strcpy(symbols[symbol_count].type,"运算符");
		strcpy(symbols[symbol_count++].value,str);
		fprintf(output,"%d %c\t运算符\n", cnt++, ch);
		getch();
		return 4;
	}
	else if (ch == '>' || ch == '<')
	{
		char op = ch;
		getch();
		if (ch == '=')
		{
			char str[3];
			str[0]=op;str[1]='=';str[2]='\0'; 
			strcpy(symbols[symbol_count].type,"运算符");
			strcpy(symbols[symbol_count++].value,str);
			fprintf(output,"%d %c=\t运算符\n", cnt++, op);
			getch();
		}
		else{
			char str[2];
			str[0]=op;str[1]='\0';
			strcpy(symbols[symbol_count].type,"运算符");
			strcpy(symbols[symbol_count++].value,str);
			fprintf(output,"%d %c\t运算符\n", cnt++, op);
		}
			
		return 4;
	}
	else if (ch == ':')
	{
		getch();
		if (ch == '=')
		{
			char str[3];
			str[0]=':';str[1]='=';str[3]='\0';
			strcpy(symbols[symbol_count].type,"运算符");
			strcpy(symbols[symbol_count++].value,str);
			fprintf(output,"%d :=\t运算符\n",cnt++);
			getch();
			return 4;
		}
		char str[2];
		str[0]=':';str[1]='\0';
		strcpy(symbols[symbol_count].type,"非法");
		strcpy(symbols[symbol_count++].value,str);
		fprintf(output,"%d :\t非法\n",cnt++);
		return 6;
	}
	else if (ch == ',' || ch == ';' || ch == '(' || ch == ')')
	{
		char str[2];
		str[0]=ch;str[1]='\0';
		strcpy(symbols[symbol_count].type,"界符");
		strcpy(symbols[symbol_count++].value,str);
		fprintf(output,"%d %c\t界符\n", cnt++, ch);
		getch();
		return 5;
	}
	else if(ch == '.')
	{
		char str[2];
		str[0]='.';str[1]='\0';
		strcpy(symbols[symbol_count].type,"界符");
		strcpy(symbols[symbol_count++].value,str);
		fprintf(output,"%d .\t界符\n",cnt++);
		return 0;
	}
	else
	{
		if (ch >= 128){
			char utf8_str[5] = {0}; // 最多四个字节的 UTF-8 字符 + null terminator
			for (int i = 0; i < size; ++i) {
				utf8_str[i] = buffer[i]; // 将整数值转换回字符
			}
			utf8_str[size] = '\0'; // 确保字符串正确终结
			strcpy(symbols[symbol_count].value, utf8_str); // 复制到 symbols 结构体
			strcpy(symbols[symbol_count++].type, "非法"); // 标记类型
			
		}
		else{
			char str[2];
			str[0]=ch;str[1]='\0';
			strcpy(symbols[symbol_count].type,"非法");
			strcpy(symbols[symbol_count++].value,str);
		}
		
		fprintf(output,"%d \t非法\n",cnt++);
		getch();
		return 6;
	}
}

二分查找关键字
int searchKey(int left, int right, const char* key)
{
	if (left > right)
	{
		int temp = left;
		left = right;
		right = temp;
	}
	while (left <= right)
	{
		int mid = (left + right) >> 1;
		if (!strcmp(key, ktt[mid]))
			return mid;
		else if (strcmp(key, ktt[mid]) > 0)
			left = mid + 1;
		else
			right = mid - 1;
	}
	return -1;
}
输出数据模块
void show(){
	for(int i=0;i<symbol_count;i++){
		printf("%d %s %s\n",i+1,symbols[i].type,symbols[i].value);
	}
}

退出前关闭文件
void save()
{
	fclose(fp);
	fclose(output);
}
主函数
int main()
{
	init();
	getch();
	while (!feof(fp) && getsym());	// 当fp为EOF后停止读数据
	show();
	save();
	return 0;
}
读取的目标文件in.txt
const       a=10;
    var         b,c;
    procedure    p;
      begin
       c:=b+a
      end;
    begin
studentid:2065658487
name:blun
end.
name:Mike
运行结果:
1 基本字 const
2 标识符 a
3 运算符 =
4 数字 10
5 界符 ;
6 基本字 var
7 标识符 b
8 界符 ,
9 标识符 c
10 界符 ;
11 标识符 procedure
12 标识符 p
13 界符 ;
14 基本字 begin
15 标识符 c
16 运算符 :=
17 标识符 b
18 运算符 +
19 标识符 a
20 基本字 end
21 界符 ;
22 基本字 begin
23 标识符 studentid
24 非法 :
25 基本字 2065658487
26 基本字 name
27 非法 :
28 基本字 blun
29 基本字 end
30 界符 .

结论:本程序能很好的解决从文本文件中读取utf-8字符乱码的问题,运行结果与实验要求结果基本相同,且运行结果不仅保存在结构数组中也保存在了out.txt文本文件中