【编译原理】 实验三 LL(1)分析法(LL1分析表的自动生成)

写在前面

由于代码较长,csdn对文章总长度有字数限制,想只看完整代码的请移步另一篇博客。

https://blog.csdn.net/qq_46640863/article/details/125705891
理论与习题讲解移步视频
https://www.bilibili.com/video/BV1zu4y1C7SL

一、实验内容

1.实现LL(1)分析算法
2.输入:教材中的算术表达式文法;待分析的语句(如i+i*i)
3.输出:语句的分析过程(参见ppt例题)
4.要求:LL(1)分析表程序自动生成。如果使用已知的分析表,实验分数会降低。

二、实验目的

通过设计、编制、调试一个具体的文法分析程序,深入理解LL(1)预测分析法的基本分析原理,理解FIRST集、FOLLOW集的构造方法并对其加以实现,构造LL(1)预测分析表并利用分析表对语句、文法进行分析。

三、实验分析

书中算术表达式文法示例如下:
G [ E ] : ( 1.1 ) E → E + T ∣ T T → T ∗ F ∣ F F → ( E ) ∣ i G[E]:(1.1)\\ E\to E+T|T\\ T\to T*F|F\\ F\to(E)|i G[E]:(1.1)EE+TTTTFFF(E)i
这是一个算术表达式文法,满足*优先于+,且*和+都服从左结合。这个文法不存在二义性,因为运算符优先级被分为了三个层次:“+”为一层,“*”为一层,括号“()”与运算对象“ i i i”为另一层,这三层优先级依次递增。用非终结符 E E E表示文法的开始符号,再增加 T T T F F F表示不同的层次。即:离开始符越近的产生式优先级越低,离开始符越远的产生式优先级越高。
使用一个消除了二义性的文法可以进行自顶向下的语法分析如递归下降分析与LL(1)分析法。但是,还需要先消除左递归与回溯。
设关于非终结符 A A A的直接左递归产生式为 A → A α ∣ β A\to A\alpha |\beta AAαβ,对其消除左递归,可以将其改写为:
{ A → β A ′ A ′ → α A ′ ∣ ϵ \begin{cases} A\to \beta A'\\ A'\to \alpha A'|\epsilon \end{cases} {AβAAαAϵ
其中, A ′ A' A为新引入的非终结符, ϵ \epsilon ϵ为空字不可缺少。
对文法 G [ E ] ( 1.1 ) G[E](1.1) G[E](1.1)消除左递归可以得到文法 G [ E ] ( 1.2 ) G[E](1.2) G[E](1.2)
G [ E ] ( 1.2 ) E → T E ′ E ′ → + T E ′ ∣ ϵ T → F T ′ T ′ → ∗ F T ′ ∣ ϵ F → ( E ) ∣ i G[E](1.2)\\ E\to TE'\\ E'\to+TE'|\epsilon\\ T\to FT'\\ T'\to *FT'|\epsilon\\ F\to(E)|i G[E](1.2)ETEE+TEϵTFTTFTϵF(E)i
回溯发生的原因在于候选式存在共同的左公因子。如关于 A A A产生式为 A → δ β 1 ∣ δ β 2 ∣ . . . ∣ δ β i ∣ β i + 1 ∣ . . . ∣ β j A\to \delta\beta_1|\delta\beta_2|...|\delta\beta_i|\beta_{i+1}|...|\beta_j Aδβ1δβ2∣...∣δβiβi+1∣...∣βj,则可以将其改写为:
{ A → δ A ′ ∣ β i + 1 ∣ . . . ∣ β j A ′ → β 1 ∣ . . . ∣ β i \begin{cases} A\to\delta A'|\beta_{i+1}|...|\beta_j\\ A'\to \beta_1|...|\beta_i \end{cases} {AδAβi+1∣...∣βjAβ1∣...∣βi
由于算术表达式文法不存在左公因子,所以无需消除回溯。
在程序实现时,可以消除文法中的或符号,即对其进行处理:
G [ E ] ( 1.3 ) E → T E ′ E ′ → + T E ′ E ′ → ϵ T → F T ′ T ′ → ∗ F T ′ T ′ → ϵ F → ( E ) F → i G[E](1.3)\\ E\to TE'\\ E'\to+TE'\\ E'\to\epsilon\\ T\to FT'\\ T'\to *FT'\\ T'\to\epsilon\\ F\to(E)\\ F\to i G[E](1.3)ETEE+TEEϵTFTTFTTϵF(E)Fi
为了LL(1)构造分析表 M M M,需要构造与文法相关的集合FIRST与FOLLOW。
FIRST( α \alpha α)表示 α \alpha α的所有可能推导的开头终结符或可能的空字 ϵ \epsilon ϵ。FIRST构造方法为反复运用下述规则,直到每个集合的FIRST集合不再变化为止。
1.若存在产生式 X → a X\to a Xa,且 a a a为终结符,则把 a a a加入FIRST( X X X)中。若存在 X → ϵ X\to\epsilon Xϵ,则 ϵ \epsilon ϵ也加到FIRST( X X X)中。
2.若存在 X → Y . . . X\to Y... XY...,且 Y Y Y为非终结符,则将FIRST( Y Y Y)中所有非 ϵ \epsilon ϵ元素加入到FIRST( X X X)中。若 X → Y 1 Y 2 . . . Y k X\to Y_1Y_2...Y_k XY1Y2...Yk Y 1 Y_1 Y1~ Y i Y_i Yi都是非终结符且产生式中存在 ϵ \epsilon ϵ,则将FIRST( Y j Y_j Yj) ( j = 1 , 2 , . . . , i ) (j=1,2,...,i) (j=1,2,...,i)的所有非 ϵ \epsilon ϵ元素加入到FIRST( X X X)中,当 Y 1 Y_1 Y1~ Y k Y_k Yk都含有 ϵ \epsilon ϵ产生式时,把 ϵ \epsilon ϵ加入到FIRST( X X X)中。
对本实验中涉及的算术表达式文法构造FIRST集合。FIRST( E ′ E' E)= { + , ϵ } \{+,\epsilon\} {+,ϵ},FIRST( T ′ T' T)= { ∗ , ϵ } \{*,\epsilon\} {,ϵ},FIRST( F F F)= { ( , i } \{(,i\} {(,i}。由 T → F . . . T\to F... TF... E → T . . . E\to T... ET...,可得FIRST( T T T)=FIRST( E E E)=FIRST( F F F)= { ( , i } \{(,i\} {(,i}
对每个非终结符构造FOLLOW集合,需要连续使用以下规则:
1.对文法开始符号 S S S,将 # \# #加入FOLLOW( S S S)。
2.若存在产生式 A → . . . B β A\to ...B\beta A...,将FIRST( β \beta β)中除 ϵ \epsilon ϵ的元素加入FOLLOW( B B B)。
3.若存在产生式 A → . . . B A\to ...B A...B A → . . . B β A\to ...B\beta A... ϵ ∈ \epsilon\in ϵFIRST( β \beta β),则把所有元素加到FOLLOW( B B B)。
构造FOLLOW集合时,按规则1,FOLLOW( E E E)= { # } \{ \#\} {#}。按规则2,由 E → T E ′ E\to TE' ETE,FOLLOW( T T T)= { + } \{+\} {+}。由 T → F T ′ T\to FT' TFT,FOLLOW( F F F)= { ∗ } \{*\} {},由 F → ( E ) F\to (E) F(E),FOLLOW( E E E)= { ) , # } \{),\# \} {),#}。按规则3,由 E → T E ′ E\to TE' ETE,FOLLOW( E ′ E' E)= { ) , # } \{),\#\} {),#}。由 E → T E ′ E\to TE' ETE E → ϵ E\to\epsilon Eϵ,FOLLOW( T T T)= { + , ) , # } \{+,),\#\} {+,),#}。由 T → F T ′ T\to FT' TFT,FOLLOW( T ′ T' T)= { + , ) , # } \{+,),\# \} {+,),#}。由 T → F T ′ T\to FT' TFT T ′ → ϵ T'\to \epsilon Tϵ,FOLLOW( F F F)= { ∗ , + , ) , # } \{*,+,),\# \} {,+,),#}
在构造完FIRST集合与FOLLOW集合后,可以构造分析表 M M M。对于文法中的每一个产生式,对每个终结符 a ∈ a\in aFIRST( A A A),把 A → α A\to \alpha Aα加入 M [ A , a ] M[A,a] M[A,a]中,其中 a ∈ a\in aFIRST( α \alpha α)。若 ϵ ∈ \epsilon\in ϵFIRST( A A A),则对任何属于FOLLOW( A A A)的终结符 b b b,将 A → ϵ A\to\epsilon Aϵ加入到 M [ A , b ] M[A,b] M[A,b]。将其他表项都认为是出错。算术表达式文法的分析表如下:

i i i + + + ∗ * ( ( ( ) ) ) # \# #
E E E E → T E ′ E\to TE' ETE E → T E ′ E\to TE' ETE
E ′ E' E E ′ → + T E ′ E'\to +TE' E+TE E ′ → ϵ E'\to\epsilon Eϵ E ′ → ϵ E'\to\epsilon Eϵ
T T T T → F T ′ T\to FT' TFT T → F T ′ T\to FT' TFT
T ′ T' T T ′ → ϵ T'\to\epsilon Tϵ T ′ → ∗ F T ′ T'\to *FT' TFT T ′ → ϵ T'\to\epsilon Tϵ T ′ → ϵ T'\to\epsilon Tϵ
F F F F → i F\to i Fi F → ( E ) F\to (E) F(E)

输入串以 # \# #作为结束标志,分析栈栈底先放入 # \# #。分析程序根据分析栈栈顶符号 x x x和当前输入符号 a a a决定分析器动作。若 x = a = # x=a=\# x=a=#,分析成功。若 x = a ≠ # x=a\ne \# x=a=#,栈顶符号与输入符号匹配,则 x x x出栈,继续对下一个字符进行分析。若栈顶为非终结符 A A A,则查表。若为一个 A A A的产生式,则将 A A A出栈,将产生式右部符号逆序压入栈。若产生式为 A → ϵ A\to\epsilon Aϵ,则只将 A A A出栈。若表中该表项为空,则出错。

四、实验流程

4.1 整体流程

首先从文件中读入文法,识别出文法的终结符与非终结符,对文法进行消除左递归、消除回溯、消除或。再次识别出文法的终结符与非终结符,对每个符号与短语求出其FIRST集合与FOLLOW集合,自动分析生成建立分析表。输入待分析的文法,对其进行分析。整体流程如下图所示。
图1 整体流程

4.2 求FIRST

对字符或短语求FIRST集合时,往往需要用到递归。若在递归时发现 c c c字符的FIRST集合已经求解完毕,则直接返回。若当前字符 c c c是终结符,则FIRST集合为自己,返回。若不是终结符,则对其产生式进行遍历。若产生式中就是空字,则将空字加入FIRST集合。否则,遍历产生式的字符。递归的求解当前字符的FIRST集合,将该字符FIRST集合除了空字以外的元素加入。若该字符的FIRST集合存在空字,则遍历下一字符,如此循环。求解短语的FIRST集合的过程与求解某一非终结符的产生式的FIRST集合的流程是类似的。求FIRST集合的流程如下图。
图2求FIRST流程

4.3 求FOLLOW

对字符或短语求FOLLOW集合时,也需要用到递归。对于文法开始符号,先将#加入其FOLLOW集合。对于每个非终结符,判断每条产生式,若存在 A → . . . B b A\to ...Bb A...Bb,则将 b b b B B B入的FOLLOW集合。对每个产生式从右往左遍历,对于当前标识符之后的字符,若是非终结符,则将这个非终结符的FIRST集加入当前字符的FOLLOW集,这一过程需要递归地进行。若 A → B β A\to B\beta A ϵ ∈ \epsilon\in ϵFIRST( β \beta β),将FOLLOW( A A A)加入FOLLOW( B B B)。每次都要反复进行此过程。
图3 求FOLLOW流程

4.4 求分析表并分析输入串

构造分析表的流程与上文是一致的:若 a a a属于FIRST( A A A),把 A → a A\to a Aa加入到 M [ A , a ] M[A,a] M[A,a]中。若 ϵ \epsilon ϵ属于FIRST( A A A),则对FOLLOW( A A A)的终结符 b b b加入 A → ϵ A\to\epsilon Aϵ。分析输入串的流程与上文一致,在此也不赘述。

五、实验代码

5.1 数据结构

利用S表示文法的开始符,利用VNVT分别表示非终结符与终结符。
字符的FIRST集合用map存储。map的键为字符,值为FIRST集合,用set存储。字符的FIRST集表被定义为:map<char, set<char>> firstSet
短语的FIRST集合也用map存储。map的键为字符串,即短语。值为FIRST集合,用set存储。其被定义为:map<string, set<char>> firstSetS
字符的FOLLOW集合用map存储。map的键为字符,值为FOLLOW集合,用set存储。字符的FOLLOW集表被定义为:map<char, set<char>> followSet;
利用MAP表示产生式表,利用map存储。map的键为产生式的左部,值为产生式右部。因为一个非终结符可能有多个右部,所以值用vector<string>存储,可以存储多个右部字符串。map<char, vector<string>> expressionSet;
预测分析表本体利用二维数组存储。vector<vector<string>> table;
文件读取到的文法、消左递归的文法、消除回溯的文法、增广的文法,可以用于求解FIRST与FOLLOW的文法存储在不同的变量名中。

vector<string> filebuffer;//文件读取的缓冲区
vector<string> expression_withoutrecursion;//消左递归
vector<string> expression_withoutback;//消完左递归和回溯
vector<string> inputExpression;//最后增广完成,用于进行分析的文法

5.2 部分核心算法设计

5.2.1 消除左递归与回溯并消除或

需要着重介绍的就是此部分的代码。在读取到的文件中,用字符”->”代替箭头。对文法消除左递归时,判断文法某一个产生式的第0项与第3项即”->”后的字符是否相同。若相同,则发现左递归。利用下式对 A → A α ∣ β A\to A\alpha|\beta AAαβ进行改写。为了方便程序的编写,所有符号都只用一个字符表示。从大写字母表中找一个没有被使用过的字母作为非终结符表示下式中的 A ′ A' A,用字母 e e e代替 ϵ \epsilon ϵ,表示空字。每次利用find()函数找到”|”,利用substr()截取子字符串,找到产生式中的 α \alpha α短语与 β \beta β短语。
{ A → β A ′ A ′ → α A ′ ∣ ϵ \begin{cases} A\to\beta A'\\ A'\to\alpha A'|\epsilon \end{cases} {AβAAαAϵ
在消除回溯时,判断第3个字符与每处”|”后的字符是否相同。若相同,则此产生式存在回溯,存在共同的左公因子。用同样的方法,找到每处”|”,用下式中的文法替换原有文法。同样的,用没有使用过的字符代替 A ′ A' A
{ A → δ A ′ ∣ β i + 1 ∣ . . . ∣ β j A ′ → β 1 ∣ . . . ∣ β i \begin{cases} A\to\delta A'|\beta_{i+1}|...|\beta_j\\ A'\to \beta_1|...|\beta_i \end{cases} {AδAβi+1∣...∣βjAβ1∣...∣βi
消除“或”时,同样需要找到每处”|”,并对含有”|”符号的字符串进行重新拼接处理。

void leftRecursion(){
	for (string str : filebuffer){
		if (str[0] == str[str.find('>') + 1]){
			char newch = 'A';
			for (newch = 'A'; newch <= 'Z'; newch++)
				if (VN.count(newch) == 0)
					break;
			VN.insert(newch);
			string newstr = "";
			string alpha = str.substr(4, str.find('|') - 4);
			//	cout << "\nalpha" << alpha << endl;
			//A->betaA'
			newstr = newstr + str[0] + (string)"->" + str.substr(str.find('|') + 1) + newch;
			expression_withoutrecursion.push_back(newstr);
			newstr = "";
			//A'->alphaA'|e
			newstr = newstr + newch + "->" + alpha + newch + "|" + 'e';
			//e表示空字符episilon
			VT.insert('e');
			expression_withoutrecursion.push_back(newstr);
		}
		else
			expression_withoutrecursion.push_back(str);
	}
	cout << "\n消除左递归:\n";
	for (string str : expression_withoutrecursion)
		cout << str << endl;
}
//消除回溯
void leftTraceback(){
	for (string str : expression_withoutrecursion){
		//发现回溯
		if (str[3] == str[str.find("|") + 1]){
			char newch = 'A';
			for (newch = 'A'; newch <= 'Z'; newch++)
				if (VN.count(newch) == 0)
					break;
			VN.insert(newch);
			string delta = "" + str[3];
			string newstr = "";
			//A->alphaA'
			newstr += str[0] + "->" + delta + newch;
			expression_withoutback.push_back(newstr);
			newstr = "";
			//A'->beta1|beta2
			string beta1 = str.substr(4, str.find('|') - 4);
			string beta2 = str.substr(str.find('|') + 1);
			newstr += newch + "->" + beta1 + '|' + beta2;
			expression_withoutback.push_back(newstr);
		}
		else
			expression_withoutback.push_back(str);
	}
	cout << "\n消除回溯:\n";
	for (string str : expression_withoutback)
		cout << str << endl;
}
//增广,删除竖线|
void deleteor(){
	for (string str : expression_withoutback){
		if (str.find('|') != string::npos){
			//将A->beta1|beta2拆成A->beta1和A->beta2
			string right1 = str.substr(3, str.find('|') - 3);
			//	cout << "right1" << right1 << endl;
			string newstr1 = "";
			newstr1 = str[0] + (string)"->" + right1;
			//	cout << "newstr1" << newstr1 << endl;
			inputExpression.push_back(newstr1);
			string right2 = str.substr(str.find('|') + 1);
			//	cout << "right2" << right2 << endl;
			string newstr2 = "";
			newstr2 += str[0] + (string)"->" + right2;
			//	cout << "newstr2" << newstr2 << endl;
			inputExpression.push_back(newstr2);
		}
		else
			inputExpression.push_back(str);
	}
	cout << "\n增广\n";
	for (string str : inputExpression)
		cout << str << endl;
}

5.2.2 求解FIRST

求字符与短语的FIRST集合的过程与4.2章节中的流程图是一致的。这是一个递归的过程。因为涉及到了求单个字符与短语,所以需要对函数进行重载。

//字符的求first集
void getFirst(char c){
	//已经求完了当前字符的first集
	if (firstSet.count(c))
		return;
	set<char> newset;
	//若c为终结符,则first为自己,结束
	if (VT.count(c)){
		newset.insert(c);
		firstSet[c] = newset;
		return;
	}
	for (string s : expressionSet[c]){
		//若存在X->e,将e加入
		if ('e' == c)
			newset.insert('e');
		else{
			for (char cur : s){
				if (!firstSet.count(cur))
					getFirst(cur);
				set<char> curFirst = firstSet[cur];
				for (auto curf : curFirst)
					newset.insert(curf);
				//将Y的first集合中的元素中不是e的加入
				if (!curFirst.count('e'))
					break;
				//如果加到了e,则循环回去,即X->Y1Y2..Yk时,Y1产生e,就需要加入Y2的first集合
			}
		}
	}
	firstSet[c] = newset;
}
//多个字符(字符串、短语)first集求法
void getFirst(string s){
	if (firstSetS.count(s))
		return;
	set<char> newset;
	int i = 0;
	while (i < s.length()){
		char cur = s[i];
		if (!firstSet.count(cur))
			getFirst(cur);
		//将当前字符的first集作为短语的first集
		set<char> rightSet = firstSet[cur];
		for (auto rs : rightSet)
			newset.insert(rs);
	//类似的,如果加到了e,则循环回去,即X->Y1Y2..Yk时,Y1产生e,就需要加入Y2的first集合
		if (rightSet.count('e'))
			i++;
		else
			break;
		//遍历完了当前语句后,将空字加入当前语句first集
		if (i == s.length())
			newset.insert('e');
	}
	firstSetS[s] = newset;
}

5.2.3 求解FOLLOW

求解FOLLOW集合的流程是迭代的。在调用初始化函数时,需要针对每一个字符反复的对其求解FOLLOW集合。

//求每个非终结符的follow
	for (char c : VN)
		getFollow(c);

求解某一字符FOLLOW集合的流程与章节4.3的流程图是一致的。

//某个字符的follow集合
void getFollow(char c){
	vector<string> list = expressionSet[c];
	set<char> leftFollowSet;
	if (followSet.count(c))
		leftFollowSet = followSet[c];
	//对文法开始符号,将#加入follow集合
	if (c == S)
		leftFollowSet.insert('#');
	for (char ch : VN) {
		for (string s : expressionSet[ch]) {
			for (int i = 0; i < s.length(); i++) {
				//若有A->aBb(a可为空),则将b加入B的follow集合
				if (c == s[i] && i + 1 < s.length() && VT.count(s[i + 1])) 
					leftFollowSet.insert(s[i + 1]);
			}
		}
	}
	followSet[c] = leftFollowSet;
	for (string s : list){
		int i = s.length() - 1;
		//对每个产生式从右往左遍历
		while (i >= 0)
		 {
			char cur = s[i];
			//对于当前标识符B
	//对于当前标识符之后的字符,若是非终结符,则将这个非终结符的first集加入当前字符的follow集
			if (VN.count(cur)){
				string right = s.substr(i + 1);
				set<char> rightFirstSet;
				if (!followSet.count(cur)) 
					getFollow(cur);
				set<char> curFollowSet = followSet[cur];
				//若A->aB或A->aB\beta且\beta->e
				//则把follow(A)加到follow(B)
				if (right.length() == 0)
					for (auto lfs : leftFollowSet) 
						curFollowSet.insert(lfs);
				else
					//将若A->Bbeta,将B后的字符的first(除e)加入
					if (right.length() == 1)
						if (!firstSet.count(right[0])) 
							getFirst(right[0]);
						rightFirstSet = firstSet[right[0]];
					else
						if (!firstSetS.count(right))
							getFirst(right);
						rightFirstSet = firstSetS[right];
					for (char var : rightFirstSet)
						if (var != 'e')
							curFollowSet.insert(var);
					//若A->Bbeta且e属于first(beta),将follow(A)加入follow(B)
					if (rightFirstSet.count('e'))
						for (auto lfs : leftFollowSet)
							curFollowSet.insert(lfs);
				}
				followSet[cur] = curFollowSet;
			}
			i--;
		 }
	}

}

5.2.4 文法分析

分析的过程与第三章节是一致的。首先,放 # \# #入分析栈,再放文法开始符 S S S入分析栈。当栈顶元素不是 # \# #时,如果栈顶元素与输入字符是一致的,则将栈顶弹出,扫描指针向后移动。若栈顶为其他终结符,则出错。若分析表中当前表项为空,则出错。若当前表项为空字,则出栈。否则,从分析表中找到当前非终结符 X X X表项 M [ X , a ] M[X,a] M[X,a],倒序将当前表项中产生式的右部入栈。其他情况均为出错情况。

void analyzeLL(){
	cout << "\nLL分析过程\n";
	cout << setw(20) << "stack" << setw(10) << "input" << setw(20) << "expression";
	cout << endl;
	analyzeStack.push('#');
	//#入栈
	analyzeStack.push(S);
	//开始符入栈
	displayLL();
	char X = analyzeStack.top();
	while (X != '#'){
		char a = strInput[index];
		if (X == a){
			action = "";
			analyzeStack.pop();
			index++;
		}
		else if (VT.count(X))
			return;//找到终结符,出错
		else if (find(X, a) == "")
			return;
		else if (find(X, a) == "e"){
			analyzeStack.pop();
			action = "";
			action += X;
			action += "->e";
		}//空字,出栈
		else{//其余情况,需要按分析表对栈顶元素进行推导
			string str = find(X, a);
			if (str != ""){
				action = "";
				action += X;
				action += "->";
				action += str;
				analyzeStack.pop();
				int len = str.length();
				for (int i = len - 1; i >= 0; i--)
				analyzeStack.push(str[i]);//栈顶元素出栈,按分析表和下一字符将分析表中元素入栈
			}
			else
			{
				cout << "\nERROR!\n";//出错,直接返回
				return;
			}
		}
		X = analyzeStack.top();
		displayLL();
	}
}

5.3 完整程序

https://blog.csdn.net/qq_46640863/article/details/125705891

六、运行结果

从文件中读取输入文法为算术表达式文法如下,这一文法存在左递归,且没有增广。图4 输入文法
因此,需要识别终结符与非终结符,对其进行消除左递归、消除回溯、增广处理,运行结果如下。为了简化程序,用A代替 E ′ E' E,用B代替 T ′ T' T,用e代替 ϵ \epsilon ϵ,其结果与第三章节推导的结果是完全一致的。
图5 消除左递归消除回溯与增广
对于消除了”|”完成的文法,再次识别终结符与非终结符,并对每个非终结符输出其产生式右部。
图6 再次识别终结符与非终结符
在预处理工作完成后,即可对每个终结符与非终结符求解FIRST集合,求解非终结符的FOLLOW集合。其结果与第三章节的结果是完全一致的。
图7 求FIRST与FOLLOW
求解FIRST集合和FOLLOW集合完成后,构建分析表,其结果与第三章节的分析是一致的。
图8 输出分析表
输入表达式 i ∗ i + i i*i+i ii+i,自动给出分析过程。
首先,将 # \# #与开始符 E E E压栈;根据 M [ E , i ] = E → T A M[E,i]=E\to TA M[E,i]=ETA,将 E E E出栈,将 T A TA TA逆序后入栈;根据 M [ T , i ] = T → F B M[T,i]=T\to FB M[T,i]=TFB,将 T T T出栈,将 F B FB FB逆序后入栈;根据 M [ F , i ] = F → i M[F,i]=F\to i M[F,i]=Fi,将 F F F出栈,将 i i i入栈;此时,栈顶与输入串匹配,弹出;此时栈顶为 B B B,根据 M [ B , + ] = B → ϵ M[B,+]=B\to\epsilon M[B,+]=Bϵ,空字符不入栈;此时栈顶为 A A A,根据 M [ A , + ] = A → + T A M[A,+]=A\to +TA M[A,+]=A+TA,将 + T A +TA +TA逆序后入栈。此时栈顶的+与输入串的+匹配,出栈。之后的分析步骤与流程与前文类似,在此不再赘述。
图9 分析文法
在对正确地文法分析完毕后,分析过程正确地停止,程序退出。

七、实验感悟

### 关于斯坦福小镇的详细介绍 斯坦福小镇是由斯坦福大学人工智能研究所开发的一个虚拟环境,旨在探索多智能体系统的交互和社会动态[^1]。该虚拟小镇包含了25个具有独特个性和背景故事的人工智能代理(Agents),它们能够在设定的空间内自由移动、相互沟通以及参与各类社交活动。 此项目的灵感来源于一款经典的模拟人生沙盒游戏,并由斯坦福与谷歌的一些顶尖专家共同打造而成[^2]。通过这种形式,研究者希望观察到更加复杂且贴近真实世界的社会现象如何在数字化场景下自然发生和发展。 当前,在这一领域内的研究方向还包括借助像GhatGPT这样的先进语言模型所提供的强大功能——例如自动规划任务流程、维持连贯性的对话机制以及高效的信息归纳技巧等——来进一步增强这些虚拟角色的表现力及其所处情境的真实性程度[^3]。 此外值得注意的是,“斯坦福小镇”不仅仅是一项技术创新成果;它还承载着深刻的社会科学研究意义。这项试验代表了当下为了理解AI技术可能给人类社会带来的影响所做的努力之一,同时也预示着未来会有越来越多关于AI融入日常生活的课题被提上议程[^5]。 ```python def simulate_town_activity(agents, environment): """ Simulates activities within Stanford Town. Args: agents (list): A list of AI agent objects with unique personalities and backstories. environment (dict): The virtual town's map resources including locations where interactions can occur. Returns: str: Summary of the day’s events generated by summarizing all dialogues between agents. """ daily_dialogs = [] for agent in agents: # Each agent performs actions based on their personality traits action_result = perform_action(agent, environment) # Agents engage in conversations according to predefined rules or learned behaviors conversation_partner = select_conversation_partner(environment['current_location'], exclude=agent) dialogue_exchange = generate_dialogue(agent, conversation_partner) daily_dialogs.append(dialogue_exchange) summary_of_day = summarize_events(daily_dialogs) return summary_of_day def compare_model_outputs(model_a_output, model_b_output): """Compares outputs from two different models.""" processed_data = preprocess_for_comparison([model_a_output, model_b_output]) similarity_score = calculate_similarity(processed_data[0], processed_data[1]) return {"similarity": similarity_score} ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zombo_tany

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值