【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

参考文献

感谢以下博主的文章,本文参考了部分代码和知识。

冯强计算机考研:编译原理-消除左递归

黎辰:编译原理(三) 消除文法的左递归

学如逆水行舟,不进则退
一百个Chocolate CSDN认证博客专家 CSDN博客专家 Vue爱好者 博客之星
不是只会写业务代码的前端开发攻城狮!博客网站:yangchaoyi.vip做限量版的自己,就这样安静地努力。一个还在苦学前端的小小Chocolate,我的博客主要分享前端、算法、大学课程笔记、平常遇到的bug、心得感悟体会,感谢您的访问,若喜欢可以关注一下~每一个清晨,记得鼓励自己。没有奇迹,只有你努力的轨迹;没有运气,只有你坚持的勇气!每一份坚持都是成功的累积,只要相信自己,总会遇到惊喜!座右铭:学如逆水行舟,不进则退!
©️2020 CSDN 皮肤主题: 程序猿惹谁了 设计师: 上身试试 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值