LR强化记忆

看了半小时还是背不下来,所以有了此篇考前强化记忆文章。

背代码还是得比照着实物,假设我们已经将文法制作成了如上的DFA图,所以代码中会直接用以下函数来从这张DFA图获取在某状态(state)下输入(symbol)所到达的下一个状态(state)。

State goTo(const State& state, Symbol symbol); // 计算在给定符号下的转移

首先同样的,类比在DFA实验中,我们要生成一张表,首先是要制作它的表头。

为了比较清晰,我们把图中的action表和goto表拆开制作,数据结构如下:

map<pair<int Symbol>, string> actionTable;
map<pair<int, Symbol>, int> gotoTable;
//在pair中,int为状态号,Symbol为朝状态中输入的符号

文法和状态的数据结构:

typedef char Symbol;
//文法数据结构
struct GrammarRule{
    Symbol leftSide;//文法左部
    string rightSide;//文法右部
};

//状态和状态集的数据结构
struct Item{
    GrammarRule rule;
    int dotPosition;
};

struct State{
    vector<Item> items;
}


//定义项集族和文法的数据类型
vector<State> states;
vector<GrammarRule> grammarRules;

为了更方便记忆,此处多提几句。

这个数据结构是为了存储这张DFA图。

所以我们定义了GrammarRule来记忆具体的文法,定义item记忆一条加了点(dotPosition)的文法,当然我们可以清晰地看到,在每个状态集中item不止一个,所以在一个状态中我们需要记录一组item。

当然我们不止一个状态,我们需要定义一个状态集。

我们的文法也不止一条,所以我们要定义一个文法集。

开始制表:

void buildSLRTables(){
    //构造ACTION表和GOTO表的主要逻辑
    for(int I = 0; I < states.size(); I++){//遍历所有状态
        for(auto symbol: getGrammarSymbols){//遍历所有文法符号
            //此处是对当前状态尝试输入所有当前文法中的符号,构造行
            State newState = goTo(states[I], symbol);//计算状态转移
            if(!newState.items.empty()){
                int newStateIndex = findStateIndex(newState);
                if(isTerminal(symbol)){
                    //如果symbol是终结符,则在ACTION表中添加移进项
                    actionTable[{I, symbol}] = 's' + to_string(newStateIndex);
                }
                else if(isNonTerminal(symbol)){
                    gotoTable[{I, symbol}] = newStateIndex;
                }
            }
        }
    }
}

我们把代码拆开来看比较好理解,先看构造ACTION表和GOTO表的部分。

先别急,我们先模拟一下手动制表的过程。

虽然是LR(0)分析表,但在制表过程中是相差无几的。

首先来到状态1,我们尝试在以上DFA图中向状态1中输入表头所有符号,如果有下一个状态跳转,我们记录,没有则不记录。

在记录的时候,分两种情况,当输入是终结符,我将转移记到ACTION表中,当输入是非终结符,我将转移记到goto表中。

此处有一个问题,为什么要分两种情况?

当输入是终结符时,我们模拟的是移进操作,当输入是非终结符时,我们模拟的是当规约完成,非终结符入栈时,它会跳转到哪个状态。

再看此算法规约部分的判断:

       // 检查归约和接受情况
        for (auto& item : states[i].items) {
            if (item.dotPosition == item.rule.rightSide.length()) { // 检查是否可以进行归约
                if (item.rule.leftSide == 'S') { // 假设'S'是起始符号
                    actionTable[{i, '$'}] = "ACC"; // 在ACTION表中标记为接受
                } else {
                    // 对于其他规则执行归约操作
                    int ruleIndex = findRuleIndex(item.rule); // 找到规则的索引
                    for (auto symbol : followSet(item.rule.leftSide)) { // 遍历FOLLOW集
                        actionTable[{i, symbol}] = "R" + to_string(ruleIndex); // 在ACTION表中添加归约项
                    }
                }
            }

假设我们已经按刚才的算法写好了一半的Action表,全部的goto表,我们就要开始写Action表的规约部分了。

首先当我们手工构造时,到底是什么时候开始规约?

答案是当我们的点走到文法推导的最后一个位置时,开始规约,所以我们检查每个State中的所有文法,如果有点在最后的情况,我们记录规约,但注意,我们还需要检查文法左边的follow集是否含有输入流中下一个需要分析的字符。

记得对起始状态特殊处理,标记接受。

完整代码:

// 定义文法符号和文法规则的数据类型
typedef char Symbol;
struct GrammarRule {
    Symbol leftSide;
    string rightSide;
};
 
// 定义状态和项集的数据类型
struct Item {
    GrammarRule rule;
    int dotPosition;
};
 
struct State {
    vector<Item> items;
};
 
// 定义项集族和文法的数据类型
vector<State> states;
vector<GrammarRule> grammarRules;
```
 
## 辅助函数
 
```c++
// 辅助函数声明
State closure(const State& state); // 计算状态的闭包
State goTo(const State& state, Symbol symbol); // 计算在给定符号下的转移
bool isTerminal(Symbol symbol); // 判断符号是否是终结符
bool isNonTerminal(Symbol symbol); // 判断符号是否是非终结符
vector<Symbol> getGrammarSymbols(); // 获取所有文法符号
int findStateIndex(const State& state); // 根据状态找到其索引
int findRuleIndex(const GrammarRule& rule); // 根据规则找到其索引
set<Symbol> followSet(Symbol symbol); // 计算符号的FOLLOW集
```
 
## 算法
 
```c++
map<pair<int, Symbol>, string> actionTable; // ACTION表
map<pair<int, Symbol>, int> gotoTable; // GOTO表
void buildSLRTables() {
    // 构建ACTION表和GOTO表的主要逻辑
    for (int i = 0; i < states.size(); ++i) { // 遍历所有状态
        for (auto symbol : getGrammarSymbols()) { // 遍历所有文法符号
            State newState = goTo(states[i], symbol); // 计算状态转移
            if (!newState.items.empty()) {
                int newStateIndex = findStateIndex(newState); // 找到新状态的索引
                if (isTerminal(symbol)) {
                    // 如果symbol是终结符,则在ACTION表中添加移进项
                    actionTable[{i, symbol}] = "S" + to_string(newStateIndex);
                } else if (isNonTerminal(symbol)) {
                    // 如果symbol是非终结符,则在GOTO表中添加项
                    gotoTable[{i, symbol}] = newStateIndex;
                }
            }
        }
        // 检查归约和接受情况
        for (auto& item : states[i].items) {
            if (item.dotPosition == item.rule.rightSide.length()) { // 检查是否可以进行归约
                if (item.rule.leftSide == 'S') { // 假设'S'是起始符号
                    actionTable[{i, '$'}] = "ACC"; // 在ACTION表中标记为接受
                } else {
                    // 对于其他规则执行归约操作
                    int ruleIndex = findRuleIndex(item.rule); // 找到规则的索引
                    for (auto symbol : followSet(item.rule.leftSide)) { // 遍历FOLLOW集
                        actionTable[{i, symbol}] = "R" + to_string(ruleIndex); // 在ACTION表中添加归约项
                    }
                }
            }
        }
    }
}

SLR(1)分析表检查

移进-规约冲突:具体来说,如果一个状态的某个项可以进行归约(即点在规则右侧的末尾),并且该规则左侧的FOLLOW集中包含一个终结符,同时这个状态在该终结符下有一个转移,那么就会在ACTION表中对于这个状态和这个终结符有两个操作:移进和归约。

归约-归约冲突:在同一个状态下,对于同一个输入符号,存在两个或以上的归约操作。具体来说,如果一个状态的两个或以上的项都可以进行归约,且这些规则左侧的FOLLOW集有交集,那么就会在ACTION表中对于这个状态和这个交集中的符号有两个或以上的归约操作。

所以我们要检查的就是这些东西:

(1)每个状态中是否同时存在点走到末尾的规则和点没有走到末尾,且其下一个字符存在于左部follow集中。比如:

T -> y·

T -> y ·+ k

follow(T) = {+}

(2)某状态存在两个以上点走到最后的项,且这些项左侧Follow集有交集。

看起来很难,一步一步走就好了。

首先定义数据结构,我们要记录每一个状态中有什么文法,和这些文法的点(分析到哪了),我们不止有一个状态,所以要有一个状态集合。

struct GrammarRule{
    string leftSide;//文法左侧
    string rightSide;//文法右侧
    int dotPosition;//点的位置
}

struct GrammarState{
    GrammarRule rules[MAX_RULES];//该状态中所有文法
    int ruleCount;//该状态文法数量
}states[MAX_STATES];//状态集

用到的函数:


// 假设以下函数已定义
bool hasIntersection(const vector<string>& set1, const vector<string>& set2);
vector<string> Follow(const string& symbol);
vector<string> First(const string& symbol);

我们先找当前状态的规约项,然后再对每一个规约项进行判断:

bool isSLR1(){
    vector<int> reductionItems;//存储所有规约项的编号
    for(int stateIndex = 0; stateIndex < MAX_STATES; stateIndex++){
        reductionItems.clear();

        //查找规约项,遍历每个状态中所有文法,看有没有点在最后一个的
        for(int ruleIndex = 0; ruleIndex < states[stateIndex].ruleCount; ruleIndex++){
            if(currentRule.dotPosition == currentRule.rightSide.size()){
                reductionItems.push_back(ruleIndex);
            }
        }
        ......
    }
}

然后我们检查规约-规约冲突,说白了就是逐项两两检查刚才构造的规约项集合中,有没有follow集都重合的。

比如说在状态1中,有:

T -> s·

S -> t·

此时我分辨它们的方法只有根据Follow集合了,如果follow(S)= {+},follow(T) = {},我就能很轻易的根据输入流的下一个字符来判断选择哪个规约。

//检测规约-规约冲突
for(size_t i = 0; i < recutionItems.size(); i++){
    for(size_t j = i + 1; j < reductionItems.size(); j++){
        GrammarRule& ruleI = states[stateIndex].rules[reductionItems[I]];
        GrammarRules& ruleJ = states[stateIndex].rules[reductionItems[j]];

        if(hasItersection(Follow(ruleI.leftSide), Follow(ruleJ.leftSide))){
        //follow有重合
            return false;
        }
    }
}

移进-规约冲突

这个就有点复杂,因为我们不仅要看规约项,还要看移进项。

在具体实现中,我们要用每一条规约项来对比所有移进项,看当前规约项左部的follow集是否与移进项下一个字符的first集重合。

for(size_t I = 0l I < reductionItems.size(); I++){
    GrammarRules& reductionRule = states[stateIndex].rules[reductionItems[I]];
    for(int ruleIndex = 0; ruleIndex < states[stateIndex].ruleCount; ruleIndex++){
        GrammarRule& currentRule = states[stateIndex].rules[ruleIndex];
        if(currentRule.dotPosition != currentRule.rightSide.size())//移进项
        {
            if(!currentRule.rightSide.empty()){
                char nextSymbol = currentRule.rightSide[currentRule.dotPosition];
                vector<string> firstSet = First(nextSymbol);
                vector<string> followSet = Follow(reductionRule.leftSide);

                if(hasIntersection(firstSet, followSet)){
                    return false;
                } 
            }
        }
    }

}

完整代码:

#include <string>
#include <vector>
using namespace std;
 
const int MAX_STATES = M;  // 最大状态数,M表示总共有M个状态
const int MAX_RULES = N;   // 最大规则数,N表示每个状态最多有N个规则
 
// 定义一个结构体用于表示单个文法规则
struct GrammarRule {
    string leftSide; // 文法的左侧
    string rightSide; // 文法的右侧
    int dotPosition;  // 点的位置
};
 
// LL结构用于表示语法状态
struct GrammarState {
    GrammarRule rules[MAX_RULES]; // 存储所有文法规则
    int ruleCount;                // 该状态中语法的数量
} states[MAX_STATES];             // states 数组存储所有的状态
 
// 假设以下函数已定义
bool hasIntersection(const vector<string>& set1, const vector<string>& set2);
vector<string> Follow(const string& symbol);
vector<string> First(const string& symbol);
 
bool isSLR1() {
    vector<int> reductionItems; // 存储归约项的编号
 
    // 遍历所有状态
    for (int stateIndex = 0; stateIndex < MAX_STATES; stateIndex++) {
        reductionItems.clear();
 
        // 查找归约项
        for (int ruleIndex = 0; ruleIndex < states[stateIndex].ruleCount; ruleIndex++) {
            GrammarRule& currentRule = states[stateIndex].rules[ruleIndex]; // 引用当前规则以减少冗余
 
            if (currentRule.dotPosition == currentRule.rightSide.size()) {
                // 如果点在文法右侧的末尾,表示是归约项
                reductionItems.push_back(ruleIndex);
            }
        }
 
        // 检测归约-归约冲突
        for (size_t i = 0; i < reductionItems.size(); i++) {
            for (size_t j = i + 1; j < reductionItems.size(); j++) {
                GrammarRule& ruleI = states[stateIndex].rules[reductionItems[i]];
                GrammarRule& ruleJ = states[stateIndex].rules[reductionItems[j]];
 
                if (hasIntersection(Follow(ruleI.leftSide), Follow(ruleJ.leftSide))) {
                    return false;
                }
            }
        }
 
        // 检测移进-归约冲突
        for (size_t i = 0; i < reductionItems.size(); i++) {
            GrammarRule& reductionRule = states[stateIndex].rules[reductionItems[i]]; // 引用归约规则以减少冗余
 
            for (int ruleIndex = 0; ruleIndex < states[stateIndex].ruleCount; ruleIndex++) {
                GrammarRule& currentRule = states[stateIndex].rules[ruleIndex];
 
                if (currentRule.dotPosition != currentRule.rightSide.size()) {
                    if (!currentRule.rightSide.empty()) {
                        char nextSymbol = currentRule.rightSide[currentRule.dotPosition];
                        vector<string> firstSet = First(nextSymbol);
                        vector<string> followSet = Follow(reductionRule.leftSide);
 
                        if (hasIntersection(firstSet, followSet)) {
                            return false;
                        }
                    }
                }
            }
        }
    }
 
    return true; // 所有检查通过,是SLR(1)文法
}

光看懂了还不够,再默写一遍!

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值