You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Bison中处理多行循环块以生成三地址码?

解决Repeat-Do循环的三地址码生成问题

对齐期望输出的核心流程

先明确你要的三地址码执行逻辑,对应语法分析的关键节点:

  1. 计算循环次数上限($t1 := i SUBI 1)
  2. 初始化循环计数器($t2 := 0)
  3. 标记循环体的起始指令行号(对应LINE_NUMBER)
  4. 执行循环体代码
  5. 计数器自增
  6. 生成条件跳转:若计数器小于上限,跳回循环体起始位置

修正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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 07:45:29