看了半小时还是背不下来,所以有了此篇考前强化记忆文章。
背代码还是得比照着实物,假设我们已经将文法制作成了如上的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)文法
}
光看懂了还不够,再默写一遍!