语法分析~LL1的实现

语法分析之 LL1分析法实现

一、设计目的

根据某一文法编制调试LL(1)分析程序,以便对任意输入的符号串进行分析。本次实验的目的主要是加深对预测分析LL(1)分析法的理解。

二、设计要求

程序输入/输出示例:

对下列文法,用LL(1)分析法对任意输入的符号串进行分析:

原文法:

E->E+T|E-T|T

T->T*F|T/F|F

F->id|(E)|num

其中: id: a-f, A-F,num:0-9

消左递归:

E->TA A->+TA A->-TA A->e

T->FB B->*FB B->/FB B->e

F->i F->(E) F->n

其中:i:id, n:num, e:epsilonE->TG

FIRST集和FOLLOW集:

TA +TA -TA e FB *FB /FB e i (E) n
FIRST i,(,n + - e i,(,n * / e i ( n
E A T B F
FOLLOW ,)|,) +,-,,)|+,,,) *,/,+,-,$,)

输出的格式如下:

(1)输入一以#结束的符号串(包括+—*/()i#):

(2)输出过程如下:

输入 输出
E|(a1)(3+4/2)+((82)) E->TA

(3)输入符号串为非法符号串(或者为合法符号串)

注意:

1.表达式中允许使用运算符(+-*/)、分割符(括号)、字符i,结束符#;

2.如果遇到错误的表达式,应输出错误提示信息(该信息越详细越好);

3.测试用的表达式可以事先放在文本文件中,一行存放一个表达式,同时以分号分割。同时将预期的输出结果写在另一个文本文件中,以便和输出进行对照;

三、设计说明

1. 需求分析:

  a****) 输入及其范围

​ 输入为文法,表达式中允许使用运算符(+-*/)、分割符(括号)、字符a。

b****) 输出形式

输入 输出
E|(a1)(3+4/2)+((82)) E->TA

c) 程序功能

根据输入的文法进行分析,利用LL(1)控制程序根据显示栈栈顶内容、向前看符号以及LL(1)分析表,对输入符号串自上而下的分析过程

d) 测试数据

​ 输入:文件“fin.txt”输入待分析串

输出:命令行界面输出预测分析表,LL(1)分析过程输出至“fout.txt”

2. 概要设计

a****)数据类型的定义

​ vector<vector> table(5,vector(9)) //预测分析表

vector G //消除左递归后的文法产生式

map<char, int> index //文法符号到下标的转换字典

string terminal("in+-*/()$") //终结符

string nonTerminal("EATBF") //非终结符

vector First // 产生式右部符号串的first集

vector Follow //非终结符的follow集

b****)主程序流程

![img](file:///C:/Users/CARRAW~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg)

3. 详细设计

\1. int main()

{

for(文法G每个产生式itG,itFirst为其右部符号串的first集){

​ x = itG左部非终结符号的下标;

​ for(itFirst中的每个终结符号first){

​ y = 终结符号first的下标;

​ 把itG加入分析表表G[x][y];

}

if(终结符号first == epsilon)

for(Follow集中的每个符号follow){

​ y = follow的下标;

​ 把itG加入分析表G[x][y];

}

​ }

​ for(所有非终结符号的Follow集)

if(对应表项为空)

写入synch;

​ 将分析表输出到命令行界面;

return analysis();

}

\2. int analysis(void)

{

从文件fin.txt读取待分析串到s;

s末尾加‘$’;

​ 分析栈vector analyStack;

​ 向栈压入‘$’、‘E’;

​ ip指向s的第一个字符;

​ do{

​ top是栈顶符号;

​ cur是ip所指向的输入符号;

​ if(cur是字母) cur = ‘i’;

​ if(cur是数字) cur = ‘n’;

​ if(top是终结符号或‘$’){

​ if(top == cur){从栈顶弹出cur;ip前移一个位置;}

​ else error;

​ }

​ else{

​ x = top对应下标; y = cur对应下标;

​ 产生式production = table[x][y];

​ if(production非空){

​ 栈顶弹出cur;

​ 把production右部逆序压栈;

​ 输出production;

​ }

​ else error;

​ }

​ while(top != ‘$’);

}

四、运行结果及分析

1.测试数据

fin.txt文件入字符串:(a-1)(3+4/2)+((82))

2.测试输出的结果

img)

输出文件:

img

img

img

3.设计和思考

主要的难点在于对LL(1)的理解部分,消除二义性、消除左递归、提取左因子,判断是否为LL(1)文法,然后开始整理思路进行编码阶段。开始要对错误的文法进行分析,并提示详细的错误信息。思考之后实现了表达式中允许使用运算符(+-*/)、分割符(括号)、字符a。

五、总结

本次课程设计是本周实验来难点最大的一次作业,首先需要温习LL(1)的知识,如何消除左递归,区别二义性文法,以及对文法的分析。在实验的过程中,最重要的还是要理顺思路,想好解决办法,这也是我经过不断实验总结出的自我思考的方法。然后就进入了编码阶段,此次编码也有一定的难度,在代码量以及代码的整体设计上都有了提升,也是最值得思考的地方。最后,通过实验报告的书写,以及参考资料的查找,对今后的学习和工作都有很大的帮助。

代码

#include <iostream>
#include <fstream>
#include <iomanip>
#include <vector>
#include <string>
#include <map>
#include <stdexcept>

using namespace std;

// 预测分析表
vector<vector<string> > table(5, vector<string>(9));
// 文法的产生式
vector<string> G = {"E->TA", "A->+TA", "A->-TA", "A->e", "T->FB", "B->*FB", "B->/FB", "B->e", "F->i", "F->(E)", "F->n"};

// 文法符号到下标转换
map<char, int> index = {{'E', 0}, {'A', 1}, {'T', 2}, {'B', 3}, {'F', 4}, {'i', 0}, {'n', 1}, {'+', 2}, {'-', 3}, {'*', 4}, {'/', 5}, {'(', 6}, {')', 7}, {'$', 8}, {'e', 9}};
// 终结符
string terminal("in+-*/()$");
// 非终结符
string nonTerminal("EATBF");

// 产生式右部的first集
vector<string> First = {"i(n", "+", "-", "e", "i(n", "*", "/", "e", "i", "(", "n"};

// 非终结符的follow集
vector<string> Follow = {"$)", "$)", "+-$)", "+-$)", "*/+-$)"};


int analysis(void);


// 预测分析过程
int analysis(void) {
	ifstream fin("fin.txt");
	if (!fin.is_open()) {
		cout << "输入文件不存在 fin.txt."  << endl;
		return 1;
	}
	ofstream fout("fout.txt");
	if (!fout.is_open()) {
		cout << "无法打开输出文件 fout.txt." << endl;
		return 1;
	}

	//输入缓冲区
	string s;
	fin >> s;
	cout << "成功读取待分析串:" << endl << s << endl;
	int wid = s.length() + 1;
	s.push_back('$');

	//分析栈
	vector<char> analyStack;
	analyStack.push_back('$');
	analyStack.push_back('E');

	// 栈顶和当前输入
	char top = '\0', cur = '\0';
	auto ip = s.begin();

	// 输出头
	fout << left << setw(wid + 10) << "栈" << right << setw(wid) << "输入" << "    " << "输出" << endl;
	do {
		// 输出当前栈和当前剩余输入
		string str1(analyStack.begin(), analyStack.end());
		string str2(ip, s.end());
		fout << left << setw(wid + 10) << str1 << right << setw(wid) << str2 << "    ";

		// 栈顶和当前输入符号
		top = analyStack.back();
		cur = *ip;

		// 标识符及数字变换
		if (isalpha(cur))
			cur = 'i';
		else if (isdigit(cur))
			cur = 'n';

		// 栈顶是终结符号或$
		if (terminal.find(top) != terminal.npos || top == '$') {
			if (top == cur) {
				analyStack.pop_back();
				++ip;
				fout << endl;
			} else {
				fout << "出错! 不匹配,弹出" << top << endl;
				analyStack.pop_back();
			}
		}
		// 栈顶非终结符
		else {
			//坐标转换
			int x = index.at(top);
			int y;
			try {
				y = index.at(cur);
			} catch (out_of_range) {
				fout << "输入字符非法!" << endl;
				break;
			}

			// 产生式
			string production = table.at(x).at(y);
			// 产生式非空
			if (!production.empty()) {
				if (production == "synch") {  //同步
					fout << "出错!synch,弹出" << top << endl;
					analyStack.pop_back();
				} else { //正常分析
					analyStack.pop_back();
					string expr(production.begin() + 3, production.end());
					if (expr == "e")    //epsilon产生式
						expr = "";
					// 逆序压栈
					for (auto iter = expr.rbegin(); iter != expr.rend(); ++iter)
						analyStack.push_back(*iter);
					// 输出产生式
					fout << production << endl;
				}
			} else { //表项空白
				fout << "出错!空白,跳过" << *ip << endl;
				++ip;
			}
		}
	} while (top != '$');
	cout << endl << "分析结果已输出至 fout.txt." << endl;
	return 0;
}


int main() {
	//算法4.2 构造预测分析表
	// 遍历G的每个产生式
	for (auto itG = G.begin(), itFirst = First.begin(); itG != G.end() && itFirst != First.end(); ++itG, ++itFirst) {
		// 非终结符下标转换
		int x = index.at(*(itG->begin()));
		for (auto first = itFirst->begin(); first != itFirst->end(); ++first) {
			if (*first != 'e') {
				int y = index.at(*first);
				table.at(x).at(y) = *itG;
			} else {
				for (auto follow = Follow.at(x).begin(); follow != Follow.at(x).end(); ++follow) {
					int y = index.at(*follow);
					table.at(x).at(y) = *itG;
				}
			}
		}
	}
	// 写入同步信息
	for (string::size_type i = 0; i < nonTerminal.length(); ++i) {
		int x = index.at(nonTerminal.at(i));
		for (vector<string>::size_type j = 0; j < Follow.at(i).length(); ++j) {
			int y = index.at(Follow.at(i).at(j));
			if (table.at(x).at(y).empty())
				table[x][y] = "synch";
		}
	}
	// 输出预测分析表
	cout << "预测分析表:" << endl;
	// 输出终结符
	for (string::size_type i = 0; i < terminal.size(); ++i)
		cout << '\t' << terminal[i];
	cout << endl;
	// 输出非终结符
	for (string::size_type x = 0; x < nonTerminal.size(); ++x) {
		cout << nonTerminal[x];
		// 输出产生式
		for (string::size_type y = 0; y < table.at(x).size(); ++y)
			cout << '\t' << table.at(x).at(y);
		cout << endl;
	}
	cout << endl;
	return analysis();
}

posted on   blueskylabor  阅读(1071)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示