LL(1)文法分析程序

news2024/11/25 6:41:03

一、实验目的

设计一个非递归预测分析器,实现对表达式语言的分析,理解自上而下语法
分析方法的基本思想,掌握设计LL()文法分析程序设计的基本原理和方法。
根据给定LL(1)分析表,输入一个句子,能由依据LL(1)分析表输出与
句子对应的语法树。能对语法树生成过程进行模拟。

二、实验原理与内容

2.1实验内容:

通过本实验,应达到的目的:
(1)使学生掌握LL(1)模块的基本工作原理;
(2)培养学生基本掌握LL(1)分析的基本思路和方法;
(3)使学生掌握LL(1)的调试;
(4)培养学生分析、解决问题的能力。

2.2实验原理

LL(1)分析法属于确定的自顶向下分析方法。LL(1)的含义是:第一个L表明自顶向下分析是从左向右扫描输入串,第2个L表明分析过程中将使用最左推导,1表明只需向右看一个符号便可决定如何推导,即选择哪个产生式(规则)进行推导。
(1)LL(1)文法的判别需要依次计算FIRST集、FOLLOW集和SELLECT集,然后判断是否为LL(1)文法,最后再进行句子分析。
(2)需要预测分析器对所给句型进行识别。即在LL(1)分析法中,每当在符号栈的栈顶出现非终极符时,要预测用哪个产生式的右部去替换该非终极符;当出现终结符时,判断其与剩余输入串的第一个字符是否匹配,如果匹配,则继续分析,否则报错。LL(1)分析方法要求文法满足如下条件:对于任一非终极符A的两个不同产生式A→a,A→β,都要满足下面条件:SELECT(A→a)n SELECT(A→p)=O

三、实验结果

在这里插入图片描述

四、代码实现

#include<iostream>
#include<stdio.h>
#include<vector>
#include<string>
#include<stack>
#include<map>
#include<cstring>
#include<cstdlib>
#include <bits/ios_base.h>
using namespace std;
map<char, int>getnum;
vector<char>getzf;
vector<string>proce(10);
vector<string>first(20);
vector<string>follow(20);
int table[100][100];      //预测分析表
int num;
int numv;//终结符的数量-1
char j[2];
void  read() { //读终结符、非终结符、产生式
	char c;
	int i = 0;
	int n = 0;
	cout << "输入产生式集合(空字用‘@’表示),输入一条后换行,以‘end’结束:" << endl;
	string ss;
	string dd;
	int j = 0;
	int y = 0;
	while (cin >> ss && ss != "end") {
		dd.clear();
		dd += ss[0];
		proce[j] += dd;
		for (i = 3; i < ss.length(); i++) {
			if (ss[i] != '|') {
				dd.clear();
				dd += ss[i];
				proce[j] += dd;
			} else {
				dd.clear();
				dd += ss[0];
				dd += ss[++i];
				proce[++j] += dd;
			}
		}
		j++;
	}

	getnum['#'] = 0; //#代表结束标志
	//	getzf[0]='#';没有定义数组大小的时候这样输入是错误的
	getzf.push_back('#');
	//终结符压栈
	for (int i = 0; i < proce.size(); i++) {
		for (int k = 0; k < proce[i].length(); k++) {
			if (proce[i][k] != '-' && proce[i][k] != '|') {
				if (proce[i][k] < 64 || proce[i][k] > 90) {
					for ( y = 0; y < getzf.size(); y++) {
						if (proce[i][k] == getzf[y]) break;
					}
					if (y == getzf.size() && k != 2) { //这里让k!=2是不让第三位置的>进去
						getnum[proce[i][k]] = ++n;
						getzf.push_back(proce[i][k]);
					}
				}
			}
		}
	}
	getnum['@'] = ++n;
	numv = n; //终结符的数量等于当前n的值
	getzf.push_back('@');
	//非终结符压栈
	for (int i = 0; i < proce.size(); i++) {
		for (int k = 0; k < proce[i].length(); k++) {
			if (proce[i][k] != '-' && proce[i][k] != '|' && proce[i][k] != '>') {
				if (proce[i][k] > 64 && proce[i][k] < 91) {
					for ( y = 0; y < getzf.size(); y++) {
						if (proce[i][k] == getzf[y]) break;
					}
					if (y == getzf.size()) {
						getnum[proce[i][k]] = ++n;
						num = n; //终结符加非终结符的数量等于当前i的值
						getzf.push_back(proce[i][k]);
					}
				}
			}
		}
	}
}

void get_firstT() { //给终结符的first数组赋值
	int i;//不能在下面int
	//先给终结符的first数组赋值
	for ( i = 1; i <= numv; i++) {
		itoa(i, j, 10);
		first[i] = j; //之前写的是first[i].push_back(j[0])是错的,字符串数组的输入不需要加下标,且如果是j[0]一个字符不能装到一个字符串当中去
	}
}

string get_firstF(int *a) { //给非终结符的first数组赋值
	//犯了一个错误,下面的a没有加*
	for (int i = 0; i < proce.size(); i++) {
		if (getnum[proce[i][0]] == *a) {
			if (getnum[proce[i][1]] <= numv) {
				itoa(getnum[proce[i][1]], j, 10);
				first[*a] += j;
			} else {
				//first[getnum[proce[i][0]]].clear();
				first[getnum[proce[i][0]]] = get_firstF(&getnum[proce[i][1]]);
			}
		}
	}
	return first[*a];
}

void  get_follow(int *a) { //得到follow集
//犯了一个错误,以stirng开头但是没有返回值
	int i, j1;
	int flag = 0;
	for (i = 0; i < proce.size(); i++) {
		for (j1 = 1; j1 < proce[i].length(); j1++) {
			if (getnum[proce[i][j1]] == *a) { //这地方应该是j1我写成了k
				if (j1 == proce[i].length() - 1) {
					if (getnum[proce[i][j1]] != getnum[proce[i][0]])
						follow[*a] += follow[getnum[proce[i][0]]];
				} else {
					if (getnum[proce[i][j1 + 1]] <= numv) {
						itoa(getnum[proce[i][j1 + 1]], j, 10);
						follow[*a] += j;
					} else {
						for (int jj = 0; jj < first[getnum[proce[i][j1 + 1]]].size(); jj++) {
							if (atoi(&first[getnum[proce[i][j1 + 1]]][jj]) == numv) //等于空时
								follow[*a] += follow[getnum[proce[i][0]]];
							else
								follow[*a] += first[getnum[proce[i][j1 + 1]]][jj];
						}
						flag = 1; //标志位,如果已经找到*a后面的非终结符就可以停止了
					}
				}
			}
		}

		if (flag == 1) break; //停止寻找
	}
}

void get_table() {        //得预测分析表
	memset(table, -1, sizeof(table)); //刚开始tableM没有初始化,导致本该是空格的地方出现E->TA
	for (int i = 0; i < proce.size(); i++) { //扫所有产生式
		if (proce[i][1] == '@') { //直接推出空字的,特判下(follow集=产生式左边的vn中元素填)
			string flw = follow[getnum[proce[i][0]]];
			for (int k = 0; k < flw.size(); k++) {
				table[getnum[proce[i][0]]][flw[k] - '0'] = i;
			}
		}
		string temps = first[getnum[proce[i][1]]];
		for (int j = 0; j < temps.size(); j++) {      //考察first集
			if (atoi(&temps[j]) != numv) {
				// table[getnum[proce[i][0]]][atoi(&temps[j])]=i;//atoi不能这么用,他表示的是从当前位一直到末位
				table[getnum[proce[i][0]]][temps[j] - '0'] = i;
			} else {                                 //有空字的,考察follw集
				string flw = follow[getnum[proce[i][1]]];
				for (int k = 0; k < flw.size(); k++) {
					table[getnum[proce[i][0]]][flw[k] - '0'] = i;
				}
			}
		}
	}
}

string get_proce(int i) { //由对应下标获得对应产生式
	if (i < 0)return " "; //无该产生式
	string ss;
	ss += proce[i][0];
	ss += "->";		//把->要加上
	for (int j = 1; j < proce[i].size(); j++)
		ss += proce[i][j];
	return ss;
}

void print_table() { //输出预测分析表
	cout << "预测分析表:" << endl;
	for (int i = 0; i < numv; i++)
		cout << '\t' << getzf[i];
	cout << endl;
	for (int i = numv + 1; i <= num; i++) {
		cout << endl << "________________________________________________________" << endl;
		cout << getzf[i];
		for (int j = 0; j < numv; j++) {
			cout << '\t' << get_proce(table[i][j]) << "";
		}
	}
	cout << endl << "________________________________________________________" << endl;
	cout << endl;
}

string word;
bool analyze() {     //分析word的合法性,若合法,输出所有产生式
	stack<char>sta;
	sta.push('#');  //#最先进栈
	sta.push(proce[0][0]);
	int i = 0;
	while (!sta.empty()) {
		int cur = sta.top();
		sta.pop();
		if (cur == word[i]) {  //是终结符的话找下一个
			i++;
		} else  if (cur == '#') { //遇到#结束
			return 1;
		} else  if (table[getnum[cur]][getnum[word[i]]] != -1) { //查表

			int k = table[getnum[cur]][getnum[word[i]]];
			cout << proce[k][0] << "->";
			for (int j = 1; j < proce[k].size(); j++)
				cout << proce[k][j];
			cout << endl;
			for (int j = proce[k].size() - 1; j > 0; j--) { //逆序入栈
				if (proce[k][j] != '@')
					sta.push(proce[k][j]);
			}
		} else {
			return 0;
		}
	}
	return 1;
}

void scanf() { //输入字
	cout << "请输入字:" << endl;
	cin >> word;
	if (analyze())
		cout << "该字有效,所用产生式如上!" << endl;
	else
		cout << "出错了!" << endl;
}

int main() {
	int k;
	int j;
	read();
	//终结符的first集
	get_firstT();
	//非终结符的first集
	for (k = numv + 1; k <= num; k++) { //犯了一个错误,numv的位置写成7
		get_firstF(&k);
	}

	follow[numv + 1] += '0'; //这地方加的是0而不是#
	for (k = numv + 1; k <= num; k++) { //犯了一个错误,numv的位置写成7
		get_follow(&k);
	}
	cout << endl;
	get_table();
	print_table();
	scanf();
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/86670.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

22-31-spark-核心编程-RDD概念及理解

22-spark-核心编程-RDD概念&#xff1a; 分布式计算基础测试&#xff1a; big-data-study\Spark-demo\src\main\java\spark\core\com\zh\test02 Spark 核心编程 Spark 计算框架为了能够进行高并发和高吞吐的数据处理&#xff0c;封装了三大数据结构&#xff0c;用于处理不同的…

vscode 离线安装ssh

首先打开官方插件地址&#xff1a;https://marketplace.visualstudio.com/VSCode 然后输入ssh 下载这两个插件&#xff1a; 安装这两个插件&#xff1a; 这样便在windows下安装成功了ssh。 接下来需要在服务器端进行配置。 首先查看windows上的vscode版本&#xff1a; 这…

Arduino 崩溃或挂起的 7 种方式及如何防止

Arduino 崩溃或挂起的 7 种方式&#xff08;以及如何防止它发生&#xff09; 作者&#xff1a;Chris in Arduino 查看原文 为了帮助防止Arduino崩溃或挂起&#xff0c;我进行了一系列实验&#xff0c;以确定Arduino崩溃&#xff0c;挂起&#xff0c;重置&#xff0c;冻结&am…

5.大型电商项目之创建前端展示模板并调用

1. templates前端模板的使用 1.1 templates前端模板的创建 首先&#xff0c;我们页面很多地方是相似的&#xff0c;这里就创建一个基础模板&#xff0c;不同的地方&#xff0c;对模板内容的block进行修改即可&#xff1b;对于相同的地方&#xff0c;我们就使用include包含即可…

B. Hossam and Friends #837 div2

Problem - B - Codeforces 题意就是给你m个数对&#xff0c;这两个人不是好朋友&#xff0c;其他的所有人都是好朋友&#xff0c;问1~n里面有多少个区间里面所有数都是好朋友 分析: 这题我分析的没错&#xff0c;但是在计算区间的时候&#xff0c;想的复杂了&#xff0c;用模…

JS中,a标签里的javascript:;和 javascript:void(0)还有##

目录 1. javascript:;【常用】点击链接之后不会刷新页面&#xff0c;不会跳转链接&#xff0c;也不会传递参数 2. javascript:void(0) 【少用】点击链接后不会刷新页面&#xff0c;不会跳转链接&#xff0c;也不会传递参数 3. a标签中的# 点击链接后会刷新页面…

Spring Batch批处理-作业Job简介

引言 书接上篇Spring Batch 批处理入门案例解析&#xff0c;上篇带小伙伴们写了一个Spring Batch 入门案例解析&#xff0c;本篇就开始批处理正文啦&#xff0c;今天先对作业Job做个全面了解。 作业介绍 目前很多项目都流程的概念&#xff0c;比如web应用&#xff0c;集成应…

Java基础03_数据类型

数据类型 强类型语言 要求变量的使用要严格符合规定&#xff0c;所有的变量必须先定义后使用&#xff1b;安全性高但是速度低&#xff1b; 弱类型语言 例如&#xff1a;‘12’ 3 123 或者 ‘12’ 3 ‘123’ 在强类型语言中是有明显的区别的。而在弱类型语言中是不区分的…

算符优先分析器的构造

一、实验目的 &#xff08;1&#xff09;理解自底向上的语法分析的基本思想。 &#xff08;2&#xff09;理解算符优先文法的概念。 &#xff08;4&#xff09;掌握算符优先分析器的工作原理和工作流程。 &#xff08;3&#xff09;掌握算符分析表和优先函数的构造。 二、实验…

函数式接口

Lambda表达式的本质&#xff1a;作为函数式接口的实例 如果一个接口中&#xff0c;只声明一个抽象方法&#xff0c;则此接口就称为函数式接口 FunctionalInnterface public interface MyInterface{void method1(); }要想用Lambda表达式就一定要在函数式接口的条件下使用 相当于…

微信小程序直播状态接口如何获取

现如今&#xff0c;小程序直播非常的红火&#xff0c;越来越多的商家开通了微信小程序直播&#xff0c;但是在直播的过程中&#xff0c;偶尔会出现一些小问题&#xff0c;如禁播&#xff0c;异常状态等等&#xff0c;下面小编就来介绍一下微信小程序直播状态接口如何获取。 一、…

echarts5.4立体柱状图

资源下载&#xff1a;https://www.jsdelivr.com/package/npm/echarts 效果图&#xff1a; 借鉴资源&#xff1a;echarts 如何绘制三维 3D 立体柱状图 - 简书 代码示例&#xff1a; <!DOCTYPE html> <head><meta charset"utf-8"><title>ECh…

基于51单片机的智能小车系统设计

原理图&#xff1a; 程序运行图&#xff1a; 部分程序&#xff1a; /******************************************************************************* * 文件名称&#xff1a;main.c * 说明&#xff1a;本文件为小车控制的主函数 * 功能&…

分布式websocket探索

单体式架构 根据基于golang的gin框架开发的web项目所展开 如果一个Web项目采用单体式架构且配备了websocket通讯的功能&#xff0c;那么在单个实例中是能够正常运行的 在我的项目中&#xff0c;用户可以通过websocket来进行实时通讯和实时消息通知&#xff0c;同时如果在web业务…

AcrelEMS-IDC数据中心综合能效管理系统解决方案-Susie 周

1、概述 安科瑞电气紧跟数据中心发展形式&#xff0c;推出AcrelEMS-IDC数据中心综合能效管理解决方案&#xff0c;包含有电力监控、动环监控、消防监控、能耗统计分析、智能照明控制以及新能源监测几个子系统。集成了变配电监测、电源备自投、电气接点测温、智能照明控制、电能…

yearning搭建及使用

yearning搭建及使用 数据库审计管理&#xff0c;是数据安全规范中不可或缺的一环&#xff0c;通过审计管理我们能够把控、追溯sql执行情况。yearning作为一款开源的数据库审计软件&#xff0c;是我们开发运维工作中经常打交道的一个“伙伴”。 yearning提供的核心功能就是sql…

mysql 自增字段、属性

mysql自增属性 参考文章 https://www.php.cn/mysql-tutorials-489209.html https://blog.csdn.net/qq_41045806/article/details/108310772 在Mysql中&#xff0c;可以为某一属性设置自增属性&#xff0c;可以很好地为我们解决属性值重复的问题。 在mysql中&#xff0c;使用au…

DevExpress Universal全面的软件开发包

DevExpress Universal全面的软件开发包 DevExpress Universal帮助您使用所有DevExpress单平台控件等为Windows、Web、移动和平板电脑构建应用程序。它包括桌面控件(WinForms、WPF、UWP和桌面报告)、Web控件(ASP.NET、ASP.NET MVC和Core、Bootstrap Web Forms、JavaScript-jQuer…

第十章用Python获取sqlite、MySQL、Excel、csv、json中的数据

这里写目录标题项目背景获取sqlite3中的数据sqlite3库获取sqlite数据pandas库获取sqlite数据获取MySQL中的数据pymsql库获取MySQL数据pandas库获取mysql数据获取Excel中的数据xlrd库获取Excel数据pandas库获取Excel数据获取csv中的数据csv库读取csv数据pandas读取csv数据获取js…

Docker02(数据卷)

目录 一、宿主机与容器之间的文件拷贝 二、数据卷 三、数据卷容器 四、Dockerfile Dockerfile简介 自定义centos&#xff0c;具备vim及ifconfig作用 自定义tomcat8 一、宿主机与容器之间的文件拷贝 在生产环境中使用 Docker &#xff0c;往往需要对数据进行持久化&#…