编译原理实验二 《预测分析法设计与实现》

编译原理实验二 《预测分析法设计与实现》

一、实验目的

加深对语法分析器工作过程的理解;加强对预测分析法实现语法分析程序的掌握;能够采用一种编程语言实现简单的语法分析程序;能够使用自己编写的分析程序对简单的程序段进行语法翻译。

二、实验内容

用预测分析法编制语法分析程序,语法分析程序的实现可以采用任何一种编程语言和工具。

三、实验方法

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)*ki**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++语言设计一个语法规则的文法比较麻烦。这个实验也偷懒了,懒得写消除左递归的函数,就这样吧。


  1. CLion是没有自带编译器的,只能配置另行安装的或者具有编译环境的,比如Dev自带的MinGW,VS自带的Visual Studio (version 16.0) ↩︎

上一篇:利用matlab对纯电动汽车在nedc工况下的行驶阻力进行计算


下一篇:FFmpeg的H264解码源码分析:概述