编译原理:LL(1)文法 语法分析器(预测分析表法)

设计要求:对于任意输入的一个LL(1)文法,构造其预测分析表,并对指定输入串分析其是否为该文法的句子。

思路:首先实现集合FIRST(X)构造算法和集合FOLLOW(A)构造算法,再根据FIRST和FOLLOW集合构造出预测分析表,并对指定的句子打印出分析栈的分析过程,判断是否为该文法的句子。

指定文法:

//文法
E->TK
K->+TK
K->$
T->FM
M->*FM
M->$
F->i
F->(E)

对于输入串i+i*i# ,这里我们先给出实验结果截图:

这个实验花了我一天的时间,主要难点在于first集和follow集中的递归判断部分,其他的只要按照下面的判断流程走就可以了。

(1)求FIRST集的算法思想

如果产生式右部第一个字符为终结符,则将其计入左部first集

如果产生式右部第一个字符为非终结符

->求该非终结符的first集

->将该非终结符的非$first集计入左部的first集

->若存在$,则将指向产生式的指针右移

->若不存在$,则停止遍历该产生式,进入下一个产生式

->若已经到达产生式的最右部的非终结符,则将$加入左部的first集

处理数组中重复的first集中的终结符

(2)求FOLLOW集的算法思想

对于文法G中每个非终结符A构造FOLLOW(A)的办法是,连续使用下面的规则,直到每个FOLLOW不在增大为止.

(1) 对于文法的开始符号S,置#于FOLLOW(S)中;?

(2) 若A->aBb是一个产生式,则把FIRST(b)\{?ε}加至FOLLOW(B)中;

(3) 若A->aB是一个产生式,或A->aBb是一个产生式而b=>ε(即ε∈FIRST(?b))则把FOLLOW(A)加至FOLLOW(B)中

(3)生成预测分析表的算法思想

构造分析表M的算法是:

(1) 对文法G的每个产生式A->a执行第二步和第三步;

(2) 对每个终结符a∈FIRST(a),?把A->a加至M[A,a]中;

(3) 若ε∈FIRST(a),则把任何b∈FOLLOW(A)把A->a加至M[A,b]中;

(4) 把所有无定义的M[A,a]标上出错标志.

(4)对符号串的分析过程

预测分析程序的总控程序在任何时候都是按STACK栈顶符号X和当前的输入符号行事的,对于任何(X,a),总控程序

每次都执行下述三种可能的动作之一;

(1) 若X=a=”#”,则宣布分析成功,停止分析过程.

(2) 若X=a≠”#”,则把X从STACK栈顶逐出,让a指向下一个输入符号.

(3) 若X是一个非终结符,则查看分析表M,若M[A,a]中存放着关于X的一个产生式,那么,首先把X逐出STACK栈顶,然后

把产生式的右部符号串按反序一一推进STACK栈(若右部符号为ε,则意味着不推什么东西进栈).在把产生式的右部

符号推进栈的同时应做这个产生式相应得语义动作,若M[A,a]中存放着”出错标志”,则调用出错诊察程序ERROR.

在我的程序中,Base类为基类,负责求出FIRST和FOLLOW集;TableStack继承Base类,求出预测分析表和显示符号串的分析过程。

Base.h

struct node
{
	char left;
	string right;
};

class Base
{
protected:
	int T;
	node analy_str[100]; //输入文法解析

	set<char> first_set[100];//first集
	set<char> follow_set[100];//follow集
	vector<char> ter_copy; //去$终结符
	vector<char> ter_colt;//终结符
	vector<char> non_colt;//非终结符

public:
	Base() :T(0){}
	bool isNotSymbol(char c);
	int get_index(char target);//获得在终结符集合中的下标
	int get_nindex(char target);//获得在非终结符集合中的下标
	void get_first(char target); //得到first集合
	void get_follow(char target);//得到follow集合
	void inputAndSolve(); //处理得到first和follow
	void display(); //显示

};

TableStack.h

class TableStack :public Base
{
protected:
	vector<char> to_any; //分析栈
	vector<char> left_any;//剩余输入串
	int tableMap[100][100];//预测表
public:
	TableStack(){ memset(tableMap, -1, sizeof(tableMap)); }

	void get_table(); //得到预测表
	void analyExp(string s); //分析栈的处理
	void print_out();//输出
	void getAns(); //结合处理
};

接下来只列出FIRST集中的核心递归过程,有详细注释:

void Base::get_first(char target)
{
	int tag = 0;
	int flag = 0;
	for (int i = 0; i<T; i++)
	{
		if (analy_str[i].left == target)//匹配产生式左部
		{
			if (!isNotSymbol(analy_str[i].right[0]))//对于终结符,直接加入first
			{
				first_set[get_index(target)].insert(analy_str[i].right[0]);
			}
			else
			{
				for (int j = 0; j<analy_str[i].right.length(); j++)
				{
					if (!isNotSymbol(analy_str[i].right[j]))//终结符结束
					{
						first_set[get_index(target)].insert(analy_str[i].right[j]);
						break;
					}
					get_first(analy_str[i].right[j]);//递归
					//	cout<<"curr :"<<analy_str[i].right[j];
					set<char>::iterator it;
					for (it = first_set[get_index(analy_str[i].right[j])].begin(); it != first_set[get_index(analy_str[i].right[j])].end(); it++)
					{
						if (*it == '$')
							flag = 1;
						else
							first_set[get_index(target)].insert(*it);//将FIRST(Y)中的非$就加入FIRST(X)
					}
					if (flag == 0)
						break;
					else
					{
						tag += flag;
						flag = 0;
					}
				}
				if (tag == analy_str[i].right.length())//所有右部first(Y)都有$,将$加入FIRST(X)中
					first_set[get_index(target)].insert('$');
			}
		}
	}

}

这个语法分析的全部工程代码在我的 Github上,欢迎大家star学习,希望可以给大家带来帮助。

时间: 2024-10-12 02:33:14

编译原理:LL(1)文法 语法分析器(预测分析表法)的相关文章

编译原理LL(1)文法

从左向右扫描输入,然后产生最左推导(就是每次都把最左边的非终结字符用产生式代替). (一)First集合 比如有产生式 A-> + T | - P , 当我们读到串为 +开头的时候,我们可以很直接地判断选择 A-> + T 这个生成式:串为- 开头的时候,选择 A-> - P 这个生成式.但如果文法是类似于A →T | P 这样的都以非终结字符开头的呢?一眼就很难判断的,我们就需要知道,T 是怎么展开的,如果 T -> a |b ,P->c|d , 那当串以a或b开头的时候,

编译原理之形式语言文法分类

高级程序设计语言的三个基本因素: 语法:描述语言成分的构成规则(包括词法规则和语法规则) 语义:描述语法成分的含义 语用:描述语法成分的使用方法 形式语言理论(formal language theory)是用数学方法研究自然语言(如英语)和人工语言(如程序设计语言)的产生方式.一般性质和规则的理论.形式语言是模拟这些语言的一类数学语言,它采用数学符号,按照严格的语法规则构成.从广义上说,形式语言是符号取自某个字母表的字符串的集合.如同自然语言具有语法规则一样,形式语言也是由形式文法生成的.一个

编译原理(四)语法分析之自顶向下分析

语法分析之自顶向下分析 说明:以老师PPT为标准,借鉴部分教材内容,AlvinZH学习笔记. 基本过程分析 1. 一般方法:对任一字符串,试图用一切可能的方法,从树根节点(开始符号)出发,根据文法自上而下地为输入符号串建立一棵语法树.直观理解为从开始符号出发,依据规则建立推导序列,最后推至目标字符串. 2. 特点:分析过程是带有预测的,是一种试探过程.试探失败就会出现回溯问题,降低了分析的效率. 3. 存在问题:左递归问题.回溯问题. 问题一:左递归问题 1. 左递归文法:文法规则中有形如 \(

北航编译原理总结 C文法

定位:传说中北航计算机学院最头疼课程其实也没有辣么难,一点点的完成,并不会出现传说中的刷夜~ 0.pascal-s和PL/0编译器源码有必要结合编译器基础知识认真读一下,当然不必细枝末节,重点是看一下人家的编译器中所谓的"词法分析""语法分析"等阶段以及符号表的建立需要什么量,每个量分别代表什么,以及运行栈出现在什么时候,如何设计(可以和后期优化结合起来~)等. 1.我的文法为 扩充c0文法-高,具体文法如下: <加法运算符> ::= +|- <乘

编译原理之理解文法和语言

简介 一个程序设计语言是一个记号系统,如同自然语言一样,它的完整定义应包括语法和语义两个方面.所谓一个语言的语法是指一组规则,用它可以形成和产生一个合适的程序.目前广泛使用的手段是上下文无关文法,即用上下文无关文法作为程序设计语言语法的描述工具.语法只是定义什么样的符号序列是合法的,与这些符号的含义毫无关系,比如对于一个Pascal程序来说,一个上下文无关文法可以定义符号串A:=B+C是一个合乎语法的赋值语句,而A:=B+就不是.但是,如果B是实型的,而C是布尔型的,或者B.C中任何一个变量没有

编译原理实验二:LL(1)语法分析器

一.实验要求 不得不想吐槽一下编译原理的实验代码量实在是太大了,是编译原理撑起了我大学四年的代码量... 这次实验比上次要复杂得多,涵盖的功能也更多了,我觉得这次实验主要的难点有两个(其实都是难点...): 1. 提取左公因子或消除左递归(实现了消除左递归) 2. 递归求First集和Follow集 其它的只要按照课本上的步骤顺序写下来就好(但是代码量超多...),下面我贴出实验的一些关键代码和算法思想. 二.基于预测分析表法的语法分析 2.1 代码结构 2.1.1  Grammar类    功

编译原理--01 复习大纲(清华大学出版社第3版)

前言 目前以手中这本清华大学出版社出版的编译原理(第3版,张素琴等编著)作为复习总结,因为考试都是大题,一部分概念会被忽略.所有内容都需要通过举例和推导来帮助加深理解,优先为过几天的考试服务.该文实现了教材中那些特别复杂的推导符号,并且这几天会加紧持续更新. 第2章 文法和语言 符号和符号串 空符号串用\(\varepsilon\)表示,长度为0 若 \(\Sigma=\{0,1\}\) ,则 \(\Sigma^*=\{\varepsilon,0,1,00,11,000,001,...\}\),

构造可配置词法语法分析器生成器(下)

本文为笔者原创,转载请注明出处 http://blog.csdn.net/xinghongduo mylex & xparser mylex & xparser是笔者实现的类似于Lex和Yacc的词法语法分析器生成器,它接受正则表达式定义的词法规则和BNF定义的语法规则,自动构造对应的以C为宿主语言的词法分析器源程序mylex.h, mylex.c和语法分析器源程序xparser.h, xparser.c. mylex & xparser特点如下: 轻量无依赖:构造器的主要代码仅2

编译原理归纳学习——去除晦涩

NFA 1. (a|b)*ab的NFA 2. aa*|bb*的NFA DFA 1. (a|b)*ab的DFA    ? NFA到DFA的变换 从NFA构造等价的DFA的一般思想是让新构造的DFA的每个状态代表NFA的一个状态集,这个DFA用它的状态去记住该NFA在读入符号后能到达的所有状态. 写一个文法,使其语言是奇数集,且每个奇数不以0开头. 解:文法G(N): N→AB|B A→AC|D B→1|3|5|7|9 D→B|2|4|6|8 C→0|D First集合的求法: First集合最终是