编译原理实践指南:SLR(1)文法下的语义分析与四元式生成实现
1. SLR(1)文法与语义分析入门第一次接触编译原理时我被那些晦涩的概念弄得晕头转向。直到亲手实现了一个SLR(1)分析器才真正理解语义分析和中间代码生成的奥妙。SLR(1)作为自底向上语法分析的重要方法特别适合处理赋值语句这类相对简单的文法结构。你可能好奇为什么要选择SLR(1)而不是更强大的LR(1)或LALR(1)。原因很简单对于大多数基础场景SLR(1)已经足够好用而且实现起来更简单。想象你正在教小朋友骑自行车没必要一开始就用专业赛车普通的童车就够用了。在实现过程中我发现SLR(1)分析器就像个聪明的机器人它会逐个吃掉输入符号移进根据当前状态决定是继续吃还是消化已吃的内容规约最终把整个输入串消化成语法树这个过程中最有趣的部分是语义动作的触发时机。比如处理表达式abc时分析器会在规约E→ET时自动生成对应的四元式。我刚开始总搞错动作触发时机结果生成的代码顺序完全乱套后来通过打印分析栈状态才找到问题所在。2. 核心数据结构设计实现SLR(1)分析器就像搭积木需要先准备好各种形状的积木块。在我的C实现中这几个数据结构特别关键编码映射表用了std::map把字符映射为数字编号。这就像给每个符号发身份证mapchar, int deCode { {i, 0}, {, 1}, {, 2}, {-, 3}, {*, 4}, {/, 5}, {(, 6}, {), 7}, {#, 8}, {S, 9}, {E, 10}, {T, 11}, {F, 12}, {V, 13} };SLR分析表是个二维vector存储状态转移信息。第一次实现时我手写这个表眼睛都快看花了vectorvectorint table { {3,0,0,0,0,0,0,0,0,1,0,0,0,2}, {0,0,0,0,0,0,0,0,-11,0,0,0,0,0}, // ...其他状态行 };四元式结构体记录中间代码的每个部分。调试时我特意加了个打印函数struct quadruple { char op[N]; char arg1[N]; char arg2[N]; char res[N]; };分析栈跟踪分析过程的状态。这里我用了C风格的结构体纯粹是个人习惯struct Stack { char s[N]; // 符号栈 int i[N]; // 状态栈 int space[N]; // 源代码位置 int top; };3. 语义分析与四元式生成实战语义分析的核心在于在合适的时机执行正确的动作。以这个简单赋值文法为例G[S]: S → VE E → ET | E-T | T T → T*F | T/F | F F → (E) | i V → i关键处理逻辑体现在SLR_analysis函数中。当遇到规约动作时table值为负就会触发语义处理。比如处理加法运算时if(tmp 2) { // E→ET topOfQuad; strcpy(quad[topOfQuad].op, ); // 处理操作数1 if(anstk-space[anstk-top - 2] 0) sprintf(quad[topOfQuad].arg1, t%d, -anstk-space[anstk-top - 2]); else strcpy(quad[topOfQuad].arg1, str[anstk-space[anstk-top - 2]]); // 处理操作数2 // 生成临时变量名 sprintf(quad[topOfQuad].res, t%d, topOfQuad); anstk-top - 3; anstk-space[anstk-top 1] -topOfQuad; // 保存临时变量位置 }赋值语句处理有些特殊需要特别注意左值的处理if(tmp 1) { // S→VE topOfQuad; strcpy(quad[topOfQuad].op, ); // 右值处理 if(anstk-space[anstk-top] 0) sprintf(quad[topOfQuad].arg1, t%d, abs(anstk-space[anstk-top])); // 左值处理 strcpy(quad[topOfQuad].res, str[anstk-space[anstk-top - 2]]); anstk-top - 3; }调试这类代码时我养成了三个好习惯打印分析栈的完整状态逐步验证每个规约动作检查生成的临时变量编号是否连续4. 完整实现与测试案例让我们看一个完整的测试案例。输入表达式a((b)c*d)/fe*g分析过程会经历这些关键步骤逐步移进直到遇到第一个右括号规约F→(E)时处理括号表达式遇到乘除法时优先规约最后处理赋值语句生成的中间代码是这样的四元式序列(, b, c*d, t1) (/, t1, f, t2) (*, e, g, t3) (, t2, t3, t4) (, t4, , a)在main函数中整个分析流程是这样组织的int main() { // 初始化分析栈 Stack *anstk (Stack *)malloc(sizeof(Stack)); anstk-s[0] #; anstk-i[0] 0; anstk-top 0; // 执行分析 if(!SLR_analysis(input, anstk)) { cout 语法错误 endl; } else { cout 分析成功 endl; dispQuad(); // 打印四元式 } return 0; }我强烈建议在实现时添加可视化输出就像这个SLR_display函数void SLR_display(char *str, Stack *anstk, int cur) { // 打印分析栈 for(int i 0; i anstk-top; i) cout anstk-s[i]; // 打印剩余输入 cout \t\t; for(int i cur; i strlen(str); i) cout str[i]; cout endl; }遇到最头疼的问题是运算符优先级处理。有次忘记在分析表中正确设置优先级导致ab*c被错误地处理成(ab)*c。后来通过单步调试分析栈状态才发现是规约顺序出了问题。