前言
最近在学习微机原理,里面涉及到编译优化的问题,又去重新看了看龙书的语法分析部分。之前学习的时候只是知道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:#
当然设计的还有很多不完善的地方,后续自己优化吧。
本文作者通过实例讲解如何用C++实现微机原理中的编译优化,详细阐述了first和follow集合的计算过程,包括规则1、2和3,最终展示了计算结果的应用实例。

2677

被折叠的 条评论
为什么被折叠?



