如何在Bison中处理多行循环块以生成三地址码?
解决Repeat-Do循环的三地址码生成问题
对齐期望输出的核心流程
先明确你要的三地址码执行逻辑,对应语法分析的关键节点:
- 计算循环次数上限(
$t1 := i SUBI 1) - 初始化循环计数器(
$t2 := 0) - 标记循环体的起始指令行号(对应
LINE_NUMBER) - 执行循环体代码
- 计数器自增
- 生成条件跳转:若计数器小于上限,跳回循环体起始位置
修正Yacc规则与代码生成逻辑
你现有规则没覆盖上述关键步骤,需要拆分规则、调整代码生成时机,用语义值传递循环所需的变量和行号信息,同时避免回填。
调整后的Yacc核心规则
%{ // 假设已定义全局/辅助函数: // int ln_inst; // 当前生成的三地址码指令行号 // char* new_temp(); // 生成唯一临时变量(如$t1、$t2) // 自定义结构体传递循环信息 typedef struct { char* counter; // 计数器临时变量 char* limit; // 循环次数上限临时变量 int start_line; // 循环体起始指令行号 } LoopInfo; %} // 词法单元定义(需和Lex文件匹配) %token REPEAT DO DONE ENDLINE ID NUM %token OP_ADDI OP_SUBI OP_LTI OP_ASSIGN %% program : sentences_list ; sentences_list : sentences_list sentence | sentence ; sentence : assignment_stmt | iterative_stmt ; // 拆分后的Repeat-Do循环规则,精准控制代码生成时机 iterative_stmt : REPEAT expr DO ENDLINE { // 1. 计算循环次数上限:expr - 1 char* t1 = new_temp(); emet($2, OP_SUBI, "1", t1, NULL); // 2. 初始化循环计数器为0 char* t2 = new_temp(); emet("0", OP_ASSIGN, NULL, t2, NULL); // 3. 记录循环体的起始指令行号(此时ln_inst指向即将生成的循环体第一条指令) LoopInfo* info = malloc(sizeof(LoopInfo)); info->counter = t2; info->limit = t1; info->start_line = ln_inst; $$ = info; } sentences_list DONE ENDLINE { LoopInfo* info = (LoopInfo*)$3; // 4. 计数器自增 emet(info->counter, OP_ADDI, "1", info->counter, NULL); // 5. 生成条件跳转指令 char label_buf[32]; sprintf(label_buf, "%d:", info->start_line); emet(info->counter, OP_LTI, info->limit, label_buf, NULL); // 清理临时内存 free(info); } // 赋值语句示例(复用你现有逻辑) assignment_stmt : ID '=' expr ENDLINE { emet($3, OP_ASSIGN, NULL, $1, NULL); } // 表达式处理示例(生成三地址码并返回临时变量名) expr : ID { $$ = strdup($1); } | NUM { $$ = strdup($1); } | expr OP_ADDI expr { char* t = new_temp(); emet($1, OP_ADDI, $3, t, NULL); $$ = t; } | expr OP_SUBI expr { char* t = new_temp(); emet($1, OP_SUBI, $3, t, NULL); $$ = t; } %%
关键注意事项
- 行号记录时机:必须在处理
sentences_list(循环体)之前记录ln_inst,这样才能拿到循环体第一条指令的行号作为跳转目标。 - 临时变量管理:
new_temp()要保证生成唯一的临时变量名,避免不同循环或表达式的变量冲突。 - 语义值传递:用自定义结构体
LoopInfo把计数器、上限变量、起始行号从循环头部传递到循环尾部,确保跳转指令能正确生成。 - Lex配合:确保Lex能正确识别
REPEAT、DO、DONE、换行(ENDLINE)等词法单元,ln_inst要在每次调用emet生成指令后自增。
测试验证
用你提供的测试代码:
i = 3 a = 0 repeat i - 1 do a = a + i done
按照上述规则生成的三地址码会完全匹配你的期望输出。
内容的提问来源于stack exchange,提问作者Galencat
相关产品推荐
相关产品推荐

