[编译原理]first和follow集合C++代码实现

本文作者通过实例讲解如何用C++实现微机原理中的编译优化,详细阐述了first和follow集合的计算过程,包括规则1、2和3,最终展示了计算结果的应用实例。

前言

最近在学习微机原理,里面涉及到编译优化的问题,又去重新看了看龙书的语法分析部分。之前学习的时候只是知道first和follow集合怎么计算,但是没有很明白背后的原理。想起轮子哥的一句话:要理解一个东西最好的办法就是实现它。所以就心血来潮,写了C++的实现

代码

思路什么的就不说了,都写在代码的注释里面。

#include<string>
#include<iostream>
#include<vector>
#include <map>
#include <set>
#include <regex>
using namespace std;
const int MAX_N = 5000;
//记录总数
int cnt = 0;
pair<string,vector<string> > exps[MAX_N];
set<string> Vns,Vts;//Vns表示非终结符的集合,Vts表示终结符的集合
//使用set的好处就是可以去掉手工判断重复元素
map<string,set<string> > first;//计算得到的first集合,同时也包含终结符的(也就是它本身。为了方便起见,往这里加入了)
map<string,set<string> > follow;//计算得到的follow集合

//判断是否属于终结符
bool is_terminal_char(const string & s){
    //只要不是大写的我们就认为它属于终结符
    regex nt("[A-Z]*");
    if(regex_match(s,nt)){
        return false;
    }
    return true;
}

void readExps(){
    string str;
    cout<<"input information (end with #):\n";

    while (getline(cin,str)){
        if(str.find("#") != -1) break;//结束的条件,如果是#就直接跳出循环
        //输入的表达式以 -> 作为左右方向的分隔符。所以直接去找这个->的位置
        int mid_pos = str.find("->");
        if(mid_pos == -1){
            cout<<"输入的表达式格式有误,已忽略该表达式\n";
            continue;
        }
        int ls = 0,le = mid_pos;//表示左边的字符串起始位置和结束位置
        while (str[ls] == ' ') ls++;

        for(int i = ls;i<mid_pos;i++){
            if(str[i] == ' '){
                le = i;
                break;
            }
        }
        //确定表达式左边的非终结符的具体内容
        exps[cnt].first = str.substr(ls,le-ls);
        Vns.insert(exps[cnt].first);//加入到终结符的集合中
        //cout<<"--"<<exps[cnt].first<<"--"<<endl;
        //继续往后读取,把表达式右边的字符全都加入进来
        for(int i = mid_pos + 2; i<(int)str.length();i++){
            //表达式右边把所有的单字符都读入到vector里面
            if(str[i] == ' ') continue;
            exps[cnt].second.push_back(str.substr(i,1));
            string exp_elem = str.substr(i,1);
            if(is_terminal_char(exp_elem)){
                Vts.insert(exp_elem);
                first[exp_elem].insert(exp_elem);
            }
        }
        //读取完毕后,把cnt+1
        cnt++;
//        for(string s : exps[cnt].second){ // 读取测试完成
//            cout<<s<<" ";
//        }
    }
}


//计算first集合
void cal_first(){
    int pre_size = -1,now_size = 0;
    //设置更新的条件
    while(pre_size != now_size){
        pre_size = now_size;
        for(int i = 0;i<cnt;i++){
            string str = exps[i].first;
            vector<string> elements = exps[i].second;
            //如果说表达式右边的第一个字符属于终结符,那么就直接把它加入到first(str)中(规则1)
            if(is_terminal_char(elements[0])){
                first[str].insert(elements[0]);
            }
                //规则2
            else {
                for (int j = 0; j < (int) elements.size(); j++) {
                    if (is_terminal_char(elements[j])) {//用于循环体判断。如果发现已经到达了终结符的位置,就退出
                        break;
                    }
                    //将两个集合进行合并
                    for(string tmp : first[elements[j]]){
                        //注意,空字符不能够加入到str的first集合中
                        if(tmp != "~"){
                            first[str].insert(tmp);
                        }
                        //但是如果发现所有的非终结符都可能推导出空字符,那么就把空字符加进去
                        else if(j == elements.size() - 1){
                            first[str].insert(tmp);
                        }
                    }
                    //如果发现没有空字符集,就直接退出去
                    if (first[elements[j]].find("~") == first[elements[j]].end()) {
                        break;
                    }
                }
            }
            now_size = 0;
            for(string tmp : Vns){//重新计算now_size的大小
                now_size += (int)first[tmp].size();
            }
        }
    }
}

void cal_follow(){
    //使用规则1
    follow["S"].insert("#");//默认S为文法的起始字符
    //这两个标志只用于判断是否已经生成了完全的follow集合
    int pre_size = -1,now_size = 0;
    while (pre_size !=  now_size){
        pre_size = now_size;
        for(int i = 0;i<cnt;i++){
            string str = exps[i].first;
            vector<string> elements = exps[i].second;
            //使用规则2
            for(int j = 0;j<(int)elements.size()-1;j++){
                if(!is_terminal_char(elements[j])){//如果当前的是非终结符
                    //并且后面的那个单元也是非终结符
                    //就把后面的那个非终结符除~以外的所有first元素都加入进去
                    follow[elements[j]].insert(first[elements[j + 1]].begin(), first[elements[j + 1]].end());
                    //去掉空字符
                    follow[elements[j]].erase("~");
                    //否则就把后面的单个终结符加入进去
                }
            }
            //如果当前的是终结符,那么规则2就不用了.
            //使用规则3,为了方便起见,从后往向前遍历
            for(int j = elements.size() - 1;j>=0;j--){
                //如果是非终结符,就把推导式中的follow元素都加入到它对应的follow集合里面
                if(!is_terminal_char(elements[j])){
                    follow[elements[j]].insert(follow[str].begin(),follow[str].end());
                }
                else break;//如果是终结符,就直接退出

                //表明在first集合中没有空字符,这样的话就直接退出
                if(first[elements[j]].find("~") == first[elements[j]].end()){
                    break;
                }
            }
        }
        now_size = 0;
        for(string tmp : Vns){//重新计算now_size的大小
            now_size += (int)follow[tmp].size();
        }
    }
}

/**
 * testData is in test_data.txt
 * @return
 */
int main(){
    Vts.insert("#");

    readExps();
    cal_first();
    cout<<"---first---\n";
    for(string n : Vns){
        cout<<n<<":";
        for(string tmp : first[n]){
            cout<<tmp<<" ";
        }
        cout<<endl;
    }
    cal_follow();
    cout<<"---follow---\n";
    for(string n : Vns){
        cout<<n<<":";
        for(string tmp : follow[n]){
            cout<<tmp<<" ";
        }
        cout<<endl;
    }
    return 0;
}

测试数据:
输入:

S->AB
S->bC
A->~
A->b
B->~
B->aD
C->AD
C->b
D->aS
D->c

预计输出:

---first---
A:b ~
B:a ~
C:a b c
D:a c
S:a b ~
---follow---
A:# a c
B:#
C:#
D:#
S:#

当然设计的还有很多不完善的地方,后续自己优化吧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值