编译原理实验二 《预测分析法设计与实现》
一、实验目的
加深对语法分析器工作过程的理解;加强对预测分析法实现语法分析程序的掌握;能够采用一种编程语言实现简单的语法分析程序;能够使用自己编写的分析程序对简单的程序段进行语法翻译。
二、实验内容
用预测分析法编制语法分析程序,语法分析程序的实现可以采用任何一种编程语言和工具。
三、实验方法
1、使用C++语言及其STL容器与STL算法进行实验编码。
2、开发工具为JetBrains CLion 2019.2.4,编译环境1为Visual Studio (version 16.0)。
四、实验步骤
1. 定义规则文件的组成规律,方便读取形成语法规则的文法。
规则文件的组成规律:在规则文件第一行、第二行分别编写非终结符Vn、终结符Vt(中间没有间隔符);下面的每一行编写一个产生式的左部和右部,使用空格隔开;终结符中未出现的符号代表空字;并且规则文件中的文法都是已经符合LL(1)文法规则的文法,举例如下。
rules.txt:
EATBF
+*()i
E TA
A +TA
A $
T FB
B *FB
B $
F (E)
F i
此处$号代表空字。
2. 设计语法规则文法的数据结构以及获取函数。
数据结构仍然使用结构体,包括两个string类型(代表Vn、Vt)、一个string类型的二维数组用于临时存储所有产生式以及产生式个数。
struct Grammar {
string Vn; // 非终结符
string Vt; // 终结符
string rules[10][2]; // 产生式
int rule_size = 0; // 产生式个数
};
编写一个函数,通过输入规则文件路径初始化文法。
void getGrammar(string &path, Grammar &grammar , FirstAndFollow &ff) {
ifstream myfile;
myfile.open(path);
/* get Vn and Vt */
getline(myfile, grammar.Vn);
getline(myfile, grammar.Vt);
ff.Vn = grammar.Vn;
/* get Rules */
int index = 0;
while(index<10 && myfile >> grammar.rules[index][0]){
myfile >> grammar.rules[index][1];
index++;
}
grammar.rule_size = index;
}
3. 构建分析表第一步,设计获取各个非终结符的FIRST集合、FOLLOW集合的函数,并将之保存至合适的数据结构当中。
用于存储各个非终结符的FIRST集合、FOLLOW集合的数据结构考虑如下:
// 每个Vn的FIRST和FOLLOW单元
struct FirstAndFollow {
string Vn;
vector<char> first_col;
vector<char> follow_col;
bool isRightVn = false; // 用于标识是否有非终结符要处理
string right_Vns; // 用于暂存需要保存的与之相关的非终结符
};
// 所有Vn的FIRST和FOLLOW
struct VnsOfFF {
string Vns;
// 每个Vns中的Vn对应一个FirstAndFollow
// 约定ff的对应顺序与Vns的顺序一致
vector<FirstAndFollow > ff;
};
通过以下两个函数对文法grammar求其FIRST集与FOLLOW集:void getFirst(Grammar &grammar, VnsOfFF &vff);
void getFollow(Grammar &grammar, VnsOfFF &vff);
具体实现使用的是教材给出的算法,在之后进行闭包运算最后得到结果。最后求出的FIRST集和FOLLOW集如下,表示算法实现正确。
由于函数具体实现过长,此处不再展示代码,仅简要对实现的算法进行描述。
求FIRST集时,对每一个非终结符vn,遍历所有产生式并进行匹配以找到左部为vn的产生式;对产生式右部求FIRST集。
① 先初始化所有的vn的FIRST集。即遍历产生式右部,如果第一个符号为终结符或者空字,则将其加入vn的FIRST集并退出遍历;否则第一个符号为非终结符,继续遍历,中途遇到终结符则退出遍历,遇到非终结符则将其暂存于代表vn的结构体的right_Vns中,置isRightVn为true,表示该非终结符vn有需要进行处理的非终结符。经过初始化处理,将显式的FIRST集元素加入了vn,并存储了在X->ABCDE…Yi这一类情况下的前面一系列非终结符ABCDE…Y。
② 进行闭包运算。对vn有相关产生式右部以非终结符开头,即存在vn->Y或vn->Y1Y2Y3…情况的,即isRightVn为true的,遍历其right_Vns进行逐个分析,条件是只有前一个非终结符含有空字才分析下一个非终结符。分析工作是:将该非终结符对应的FIRST集合并到vn的FIRST集合中。循环闭包运算直到某次循环没有进行分析工作,则说明完成闭包,到此FIRST集可以说是求完了。
对于FOLLOW集的求法,和上面是类似的,细节不太一样,但仍然是以先初始化分析+后闭包分析为基础求法展开的。
4. 构建分析表第二步,通过求得的FIRST集与FOLLOW集构造一个预测分析表。
考虑到存储文法的结构体当中已经存储了产生式规则,则只需设计一个二维数据结构存储产生式位置(整形)如下,即可完成对分析表的构建与存储。
// 初始化一个预测分析表,把#也算进去
vector<vector<int > > pre_anal(grammar.Vn.size(), vector<int >(grammar.Vt.size()+1, -1));
具体构建代码包括在函数:void getPreAnal(vector<vector<int > > &pre_anal, Grammar &grammar, VnsOfFF &vff);
当中。最后得到的结果:(-1表示表内空白的地方,其他表示产生式位置)
5. 通过构建好的预测分析表,编写总控程序完成分析。
总控程序主要由函数void mainControl(vector<vector<int > > &pre_anal, string &expr, Grammar &grammar);
完成。
五、实验结果
将上述做好的文法输入rule.txt,并将测试的正确输入串i*i+i
输入test_02.txt,运行程序得到分析过程与结果如下:(symbol为符号栈,production这里笔误了属于是,为当前剩余的输入串,operation为当前步骤所用的产生式)
可见实验结果是准确的。
将测试的有错误的输入串(i)*k
、i**i+i
等依次输入test_02.txt,运行程序得到分析过程与结果如下:
当输入串为(i)*k
时,中途会上报无法识别文法符号k的错误。
当输入串为i**i+i
的时候,中途会上报无法匹配产生式的错误。
六、实验结论
实验利用自定义的源程序进行测试,结果正确,符合预期结果,测试源码及结果截图和说明如上所示。
七、实验小结
本次实验的关键因素在于FIRST集与FOLLOW集的获得,以及预测分析表的构造。而求FIRST集与FOLLOW集的关键在于如何将教材给出的闭包算法(文字描述形式)具体化为代码进行实现。这里采用的是先初始化分析+后闭包运算的实现方法,比较容易理解,当然也可以选择将初始化和闭包混合在一起(同一个循环中),但是那样子的话实现之后会有部分代码重复跑很多遍,效率不高,对分析器性能有影响。初始化分析主要是将一些显而易见的终结符进行处理,根据算法加入各个非终结符的FIRST集与FOLLOW集中进行初始化,而闭包运算主要是集中于对非终结符进行处理,不断循环“挖出”隐式存在的FIRST集与FOLLOW集元素;值得注意的是,必须先把FIRST集完全求出才能继续求FOLLOW集的动作。预测分析表的构造则关键在于如何根据已经求得的FIRST集与FOLLOW集构建预测分析表,主要还是根据教材给出的文字算法描述进行实现,这部分不难。最后是对于出错的处理,本来应该将错误处理写入预测分析表的,但是为了迅速开发就直接放在主控函数里面实现了。
本次实验由于大量采用STL算法,效率较之前的程序更高,当然也有很多不足的地方,比如一开始求FIRST集,和之后求FOLLOW集时的求FIRST(β)的代码其实是可以重复运用的,但是一开始没考虑这么多导致代码比较冗余;并且由于对数据结构的选择问题,有一些地方是约定默认顺序的,如果不写注释就比较难看懂,可读性不高。
八、附录
实验源码如下。
1、exp_02.h
//
// Created by csu_cangkui on 2021/5/31.
//
#ifndef COMPILEEXPS_EXP_02_H
#define COMPILEEXPS_EXP_02_H
#include <vector>
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
// 文法
struct Grammar {
string Vn; // 非终结符
string Vt; // 终结符
string rules[10][2]; // 产生式
int rule_size = 0; // 产生式个数
};
// 每个Vn的单元,存储FIRST和FOLLOW
struct FirstAndFollow {
string Vn;
vector<char> first_col;
vector<char> follow_col;
bool isRightVn = false; // 用于标志是否有与之相关的非终结符需要处理
string right_Vns; // 用于暂存需要保存的与之相关的非终结符
};
// 所有Vn的FIRST和FOLLOW
struct VnsOfFF {
string Vns;
// 每个Vns中的Vn对应一个FirstAndFollow
// 约定ff的对应顺序与Vns的顺序一致
vector<FirstAndFollow> ff;
};
/**
* 读取规则文件获取语法规则的文法
* @param path
* @param grammar
*/
void getGrammar(string &path, Grammar &grammar, VnsOfFF &ff);
/**
* 求 FIRST 集合
* @param grammar
* @param ff
*/
void getFirst(Grammar &grammar, VnsOfFF &vff);
/**
* 辅助函数:获取产生式右部的一系列开头的非终结符
* @param ff
* @param rule 产生式右部
* @param grammar
*/
void getRightVns(FirstAndFollow &ff, string &rule, Grammar &grammar);
/**
* 求 FOLLOW 集合
* @param grammar
* @param vff
*/
void getFollow(Grammar &grammar, VnsOfFF &vff);
/**
* 辅助函数:求 X->αVnβ 右侧 β 的 FIRST 集合
* @param last_rule 右侧 β
* @param grammar
* @param vff
* @param isEmpty 用于判断FIRST 集合是否含有空字
* @return
*/
string getVnRightFirst(string &last_rule, Grammar &grammar, VnsOfFF &vff, bool &isEmpty);
/**
* 构建预测分析表
* @param pre_anal 预测分析表
* @param grammar
* @param vff
*/
void getPreAnal(vector<vector<int > > &pre_anal, Grammar &grammar, VnsOfFF &vff);
/**
* 获取句子
* @param expr
* @param path
*/
void getExpress (string &expr, string &path);
/**
* 总控程序
* @param pre_anal
* @param expr
* @param grammar
*/
void mainControl(vector<vector<int > > &pre_anal, string &expr, Grammar &grammar);
/**
* 输出函数,用于格式化输出每一步的分析过程
* @param step
* @param op_stack
* @param expr
* @param pro_left
* @param pro_right
*/
void printFunc(int step, vector<char> &op_stack, string &expr, string &pro_left, string &pro_right);
#endif //COMPILEEXPS_EXP_02_H
2、exp_02.cpp
//
// Created by csu_cangkui on 2021/5/31.
//
#include "exp_02.h"
// 定义空字
char v_space = '$';
void getGrammar(string &path, Grammar &grammar, VnsOfFF &ff) {
ifstream myfile;
// 对于MinGW编译器这里会报异常,但是VS的编译器可以通过
myfile.open(path);
// get Vn and Vt
getline(myfile, grammar.Vn);
getline(myfile, grammar.Vt);
ff.Vns = grammar.Vn;
// get Rules
int index = 0;
while(index<10 && myfile >> grammar.rules[index][0]){
myfile >> grammar.rules[index][1];
index++;
}
grammar.rule_size = index;
myfile.close();
}
void getFirst(Grammar &grammar, VnsOfFF &vff) {
// 存储每个非终结符
char vn;
// 存储右侧产生式
string rule_right;
// 遍历每个非终结符
for(unsigned int i=0; i<grammar.Vn.size(); ++i) {
// 对文法的每个非终结符求FIRST
vn = grammar.Vn[i];
FirstAndFollow ff;
ff.Vn = vn;
for (int j=0; j<grammar.rule_size; ++j) {
// 匹配相应的产生式
if(vn==grammar.rules[j][0][0]) {
rule_right.clear();
// 获取产生式右部
rule_right = grammar.rules[j][1];
string::size_type idx = grammar.Vt.find(rule_right[0]);
// 右部第一个符号为终结符或者空字,加入first集
if (idx!=string::npos || rule_right[0]==v_space) {
ff.first_col.push_back(rule_right[0]);
} else {
// 右部第一个符号为非终结符,则暂时先将可能的非终结符保存
for (int k = 0; k<rule_right.size(); ++k) {
ff.isRightVn = true;
// 将产生式右部开头的一个/一些连续的非终结符存入ff的right_Vns当中
getRightVns(ff, rule_right, grammar);
}
}
}
}
// 将做好的ff加入
vff.ff.push_back(ff);
}
// 经过上一个循环,已经将所有非终结符有关的产生式分析完毕,做了如下事情:
// 至少将产生式右部显式的开头终结符加入了FIRST以及存储了右部开头的一个或一系列非终结符
bool isChanged = true;
// 闭包运算
while(isChanged) {
// 初始断定不会发生变化
isChanged = false;
// 循环途中发生变化则将isChanged置为true,使之继续循环
for(unsigned int i=0; i<vff.Vns.size(); ++i) {
// 对每个非终结符vn进行闭包运算
vn = vff.Vns[i];
// 如果vn有相关产生式右部以非终结符开头,即存在vn->Y/vn->Y1Y2Y3...情况的
if (vff.ff[i].isRightVn) {
// 遍历所有堆在开头的非终结符
char vn_start;
bool has_empty = true;
// 只有前一个含有空字才分析下一个
for (int j=0; j<vff.ff[i].right_Vns.size() && has_empty; ++j) {
has_empty = false;
vn_start = vff.ff[i].right_Vns[j];
// 第一个非终结符必定会进行操作加入
// 获取所在下标,方便在vector中定位其对应的FirstAndFollow
int index = vff.Vns.find(vn_start);
// 遍历该非终结符对应的FIRST集合并合并到vn的FIRST集合中
vector<char>::iterator it;
for (it = vff.ff[index].first_col.begin();
it != vff.ff[index].first_col.end() ; ++it) {
// 空字不加入
if ((*it)==v_space){
has_empty = true;
break;
}
// 如果vn的FIRST集当中没有则加入,并标记存在某个FIRST集合发生了变化
if (find(
vff.ff[i].first_col.begin(),
vff.ff[i].first_col.end(), (*it)
) == vff.ff[i].first_col.end()){
vff.ff[i].first_col.push_back((*it));
isChanged = true;
}
}
}
}
}
}
}
void getRightVns(FirstAndFollow &ff, string &rule, Grammar &grammar) {
int index = 0;
string::size_type idx;
for (; index<rule.size(); ++index) {
idx = grammar.Vn.find(rule[index]);
// 如果不是非终结符
if (idx==string::npos)
break;
idx = ff.right_Vns.find(rule[index]);
// 如果已经追加的非终结符当中没有则进行追加
if (idx==string::npos)
ff.right_Vns.push_back(rule[index]);
}
}
void getFollow(Grammar &grammar, VnsOfFF &vff) {
// 存储每个非终结符
char vn;
// 存储右侧产生式
string rule_right;
// 对文法开始符号的FOLLOW添加#号
string::size_type index = vff.Vns.find(grammar.rules[0][0]);
vff.ff[index].follow_col.push_back('#');
// 遍历vff每个非终结符vn,完成初始化
for(unsigned int i=0; i<vff.Vns.size(); ++i) {
// 对已经经历过求FIRST的vff的每个非终结符求FOLLOW
vn = vff.Vns[i];
vff.ff[i].right_Vns.clear();
vff.ff[i].isRightVn = false;
// 遍历产生式规则
for (int j=0; j<grammar.rule_size; ++j) {
string::size_type idx = grammar.rules[j][1].find(vn);
// 如果产生式右部含有该非终结符vn
if (idx!=string::npos) {
rule_right.clear();
rule_right = grammar.rules[j][1];
// X->αVnβ模式,找出右部中所有的Vn并求其后面的β的FIRST集合
idx = 0;
while ((idx=rule_right.find(vn, idx))!=string::npos) {
if ((idx+1)>=rule_right.size()) {
// 如果右侧为结尾,把产生式左部非终结符存储,方便之后求闭包
vff.ff[i].isRightVn = true;
if (vff.ff[i].right_Vns.find(grammar.rules[j][0])==string::npos)
vff.ff[i].right_Vns.append(grammar.rules[j][0]);
} else {
// 如果右侧为β
bool has_empty = false;
string right = rule_right.substr(idx+1);
string first = getVnRightFirst(right, grammar, vff, has_empty);
string::iterator it;
for (it = first.begin(); it!=first.end(); ++it) {
// 如果follow集合当中没有则加入
if ( find(
vff.ff[i].follow_col.begin(),
vff.ff[i].follow_col.end(), (*it)
)==vff.ff[i].follow_col.end() && (*it)!=v_space ) {
vff.ff[i].follow_col.push_back((*it));
}
}
if (has_empty) {
// 如果β可推出空字,把产生式左部非终结符存储,方便之后求闭包
vff.ff[i].isRightVn = true;
if (vff.ff[i].right_Vns.find(grammar.rules[j][0])==string::npos)
vff.ff[i].right_Vns.append(grammar.rules[j][0]);
}
}
idx++;
}
}
}
}
// 经过上一个循环,已经将所有右部含有vn的产生式分析完毕,做了如下事情:
// 至少将右部当中vn右侧的β的FIRST集加入了vn的FOLLOW集
// 并存储了所有需要将其FOLLOW集加入vn的FOLLOW集合的非终结符
bool isChanged = true;
// 闭包运算
while (isChanged) {
// 初始断定不会发生变化
isChanged = false;
// 遍历所有非终结符vn,循环途中发生变化则将isChanged置为true,使之继续循环
for(unsigned int i=0; i<vff.Vns.size(); ++i) {
// 对每个非终结符vn进行FOLLOW闭包运算
vn = vff.Vns[i];
// 如果vn有需要加入一些非终结符的FOLLOW集合的
if (vff.ff[i].isRightVn) {
// 遍历所有需要加入其FOLLOW集合的非终结符s
char vn_start;
// 遍历所有目标非终结符,一个个将其FOLLOW集加入vn
for (int j=0; j<vff.ff[i].right_Vns.size(); ++j) {
vn_start = vff.ff[i].right_Vns[j];
// 获取所在下标,方便在vector中定位其对应的FirstAndFollow
int index = vff.Vns.find(vn_start);
// 遍历该非终结符对应的FOLLOW集并合并到vn的FOLLOW集中
vector<char>::iterator it;
for (it = vff.ff[index].follow_col.begin();
it != vff.ff[index].follow_col.end() ; ++it) {
// 如果vn的FOLLOW集当中没有则加入,并标记存在某个FOLLOW集合发生了变化
if (find(
vff.ff[i].follow_col.begin(),
vff.ff[i].follow_col.end(), (*it)
) == vff.ff[i].follow_col.end()){
vff.ff[i].follow_col.push_back((*it));
isChanged = true;
}
}
}
}
}
}
}
string getVnRightFirst(string &last_rule, Grammar &grammar, VnsOfFF &vff, bool &isEmpty) {
string res;
string::size_type idx = grammar.Vt.find(last_rule[0]);
if (idx!=string::npos) {
// 如果首个字符为终结符,则直接返回该终结符
res.push_back(last_rule[0]);
isEmpty = false;
return res;
} else {
bool has_empty = true;
// 遍历所有堆在开头的非终结符
int i = 0;
for (; i<last_rule.size() && has_empty; ++i) {
has_empty = false;
idx = grammar.Vn.find(last_rule[i]);
// 如果不是非终结符,即为空字
if (idx==string::npos) {
i++;
break;
}
// 如果是非终结符,考虑加入该非终结符的FIRST
// 获取所在下标,方便在vector中定位其对应的FirstAndFollow
int index = vff.Vns.find(last_rule[i]);
// 遍历该非终结符对应的FIRST集合并合并到res中
vector<char>::iterator it;
for (it = vff.ff[index].first_col.begin();
it != vff.ff[index].first_col.end() ; ++it) {
// 空字不加入
if ((*it)==v_space) {
has_empty = true;
break;
}
// 如果res当中没有则加入
if ( ( res.find((*it)) )==string::npos)
res.push_back((*it));
}
}
// 判断前面这一系列是否可以推出空字
if (i == last_rule.size()) {
isEmpty = true;
res.push_back(v_space);
} else {
isEmpty = false;
}
return res;
}
}
void getPreAnal(vector<vector<int > > &pre_anal, Grammar &grammar, VnsOfFF &vff) {
// 遍历产生式
// 此处,整个二维表的每一纵列代表一个终结符,最后一个为#,每一横排代表一个非终结符
// 约定二维表的终结符、非终结符的排列顺序和grammar中的排列顺序是一致的
for (int i = 0; i < grammar.rule_size; ++i) {
bool has_empty = false;
// 求传入的字符串的FIRST集,并判断其FIRST集是否含有空字
string first = getVnRightFirst(grammar.rules[i][1], grammar, vff, has_empty);
// 获取横向坐标,这里以A->a为例,A=grammar.rules[i][0],a=grammar.rules[i][1]
int row = grammar.Vn.find(grammar.rules[i][0]);
string::iterator it;
for (it = first.begin(); it != first.end(); ++it) {
// 获取纵向坐标
int col = grammar.Vt.find((*it));
// 将A->a加入到M[A,a]当中
if (col!=string::npos)
pre_anal[row][col] = i;
}
if (first.find(v_space)!=string::npos) {
// 若空字属于FIRST(a),则将产生式加入M[A,b],b属于FOLLOW(A)
// 获取FOLLOW所在位置
string::size_type index = vff.Vns.find(grammar.rules[i][0]);
vector<char>::iterator iv;
for (iv = vff.ff[index].follow_col.begin();
iv != vff.ff[index].follow_col.end(); ++iv) {
// 如果a为终结符则获取纵向坐标v
int col = grammar.Vt.find((*iv));
// 将A->a加入到M[A,b]当中
if (col!=string::npos)
pre_anal[row][col] = i;
// 如果a为#
if ((*iv)=='#')
pre_anal[row][grammar.Vt.size()] = i;
}
}
}
}
void getExpress (string &expr, string &path) {
ifstream myfile;
myfile.open(path);
// 这里懒得再去搞花里胡哨的了,直接读取第一行作为句子
getline(myfile, expr);
myfile.close();
}
void mainControl(vector<vector<int > > &pre_anal, string &expr, Grammar &grammar) {
vector<char> op_stack;
// 前期准备工作
op_stack.push_back('#');
// 将文法开始符号入栈
op_stack.push_back(grammar.rules[0][0][0]);
expr.push_back('#');
// step:第几步分析;index:当前指向是输入串符号的下标
int step = 0, index = 0;
// pro用于填充的空字符串
string pro = "";
printFunc(step, op_stack, expr, pro, pro);
string::iterator it = expr.begin();
// 把第一个输入符号读入a
char a = (*it);
bool flag = true;
while (flag) {
// expr_last:输入串剩余部分
string expr_last;
// 弹出符号栈栈顶并存至op_top
char op_top = op_stack.back();
op_stack.pop_back();
if (grammar.Vt.find(op_top)!=string::npos) {
// 符号栈栈顶为终结符
if (op_top==a) {
// 把下一符号读入a
++it;
a = (*it);
step++;
index++;
expr_last.clear();
expr_last = expr.substr(index);
printFunc(step, op_stack, expr_last, pro, pro);
}
else {
cout << "terminal symbol not matching of " << op_top
<< " and " << a << endl; // ERROR
break;
}
} else if (op_top=='#') {
if (op_top==a)
flag = false; // 分析结束
else {
cout << "sentence length error" << endl; // ERROR
break;
}
} else {
// 符号栈栈顶为非终结符。这里无需关心其是否为其他符号
// 只需关心当前指向的输入串字符是否为文法外的符号
int col_a = grammar.Vt.find(a);
if (col_a!=string::npos||a=='#') {
// 如果a是终结符或者#号则进行下一步
int row = grammar.Vn.find(op_top);
int col = a=='#' ? grammar.Vt.size() : col_a;
int rule_pos = pre_anal[row][col];
if (rule_pos!=-1) {
string rule = grammar.rules[rule_pos][1];
if (!(rule.size()==1 && rule[0]==v_space)) {
// 右部不是空字则反序压栈
int ir = rule.size()-1;
for (; ir>=0; --ir)
op_stack.push_back(rule[ir]);
}
step++;
expr_last.clear();
expr_last = expr.substr(index);
printFunc(step, op_stack, expr_last, grammar.rules[rule_pos][0], rule);
} else {
cout << "no matching production of " << op_top
<< " to " << a << endl; // ERROR
break;
}
} else {
cout << "unrecognized symbol of " << a << endl; // ERROR
break;
}
}
}
if (!flag) {
cout << "Analyzing successfully";
}
}
void printFunc(int step, vector<char> &op_stack,
string &expr, string &pro_left,
string &pro_right) {
string stack_content;
vector<char>::iterator it = op_stack.begin();
for (; it!=op_stack.end(); ++it) {
stack_content.push_back((*it));
}
if (step==0) {
cout << "step\t"
<< "symbol\t"
<< "production\t"
<< "operation" << endl;
}
if (!pro_left.empty()&&!pro_right.empty()) {
cout << step << "\t"
<< stack_content << "\t"
<< expr << "\t\t"
<< pro_left+"->"+pro_right << endl;
} else {
cout << step << "\t"
<< stack_content << "\t"
<< expr << "\t\t"
<< "-" << endl;
}
}
3、main.cpp
#include "exp_02.h"
int main() {
Grammar grammar;
// 地址根据实际情况而定,必须为绝对路径且双反斜杠
string path = "D:\\Projects\\CLion Projects\\CompileExps\\rules.txt";
VnsOfFF vff;
getGrammar(path, grammar, vff);
getFirst(grammar, vff);
getFollow(grammar, vff);
vector< vector<int > > pre_anal(grammar.Vn.size(),vector<int >(grammar.Vt.size()+1, -1));
string expr, path_expr = "D:\\Projects\\CLion Projects\\CompileExps\\test_02.txt";
getPreAnal(pre_anal, grammar, vff);
getExpress(expr, path_expr);
mainControl(pre_anal, expr, grammar);
return 0;
}
ps:本人比较懒,所以整个编译原理四个必做实验自己只把前两个手撸了,后面全抄的,前两个也没有连贯起来,因为感觉为实验一的那个简单C++语言设计一个语法规则的文法比较麻烦。这个实验也偷懒了,懒得写消除左递归的函数,就这样吧。
-
CLion是没有自带编译器的,只能配置另行安装的或者具有编译环境的,比如Dev自带的MinGW,VS自带的Visual Studio (version 16.0) ↩︎