AI开发C语言应用按步走,表达式计算器calc的第二步,增加取模运算、区分负号和减号与自动测试 calc2 — 取模运算、负号区分与自动化测试1. 概述本次迭代在 calc1 基础上完成三项增强取模运算— 新增%运算符支持TOKEN_MOD负号与减号区分— 通过expect_operand状态机将一元负号如-5与二元减号如3-5解析为不同的 token 类型自动化测试— 创建test.expr测试用例文件在 Makefile 中添加test目标支持一键运行全部测试2. 变更清单文件操作说明parse.h编辑TokenType 枚举增加TOKEN_MOD和TOKEN_NEG更新注释parse.c编辑增加expect_operand状态和update_expect辅助函数parse_next中区分负号/减号并处理%token_name增加两个 caseMakefile编辑新增TESTFILE变量和test目标test.expr新建9 个测试用例main.c无需改动。3. 取模运算3.1 parse.htypedefenum{TOKEN_NUMBER,/* 整数字面量 */TOKEN_PLUS,/* */TOKEN_MINUS,/* -二元减号 */TOKEN_STAR,/* * */TOKEN_SLASH,/* / */TOKEN_MOD,/* % ← 新增 */TOKEN_LPAREN,/* ( */TOKEN_RPAREN,/* ) */TOKEN_NEG,/* -一元负号 ← 新增 */TOKEN_END,/* 输入结束 */TOKEN_ERROR/* 非法字符 */}TokenType;3.2 parse.c在运算符 switch 中新增%分支case%:tok.typeTOKEN_MOD;break;token_name中对应caseTOKEN_MOD:returnMOD;4. 负号与减号区分核心设计引入expect_operand状态变量记录下一位置是否期待操作数从而决定-的语义。4.1 expect_operand 判定规则上下文-的语义对应 Token原因表达式开头负号TOKEN_NEG如-53运算符之后 ( - * / %)负号TOKEN_NEG如3-5左括号之后(负号TOKEN_NEG如(-3)数字或右括号之后减号TOKEN_MINUS如3-5,(a)-b4.2 状态更新规则每解析一个 token 后根据其类型更新expect_operandToken 类型expect_operand 设为原因NUMBER,RPAREN0操作数之后应跟运算符PLUS,MINUS,STAR,SLASH,MOD,LPAREN,NEG1运算符/左括号/负号之后应跟操作数END,ERROR不变结束或错误无需继续4.3 parse_next 中的判定逻辑/* 跳过空白后先处理 - 的特殊情况 */if(*input_pos-expect_operand){tok.typeTOKEN_NEG;input_pos;column;update_expect(tok.type);returntok;}/* 然后进入 switch 处理其他运算符 */switch(*input_pos){case-:tok.typeTOKEN_MINUS;break;/* expect_operand0 时才会走到这里 */case%:tok.typeTOKEN_MOD;break;/* ... */}5. 自动化测试5.1 test.expr 文件格式每行一个测试用例表达式 | 期望退出码空行和#开头的行被忽略。# 测试用例 # 格式: 表达式 | 期望退出码 (90-18)/315 | 0 10%3 | 0 -53 | 0 3-5 | 0 (-3) | 0 (-820)%-3 | 0 3 5 | 0 35 | 1 empty | 1共9 个测试用例#表达式期望退出码覆盖场景1(90-18)/3150基本表达式回归210%30取模运算3-530负号开头43-50运算符后负号5(-3)0括号后负号6(-820)%-30混合 取模 负号73 50多余空格8351非法字符9empty1空输入5.2 Makefile test 目标TESTFILE test.expr test: $(TARGET) total0; pass0; fail0; \ while IFS| read -r expr expected; do \ expr$$(echo $$expr | sed s/^[[:space:]]*//; s/[[:space:]]*$$//); \ expected$$(echo $$expected | sed s/^[[:space:]]*//; s/[[:space:]]*$$//); \ case $$expr in |\#*) continue;; esac; \ total$$((total 1)); \ if [ $$expr empty ]; then \ $(TARGET) /dev/null 21; actual$$?; \ else \ $(TARGET) $$expr /dev/null 21; actual$$?; \ fi; \ if [ $$actual $$expected ]; then \ printf PASS [%2d] %s\n $$total $$expr; \ else \ printf FAIL [%2d] %s (expected %s, got %s)\n \ $$total $$expr $$expected $$actual; \ fi; \ done $(TESTFILE); \ echo $$pass passed, $$fail failed, $$total total; \ [ $$fail -eq 0 ]设计要点依赖$(TARGET)自动先编译再测试 /dev/null 21丢弃程序输出仅校验退出码变量$$expr和$$expected前后去空格6. 目录结构更新后calc/ ├── Makefile # 构建脚本新增 test 目标 ├── parse.h # 头文件新增 TOKEN_MOD, TOKEN_NEG ├── parse.c # 词法分析器实现新增负号/减号区分逻辑 ├── main.c # 入口程序未改动 ├── test.expr # 测试用例文件新增 ├── doc/ │ ├── calc1.md # 第一次构建笔记 │ └── calc2.md # 本次构建笔记 └── build/ ├── calc ├── parse.o └── main.o7. 测试验证$maketestcalc — 测试套件PASS[1](90-18)/315 PASS[2]10%3 PASS[3]-53 PASS[4]3-5 PASS[5](-3)PASS[6](-820)%-3 PASS[7]35PASS[8]35 PASS[9]empty9passed,0failed,9total8. 新增 Token 一览枚举值对应字符token_name 输出含义TOKEN_MOD%MOD取模运算符二元TOKEN_NEG-NEGATIVE负号运算符一元新 Token 输出示例输入关键输出行10%3% MOD-5- NEGATIVE3-5- MINUS二元减号保持原输出3-5 PLUS/- NEGATIVE