文法分析---2022-11-03

一、存储结构

文法规则设计如下

class LinkNode
{
public:
    //规则左部
    string left;
    //规则右部分 abc | def | hji,一个vector<string>元素代表一种可能
    vector<vector<string>> right;
}
class Processor
{
public:
    //存用户输入的语句
    vector<string>input;
    //开始节点索引
    int startWordIndex;
    //开始结点值
    string startNode;
    //存文法
    vector<LinkNode> grammers;
    //终结符号
    vector<string> finalWord;
    //非终结符号
    vector<string> nonFinalWord;
    //First集合
    map<string, set<string>> first;
    //Follow集合
    map<string, set<string>> follow;
    //文件流,用于写出日志
    ofstream log;
}

文法规则分为左部和右部。
左部都是非终结符号
vector<vector> right;
右边可以看成是一个二维数组。
第一列存开始符号。每一行存对应的右部。
如下图
在这里插入图片描述
在输入阶段
从文件中读取文法规则。
再进行处理

规则有效性

1.不可到达规则。
发现某些非终结符不在任何规则的右部出现,该非终结符称为不可到达。
2. 不可终止
发现文法中,某些非终结符,由它不能导出终结符号串,则说明不能终止。
删除上面说的规则。
也包括:
3. A->A,这样的是有害规则

// A->A,这样的是有害规则
        if(str.length() == 4 && str[0] == str[3])
        {
            log << "有害规则,已经删除\n";
            continue;
        }
// A->A,这样的是有害规则
        if(str.length() == 4 && str[0] == str[3])
        {
            log << "有害规则,已经删除\n";
            continue;
        }

//设置开始结点的值,存储文法起点的值。
string startNode ;
** grammers就是 vector 和vector的嵌套**

文法规则中的或符号 |

用splitString(rights,“|”), 进行分割
横着拉出一条链,就是一句文法规则了。
vector temp.push_back(s + str[i]);

//E->abc|def从下标3开始,都是右边部分
        string rights = str.substr(3);
        vector<string> toNodes = splitString(rights, "|");
        //新建一个文法规则的结点
        LinkNode node(left);
        //将分割好的字串添加到文法规则中
        for(string str:toNodes)
        {
            vector<string> temp;
            for(size_t i = 0; i < str.length(); i++)
            {
                string s = "";
                temp.push_back(s + str[i]);
            }
            node.insert(temp);
        }
        grammers.push_back(node);
    }

消除间接左递归

例如:
S -> Qc|c
Q -> Rb|b|Ra|Rs|Rkjkdfl
R -> Sa|a
可以发现上面是间接左递归
** 自顶向下的分析方法。**
把第三个式子右边代换第二个式子中的R。
在process环节已经把|删除了,每一个组合都单独保存。比如Rb, b, Rs, Rkjjkl
一个for循环, 把每一个组合前的第一个大写(非终结符号进行比较)
如果发现和其他规则中的左边的非终结符号相同, 则说明需要替换。
得到转换后的结果是:
Q-> (Sa|a)b| b| (Sa|a)a | (Sa|a)s |(Sa|a)kjkdfl

替换过程的代码:
/*
* 例子:S -> Qc|c
* R -> Sa|a
* Q -> Rbc|b
* 设 i : Q -> Rbc|b
* j : R -> Sa|a
*/
亮点是:
after.insert(after.end(),other.begin(),other.end());
这里第一个参数,是指的插入的位置, 后面表示区间,从other.begin() 开始到 other.end() 结束 。

 					vector< vector<string> > cont;
            vector< vector<string> > &right1 = grammers[i].right;
            vector< vector<string> > &right2 = grammers[j].right;
            string check = grammers[j].left;
            vector<vector<string>>::iterator it1 = right1.begin();
            vector<vector<string>>::iterator it2 = right2.begin();
            for(; it1 != right1.end(); it1++)
            {
                // i的右边第一个元素与j的非终结符号相同,代入
                if(it1->at(0) == check)
                {
                    // 将 j 代入 i
                    // 例如 此时 *it1 = {R,b,c}
                    //  而  right2 = {{S,a},{a}}
                    //   代入后  为 {S,a,b,c}、{a,b,c}

                    //先取出i的右边除了R外的所有元素
                    vector<string> other;
                    for(size_t k = 1; k < (*it1).size(); k++)   // 将i中R之后的符号代入
                    {
                        other.push_back((*it1)[k]);
                    }
                    // 此时 other = <{b},{c}>

                    for(; it2 != right2.end(); it2++)
                    {
                        //用于临时存储带入后产生的式子
                        vector<string> after = *it2;//这里遍历right2,第一次{Sa},第二次{a}
                        //将other的内容分别插入after后面,因为other可能不只一个字符串。
                        after.insert(after.end(),other.begin(),other.end());
                        //代入后  为 {S,a,b,c}、{a,b,c}
                        cont.push_back(after);
                    }
                }
            }
                    //接下来需要修改i:Q->Rb|b的右边部分
            //先把右边部分删掉
            size_t nn = right1.size();
            while(nn--)
            {
                //如果是Rbc,就删掉,因为cont中已经有替换好的部分{{S,a,b,c}{a, b}}了
                //如果是b,就先删掉,并添加到cont中,因为cont中没有b
                if(right1.begin()->at(0) != check)
                    cont.push_back(*right1.begin());
                right1.erase(right1.begin());
            }
            //执行完后Q->{}
            //然后把cont中的内容添加到Q->的右边
            //Q  -  >  Sabc |  ab  |  b
            for( size_t i = 0; i < cont.size(); i++)
            {
                right1.push_back(cont[i]);   // 加入了这两个新的 Sabc |  ab 
            }

以上步骤完成了替换工作
所有的递归都是直接左递归了。不存在间接左递归。

直接左递归的消除

按照下面的思路来做,比较机械。
也是自顶而下的处理逻辑
在这里插入图片描述
遇到直接左递归A,记录A后面的a字符

一般思路都是先变成间接左递归,再变成直接左递归。
在这里插入图片描述
在这里插入图片描述
下面是消除直接左递归的思路
我们会发现,其中每一个部分开头都是比较顶层的非终结符。

for(size_t i = 0; i < grammers.size(); i++){
        //左边部分
        string check = grammers[i].left;
        //右边部分
        vector<vector<string>> temp = grammers[i].right;
        vector<vector<string>>::iterator it = temp.begin();
        //新的结点
        //如: A -> Aa|b
        //那么新建一个结点A'
        string tt = check + "'";
        bool flag = true;
        //判断这条规则是否包含左递归
        for(; it != temp.end(); it++)
        {
            // 左 = 右边第一个,发现直接左递归
            if(it->at(0) == check)
            {
                //新建一个文法结点A'
                grammers.push_back(LinkNode(tt));
                flag = false;
                break;
            }
        }
        //不包含左递归
        if(flag)
            continue;
        //包含左递归的情况:
        //临时存储A'的右边
        vector<vector<string>> cont;
        //指向最后一条文法结点
        vector<vector<string>> &ss = grammers[grammers.size()-1].right;
        vector<string> eplision;
        eplision.push_back("@");
        //ss: {{"@"}}
        ss.push_back(eplision);
        vector<vector<string>>&temp1 = grammers[i].right;
        while(!temp1.empty())
        {
            //对于A->Aa|b,如果现在处理的是Aa
            if(temp1.begin()->at(0) == check)  // 发现左递归元素
            {
                vector<string> vt;
                //就把a复制到vt中
                //vt:{a}
                vt.assign(temp1.begin()->begin()+1,
                          temp1.begin()->end());
                //在末尾增加A'
                //vt:{aA'}
                vt.push_back(tt);
                //ss:{{aA'},{"@"}}
                ss.push_back(vt);
            }
            else  // 否则,应该修改原语法,修改成:A->bA'
            {
                vector<string> vt;
                //vt:{b}
                vt.assign(temp1.begin()->begin(),
                          temp1.begin()->end());
                //vt:{bA'}
                vt.push_back(tt);
                //cont:{{bA'}}
                cont.push_back(vt);
            }
            temp1.erase(temp1.begin());
        }
        //这时A->{}
        //cont:{{bA'}}
        //用cont里面的内容取代A的右边
        for(size_t i = 0; i < cont.size(); i++)
        {
            temp1.push_back(cont[i]);
        }
    }
实验2 文法的读入、判定和处理 一、实验目的 熟悉文法的结构,了解文法在计算机内的表示方法。 二、实验内容 1、 设计一个表示文法的数据结构; 2、 从文本文件中读入文法,利用定义的数据结构存放文法,并输出; 3、 本实验结果将来还有用。 三、实验要求 1、 了解文法定义的4个部分: G(Vn, Vt, S, P) Vn 文法的非终结符号集合,在实验中用大写的英文字母表示; Vt 文法的终结符号集合,在实验中用小写的英文字母表示; S 开始符号,在实验中是Vn集合中的一个元素; P 产生式,分左部和右部,左部为非终结符号中的一个,右部为终结符号或非终结符号组成的字符串,如S->ab|c 2、 根据文法各个部分的性质,设计一个合理的数据结构用来表示文法, 1) 若使用C语言编写,则文法可以设计成结构体形式,结构体中应包含上述的4部分, 2) 若使用C++语言或java语言编写,则文法可以设计成文法类形式,类中至少含有4个数据成员,分别表示上述4个部分 文法数据结构的具体设计由学生根据自己想法完成,并使用C或C++语言或Java实现设计的数据结构。 3、 利用完成的数据结构完成以下功能: 1) 从文本文件中读入文法文法事先应写入文本文件); 2) 根据文法产生式的结构,分析文法的4个部分,分别写入定义好的文法数据结构的相应部分; 3) 整理文法的结构,判断该文法文法类型,是否为0型,1型,2型或3型文法,并输出判断结果; 4) 在计算机屏幕或者文本框中输出文法文法输出按照一个非终结符号一行,开始符号引出的产生式写在第一行,同一个非终结符号的候选式用“|”分隔的方式输出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值