【C++實現】編譯原理 免考小隊 消除一切左遞歸

背景

期末考試免考,衝!

實驗名稱

消除一切左遞歸

實驗時間

2020年5月27日 到 2020年5月31日

院系

信息科學與工程學院

組員姓名

Chocolate、kry2025、鍾先生、leo、小光

實驗環境介紹

  • windows 10 操作系統
  • Eclipse 進行 java 編程
  • CodeBlocks 進行 C++ 編程

實驗目的與要求

目的

  • 深刻理解左遞歸的算法
  • 掌握消除左遞歸的過程
  • 加強團隊合作能力
  • 提高自身的編程能力和解決問題的能力

要求

  • 編程實現消除一切左遞歸
  • 算法簡潔,不冗餘

解決問題

產生式直接消除左遞歸

形如 P → Pα | β 可以通過直接消除轉化爲:

P → βP′
P′ → αP′ | ϵ

產生式間接消除左遞歸

有時候雖然形式上產生式沒有遞歸,但是因爲形成了環,所以導致進行閉包運算後出現左遞歸,如下:

S → Qc | c
Q → Rb | b
R → Sa | a

雖不具有左遞歸,但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

參考文獻

感謝以下博主的文章,本文參考了部分代碼和知識。

馮強計算機考研:編譯原理-消除左遞歸

黎辰:編譯原理(三) 消除文法的左遞歸

學如逆水行舟,不進則退
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章