AI开发C语言应用按步走,表达式计算器calc的第四步,交互式 REPL 模式

calc4 — 交互式 REPL 模式

1. 概述

本次迭代在 calc3 求值器基础上增加交互式 REPL 模式(Read-Eval-Print Loop),提供实时运算体验,并显示运算步骤。

运行模式对比

模式启动方式输出用途
批处理./calc "表达式"仅结果数字脚本调用、自动化测试
交互式./calc(无参数)运算步骤 + 结果手动输入探索

交互示例

$ ./build/calc calc> (90-18)/3+15 90 - 18 = 72 72 / 3 = 24 24 + 15 = 39 result: 39 calc> 3+5*2 5 * 2 = 10 3 + 10 = 13 result: 13 calc> exit

2. 变更清单

文件操作说明
eval.h编辑新增eval_set_print_steps(int enable)声明
eval.c编辑新增print_steps标志;parse_exprparse_term中每步运算插入步骤打印;新增eval_set_print_steps实现
main.c编辑新增repl()函数;main()按参数有无分流批处理/交互模式
其余文件无需改动parse.h/parse.c/Makefile/test.expr不变

3. 交互模式设计

3.1 main.c — REPL 循环

staticintrepl(void){charbuf[MAX_INPUT];eval_set_print_steps(1);while(1){printf("calc> ");fflush(stdout);if(!fgets(buf,sizeof(buf),stdin))break;/* 去掉末尾换行符 */size_tlen=strlen(buf);if(len>0&&buf[len-1]=='\n')buf[len-1]='\0';if(strcmp(buf,"exit")==0)break;if(*buf=='\0')continue;intresult;if(eval_expr(buf,&result)!=0)continue;printf(" result: %d\n",result);}return0;}

核心逻辑

  1. 打印calc>提示符(fflush确保立即显示)
  2. fgets读取一行用户输入
  3. 去掉末尾换行符
  4. 判断exit→ 退出循环
  5. 判断空行 → 跳过
  6. 调用eval_expr求值 → 输出结果
  7. 循环往复,直到 EOF 或exit

3.2 main() 分流

intmain(intargc,char*argv[]){if(argc>1){/* 批处理模式:从命令行参数读取 */returnparse_and_eval(argv[1]);}/* 交互模式 */returnrepl();}
  • 有参数 → 批处理(eval_set_print_steps(0),不输出步骤)
  • 无参数 → 交互式 REPL(eval_set_print_steps(1),输出步骤)

4. 运算步骤打印

4.1 eval_set_print_steps API

voideval_set_print_steps(intenable);

eval.c中通过print_steps静态变量控制,eval_expr每次调用时会重置eval_error保留print_steps标志。

4.2 步骤打印位置

parse_exprparse_term的每个二元运算完成后插入打印:

// parse_expr 中 —— 处理 + -intresult=left+right;if(print_steps)printf(" %d + %d = %d\n",left,right,result);left=result;// parse_term 中 —— 处理 * / %intresult=left*right;if(print_steps)printf(" %d * %d = %d\n",left,right,result);left=result;

4.3 优先级自动体现

利用递归下降的天然求值顺序——内层语法层先求值,外层后求值——步骤打印顺序自动反映运算符优先级:

输入步骤输出顺序说明
3+5*25 * 2 = 103 + 10 = 13*行先于+
(90-18)/3+1590 - 18 = 7272 / 3 = 2424 + 15 = 39括号内先求值

无需额外排序逻辑,打印顺序等于求值顺序。

5. 目录结构(更新后)

calc/ ├── Makefile # 构建脚本 ├── parse.h # tokenizer 头文件 ├── parse.c # tokenizer 实现 ├── eval.h # 求值器头文件(含 eval_set_print_steps) ├── eval.c # 递归下降求值器实现(含步骤打印) ├── main.c # 入口程序(批处理 + REPL 双模式) ├── test.expr # 测试用例文件 ├── doc/ │ ├── calc1.md # 第一次构建:tokenizer 基础 │ ├── calc2.md # 第二次构建:取模、负号区分、测试套件 │ ├── calc3.md # 第三次构建:表达式求值器 │ └── calc4.md # 本次构建:交互式 REPL └── build/ └── calc

6. 测试验证

6.1 回归测试

批处理模式不受影响,make test9/9 全部 PASS:

$maketest============================================calc — 测试套件============================================PASS[1](90-18)/3+15 →39PASS[2]10%3 →1PASS[3]-5+3 →-2PASS[4]3+-5 →-2PASS[5](-3)-3PASS[6](-8+20)%-3 →0PASS[7]3+58PASS[8]3+@5 → error PASS[9]<empty>→ error============================================9passed,0failed,9total============================================

6.2 交互模式验证

$ ./build/calc calc> (90-18)/3+15 90 - 18 = 72 72 / 3 = 24 24 + 15 = 39 result: 39 calc> 3+5*2 5 * 2 = 10 3 + 10 = 13 result: 13 calc> 10%3 10 % 3 = 1 result: 1 calc> --5 result: 5 calc> exit

6.3 管道输入验证

$echo"(90-18)/3+15"|./build/calc calc>90-18=7272/3=2424+15=39result:39

7. 设计要点

  1. 双模式共存main()通过argc简单分流,两种模式互不干扰
  2. 步骤打印嵌入求值过程:在递归下降的每个二元运算点插入printf,无需额外遍历
  3. 打印顺序自动反映优先级:内层term先于外层expr求值,*/%的步骤行自然出现在+-之前
  4. 静默求值不受影响eval_set_print_steps(0)print_steps为假,不产生任何额外输出,批处理和测试路径完全透明