#include iostream#include map#include set#include vector#include stringusing namespace std; 终结符和非终结符的类型enum SymbolType TERMINAL 终结符 NONTERMINAL 非终结符; 文法符号struct Symbol SymbolType type;
注释解析:
-
枚举类型
SymbolType定义了TERMINAL和NONTERMINAL两个符号类型。 -
结构体
Symbol定义了文法符号的类型和名称,重载了<运算符以便于使用set存储。 -
结构体
Item定义了 SLR(1) 项,包括了产生式左部、右部、"." 的位置和向前看符号集合,重载了<运算符以便于使用set存储。 -
结构体
Grammar定义了文法,包括了终结符集合、非终结符集合、产生式集合和开始符号。 -
结构体
SLR1Table定义了 SLR(1) 分析表,包括了 Action 表和 Goto 表。 -
函数
first计算了给定符号的 FIRST 集合。 -
函数
closure计算了给定项集的 CLOSURE 集合。 -
函数
goto_计算了给定项集和符号的 GOTO 集合。 -
函数
lr0构造了 LR(0) 自动机,并返回其 SLR(1) 分析表。
以上是代码中的注释解析,下面对代码中的函数 lr0 进行详细解析。
函数 lr0 的实现:
SLR1Table lr0(const Grammar& grammar) {
SLR1Table table; // 定义 SLR(1) 分析表
set<set<Item>> states; // 定义项集族
set<Item> init_state = closure(grammar, {Item{grammar.start, {Symbol{NONTERMINAL, "S"}}, 0, {Symbol{TERMINAL, ""}}}}); // 构造初始项集
states.insert(init_state); // 将初始项集加入项集族
map<set<Item>, int> state_id; // 定义项集到状态编号的映射
state_id[init_state] = 0; // 初始项集的编号为 0
int current_id = 1; // 当前状态编号从 1 开始
vector<set<Item>> state_list{init_state}; // 定义状态列表,初始项集为第一个状态
for (size_t i = 0; i < state_list.size(); i++) { // 遍历状态列表中的每一个状态
const set<Item>& state = state_list[i];
for (const Symbol& sym : grammar.terminals) { // 对于每个终结符
set<Item> next_state = goto_(grammar, state, sym); // 计算 GOTO 集合
if (!next_state.empty()) { // 如果 GOTO 集合不为空
if (!state_id.count(next_state)) { // 如果 GOTO 集合对应的状态编号还没有分配
state_id[next_state] = current_id++; // 分配一个新的状态编号
state_list.push_back(next_state); // 将 GOTO 集合加入状态列表
}
table.action[{i, sym}] = state_id[next_state]; // 在 Action 表中记录移进动作
}
}
for (const Symbol& sym : grammar.nonterminals) { // 对于每个非终结符
set<Item> next_state = goto_(grammar, state, sym); // 计算 GOTO 集合
if (!next_state.empty()) { // 如果 GOTO 集合不为空
if (!state_id.count(next_state)) { // 如果 GOTO 集合对应的状态编号还没有分配
state_id[next_state] = current_id++; // 分配一个新的状态编号
state_list.push_back(next_state); // 将 GOTO 集合加入状态列表
}
table.goto_[{i, sym}] = state_id[next_state]; // 在 Goto 表中记录转移动作
}
}
for (const Item& item : state) { // 对于状态中的每个项
if (item.dot == item.right.size()) { // 如果 "." 在产生式右部的末尾
if (item.left == grammar.start) { // 如果产生式左部是开始符号
table.action[{i, Symbol{TERMINAL, "$"}}] = -1; // 在 Action 表中记录接受动作
} else { // 如果产生式左部不是开始符号
for (const Symbol& lookahead : item.lookahead) { // 对于每个向前看符号
table.action[{i, lookahead}] = -state_id[state]; // 在 Action 表中记录规约动作
}
}
}
}
}
return table; // 返回 SLR(1) 分析表
}
函数 lr0 的主要思路是构造 LR(0) 自动机并生成 SLR(1) 分析表。首先,根据文法和开始符号构造初始项集,并将其加入项集族。然后,遍历项集族中的每一个项集,对于每个终结符和非终结符,计算 GOTO 集合,并将其加入状态列表和项集族,并在 Action 表和 Goto 表中记录移进动作和转移动作。最后,对于每个状态中的每个项,如果 "." 在产生式右部的末尾,则在 Action 表中记录接受动作或规约动作。最终返回 SLR(1) 分析表
原文地址: https://www.cveoy.top/t/topic/huBx 著作权归作者所有。请勿转载和采集!