【编译原理】计算器实现(C语言)

这几天编译原理课留了个编写一个简单的计算器,要求能够检查错误并进行计算。其实也就是完成词法分析,语法分析,再完成一个后缀表达式的计算就好了。
这里找到了一个比较简单的方法,这里记录一下。

题目要求

在这里插入图片描述

简单分析

整个程序其实整体上大致分成四部分去做
1.读文件存储内容(这里的文本是以字符的形式存储的)
2.词法分析,把字符拆分成一个个的token
3.语法分析,对拆分的token进行分析,确定有无错误
4.后缀表达式求值,在确认格式无误后进行后缀表达式求值并输出

(1)读文件

readFile()函数完成了读入字符串到str数组中保存起来,方便后续操作,在这里处理了一个结尾是否为“ . “的错误,比较简单没什么好说的。
下面是具体函数 readFile

unsigned long readFile(char *filePath, char str[]) {
	FILE *fp = fopen(filePath, "r");//打开文件
	if (fp == NULL) {
		printf("打开文件出错,请确认文件存在当前目录下!\n");
		exit(0);
	}
	unsigned long i;
	i = fread(str, 1, MaxLen, fp);
	if (i >= MaxLen) {
		printf("文件过大!请重新输入一个更小的文件。\n");
		exit(1);
	}
	if (str[i - 1] != '.') {
		printf("Error, Please end up with \".\"\n");
		is_error = 1;
	}
	str[i] = '\0';
	fclose(fp);
	return i;
}

(2)词法分析

词法分析函数Scaner()这里的词法分析生成token,如果了解过一些编译原理词法分析的内容的话,这里的这个方法其实是有很多简化的,他其实只处理并储存了数字,标识符(变量名、int 、float) 对于其它的标点,运算符等符号其实都没有进行处理,只是将其分割但是没有保存

这里用了一个链表把标识符保存了起来,方便后续的检测

值得一提的是为什么这里用了while循环,这个循环,巧妙地解决的空格的问题,也就是如果有多余的空格,这里会直接跳过,很巧妙。

这是 Scaner()

void Scaner() {
	int i, is_float = -1;
	while (token = *src) {
		src++;
		//词法分析开始
		if (token == '.')
			return;
		else if (token == '\n')
			line++;
		else if (token == '+' || token == '-' || token == '*' || token == '/'
			|| token == '=' || token == ';' || token == '(' || token == ')')
			return;
		//处理标识符
		else if ((token >= 'a' && token <= 'z') || (token >= 'A' && token <= 'Z')) {
			new_id = 1;
			char *front = src - 1;
			while ((*src >= 'a' && *src <= 'z') || (*src >= 'A' && *src <= 'Z') || (*src >= '0' && *src <= '9'))
				src++;
			char name1[MaxLen];
			int n = src - front;
			for (i = 0; i < n; i++)
				name1[i] = *front++;
			name1[i] = '\0';
			//判断标识符是否重复
			for (current = head->next; current != NULL; current = current->next) {
				if (!Strcmp(current->name, name1)) {
					current1 = current;
					token = current->stamp;
					new_id = 0;
					return;
				}
			}
			token = Id;
			if (current1->type_float == 1)
				is_float = 1;
			ListPush(token, name1, -1, 0, is_float);
			return;
		}
		//处理整数,浮点数
		else if (token >= '0' && token <= '9') {
			int float_not = 1;
			int num = 1;
			token_value = (float)token - '0';
			while (*src >= '0' && *src <= '9' || *src == '.') {
				if (*src == '.') {
					float_not = 0;
					src++;
					continue;
				}
				if (float_not) {
					token_value = token_value * 10 + (*src - '0');
					src++;
				}
				else {
					num = num * 10;
					token_value += (float)((*src - '0') * 1.0 / num);
					src++;
				}
			}
			token = Num;
			return;
		}
		else if (token == ' ')
			continue;
		else 
			return;
	}
}

后面还用match()Scaner()进行了包装,这里的match在语法分析里有妙用。

(3)语法分析

Reader()函数做了语法分析。
这里先介绍match()的作用,就是判断给定的格式是不是当前token指向的字符格式,如果是则扫描下一个token,这里的token是指一个字符,一个数字,一个标识符。也就是说match()做的就是语法里面的匹配。

这样来再看语法分析,根据当年token的类型,决定进行什么语句的分析。
例如说开头的是变量Id,就是赋值语句,依次去match(Id)、match(=)、match(;)。这里为什么没有去求值,后面的后缀表达式介绍。
开头的是int 就要匹配定义语句:match(int)、match(=)、match(Id)、match(;)。

基本上搞清楚token指向的是什么,就很容易理解了。
Reader()

void Reader() {
	if (token == Id) {
		if (new_id) {
			ListPop();
			printf("Line %d: Error: Undeclared Variable!\n", line);
			is_error = 1;
		}
		Token *assign = current;
		match(Id);
		match('=');
		if (current->type_float == 1) {
			is_typeint = 1;
		}
		float value;
		value = expr();
		if (((value - (int)value) == 0) && is_typeint == 1) {
			printf("Line %d: Error: Illegal Conversion!\n", line);
			is_error = 1;
		}
		if (assign->type == Float)
			assign->value = value;
		else
			assign->value = (int)value;
		match(';');
	}
	else if (token == Int) {
		new_id = 1;
		match(Int);
		if (!new_id) {
			printf("Line %d: Warning: The Variable Is Repeatedly Declared!\n", line);
			is_error = 1;
		}
		current->type = Int;
		match(Id);
		match(';');
	}else if (token == Float) {
		new_id = 1;
		match(Float);
		if (!new_id) {
			printf("Line %d: Warning: The Variable Is Repeatedly Declared!\n", line);
			is_error = 1;
		}
		current->type = Float;
		match(Id);
		match(';');
	}
	else if (token == Write) {
		float output;
		int output_type;
		match(Write);
		match('(');
		if (new_id) {
			ListPop();
			printf("Line %d: Error:Undeclared Variable!\n", line);
			is_error = 1;
		}
		else {
			output = current->value;
			output_type = current->type;
		}
		match(Id);
		if (!is_var)
		{
			match(Id);
		}
		match(')');
		match(';');
		if (!is_error) {
			if (output_type == Int)
				printf("  %d  \n", (int)output);
			else if (output_type == Float)
				printf("  %f  \n", output);
		}

	}
	else if (token == '.')
		return;
}

(4)后缀表达式求值

后缀表达式求值本质上就是一个递归(或者堆栈)区分出运算符号优先级的过程,具体操作可以参照数据结构课本的堆栈相关章节。

这里只说这里的具体实现,因为我们在Scaner()函数重拆分的时候会得到具体的数字和运算符,而我们的Reader()又是一个个token进行匹配的,所以当我们进行计算时,可以先行计算出来值。

总结

基本内容就结束了,详细的源码以及说明放在这里了,如何运行请参照里面的。readme.md

链接: Caculator.