C++实现编译原理 免考小队 消除一切左递归

Posted 一百个Chocolate

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

参考文献

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

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

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

学如逆水行舟,不进则退

以上是关于C++实现编译原理 免考小队 消除一切左递归的主要内容,如果未能解决你的问题,请参考以下文章

P10 消除文法的左递归编译原理

编译原理10 消除左递归

编译原理:消除左递归

如何消除间接呼叫 Method 在 Android Studio 中的编译警告

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

动手写编译器:左递归消除和无歧义算术表达式解析代码实现