背景
期末考試免考,衝!
實驗名稱
消除一切左遞歸
實驗時間
2020年5月27日 到 2020年5月31日
院系
信息科學與工程學院
組員姓名
Chocolate、kry2025、鍾先生、leo、小光
實驗環境介紹
- windows 10 操作系統
- Eclipse 進行 java 編程
- CodeBlocks 進行 C++ 編程
實驗目的與要求
目的
- 深刻理解左遞歸的算法
- 掌握消除左遞歸的過程
- 加強團隊合作能力
- 提高自身的編程能力和解決問題的能力
要求
- 編程實現消除一切左遞歸
- 算法簡潔,不冗餘
解決問題
產生式直接消除左遞歸
形如 P → Pα | β 可以通過直接消除轉化爲:
產生式間接消除左遞歸
有時候雖然形式上產生式沒有遞歸,但是因爲形成了環,所以導致進行閉包運算後出現左遞歸,如下:
雖不具有左遞歸,但S、Q、R都是左遞歸的,因爲經過若干次推導有
- SQcRbcSabc
- QRbSabQcab
- RSaQcaRbca
就顯現出其左遞歸性了,這就是間接左遞歸文法。
消除間接左遞歸的方法是:
把間接左遞歸文法改寫爲直接左遞歸文法,然後用消除直接左遞歸的方法改寫文法。
如果一個文法不含有迴路,即形如PP的推導,也不含有以ε爲右部的產生式,那麼就可以採用下述算法消除文法的所有左遞歸。
for (i=1;i<=n;i++)
for (j=1;j<=i-1;j++)
{ 把形如Ai→Ajγ的產生式改寫成Ai→δ1γ /δ2γ /…/δkγ
其中Aj→δ1 /δ2 /…/δk是關於的Aj全部規則;
消除Ai規則中的直接左遞歸;
}
實驗結果
源代碼
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
const int maxn=100+5;
char buf[maxn]; //輸入產生式
int n; //產生式的數量
class node{
public:
string left; //產生式左部
set<string> right; //產生式右部
node(const string& str){
left=str;
right.clear();
}
void push(const string& str){
right.insert(str);
}
void print(){
printf("%s->",left.c_str());
set<string>::iterator it = right.begin();
printf("%s",it->c_str());
it++;
for (;it!= right.end();it++ )
printf("|%s",it->c_str());
cout<<endl;
}
};
map<string,int> mp; //記錄每個node的下標
vector<node> vnode; //每一個產生式
string start; //文法G[s]
bool used[maxn]; //用於去掉無用產生式
//初始化工作
void init(){
mp.clear();
vnode.clear();
start="S";
}
//消除間接左遞歸
void eliminateIndirectLeftRecursion(){
for(int i=0;i<vnode.size();i++){
for(int j=0;j<i;j++){
vector<string> ans;
set<string>& righti=vnode[i].right;
set<string>& rightj=vnode[j].right;
char ch=vnode[j].left[0]; //取所有Aj產生式的左部的非終結符
set<string>::iterator iti,itj;
for(iti=righti.begin();iti!=righti.end();iti++){
if(iti->at(0)==ch) //如果當前產生式右部的非終結符和Aj相同
for(itj=rightj.begin();itj!=rightj.end();itj++)
ans.push_back(*itj+iti->substr(1)); //進行替換操作,先存儲起來
}
while(!righti.empty()){
if(righti.begin()->at(0)!=ch) //存儲當前沒有替換的產生式右部
ans.push_back(*righti.begin());
righti.erase(righti.begin()); //被替換過的產生式右部也刪除掉
}
for(int k=0;k<ans.size();k++) //將替換過的產生式右部進行更新操作
righti.insert(ans[k]);
}
}
cout<<"消除間接左遞歸後的結果:"<<endl;
for(int k=0;k<vnode.size();k++)
vnode[k].print();
cout<<endl;
}
//消除直接左遞歸
void eliminateDirectLeftRecursion(){
for(int i=0;i<vnode.size();i++){
char ch=vnode[i].left[0];
set<string>& right=vnode[i].right; //拿到當前右部
set<string>::iterator it;
string tmp=vnode[i].left.substr(0,1)+"\'"; //對非終結符更改
bool flag=true;
for(it=right.begin();it!=right.end();it++){
if(it->at(0)==ch){
vnode.push_back(node(tmp));
mp[tmp]=vnode.size();
flag=false;
break;
}
}
int idx=mp[tmp]-1;
if(flag) continue; //對於非終結符不相同的產生式我們需要跳過
vector<string> ans;
set<string>& tmpSet=vnode[idx].right;
tmpSet.insert("~"); //添加空字符
while(!right.empty()){
if(right.begin()->at(0)==ch)
tmpSet.insert(right.begin()->substr(1)+tmp);
else
ans.push_back(right.begin()->substr(0)+tmp);
right.erase(right.begin()); //刪除掉原本產生式右部
}
for(int k=0;k<ans.size();k++)
right.insert(ans[k]); //更新加入新的產生式右部
}
cout<<endl;
cout<<"消除直接左遞歸後的結果:"<<endl;
for(int k=0;k<vnode.size();k++)
vnode[k].print();
cout<<endl;
}
//搜索
void dfs(int x){
if(used[x]) return;
used[x]=1; //將當前下標記錄
set<string>::iterator it=vnode[x].right.begin();
for(;it!=vnode[x].right.end();it++){
for(int i=0;i<it->length();i++){
if(isupper(it->at(i))){ //判斷是否是大寫字母
if(i+1<it->length() && it->at(i+1)=='\'') //如果當前是替換的那個字符
dfs(mp[it->substr(i,2)]-1);
else
dfs(mp[it->substr(i,1)]-1);
}
}
}
}
//去掉無用產生式
void removeUselessProduction(){
memset(used,0,sizeof(used));
int idx=mp[start]-1;
dfs(idx); //搜索
cout<<"最終文法:"<<endl;
vector<node> res;
for(int i=0;i<vnode.size();i++)
if(used[i]) //存儲已經標記過的產生式
res.push_back(vnode[i]);
vnode.clear();
vnode=res;
}
int main(){
cout<<"請輸入文法G[S]的產生式數量:"<<endl;
while(cin>>n){
init(); //初始化
getchar();
cout<<"依次輸入文法G[S]的產生式:"<<endl;
for(int i=0;i<n;i++){
scanf("%s",buf); //輸入產生式
int len=strlen(buf),j;
for(j=0;j<len;j++)
if(buf[j]=='-'){
buf[j]=0; //進行左部和右部切分
break;
}
string tmp=buf; //拿到產生式的左部
if(!mp[buf]){
vnode.push_back(node(tmp));
mp[tmp]=vnode.size();
}
int idx=mp[tmp]-1; //獲取左部的下標
tmp=buf+j+2; //拿到產生式的右部
vnode[idx].push(tmp);
}
//確定開始節點
int idx=vnode.size()-1;
start=vnode[idx].left[0];
eliminateIndirectLeftRecursion(); //消除間接左遞歸
eliminateDirectLeftRecursion(); //消除直接左遞歸
removeUselessProduction(); //去掉無用產生式
/*
*test
*/
/*cout<<"---------test---------"<<endl;
for(int k=0;k<vnode.size();k++)
vnode[k].print();*/
for(int k=0;k<vnode.size();k++)
vnode[k].print();
}
return 0;
}
輸出結果
測試樣例
6
S->Qc
S->c
Q->Rb
Q->b
R->Sa
R->a
6
R->Sa
R->a
Q->Rb
Q->b
S->Qc
S->c
6
Q->Rb
Q->b
R->Sa
R->a
S->Qc
S->c
6
Q->Rb
R->Sa
Q->b
S->Qc
R->a
S->c
參考文獻
感謝以下博主的文章,本文參考了部分代碼和知識。
馮強計算機考研:編譯原理-消除左遞歸學如逆水行舟,不進則退