欢迎光临
我们一直在努力

AI开发C语言应用按步走,表达式计算器calc的第五步,变量绑定与增强错误提示

calc5 — 变量绑定与增强错误提示

1. 概述

本次迭代在 calc4 的 REPL 基础上增加两大功能:

  • 变量绑定 — 支持 name = expr 赋值,变量可在后续表达式中引用
  • 增强错误提示 — 错误时显示输入表达式 + ^ 标记定位
  • 功能变化:

    项目calc4calc5
    变量引用 不支持 x = 5 + 3 → x * 2 → 16
    错误提示 col 3: illegal character 3 + @ 5 + ^ 标记
    内部命令 exit exit + vars
    新模块 sym.h / sym.c 符号表

    2. 变更清单

    文件操作说明
    sym.h 新建 变量符号表 API(sym_set / sym_get / sym_print / sym_clear)
    sym.c 新建 线性查找实现,最多 64 个变量
    parse.h 编辑 新增 TOKEN_IDENTIFIER;Token 增加 name[32] 字段
    parse.c 编辑 识别字母/下划线开头的标识符
    eval.h 编辑 新增 eval_set_input(const char *)
    eval.c 编辑 parse_factor 支持变量查找;增强错误上下文;eval_set_input 实现
    main.c 编辑 变量赋值检测(is_assignment);vars 命令;eval_set_input 设置
    Makefile 编辑 SRC 加入 sym.c

    3. 变量绑定

    3.1 符号表模块(sym.h / sym.c)

    独立的符号表模块,使用线性查找数组存储变量名 → 整数值的映射。

    #define SYM_MAX 64

    void sym_set(const char *name, int val); // 设置变量(覆盖旧值)
    int sym_get(const char *name, int *val); // 获取变量值,返回 0 成功 / 1 未定义
    void sym_print(void); // 打印所有变量
    void sym_clear(void); // 清空所有变量

    内部数据结构:

    typedef struct {
    char name[32];
    int value;
    } SymEntry;

    static SymEntry sym_table[SYM_MAX];
    static int sym_count;

    3.2 tokenizer 扩展(parse.h / parse.c)

    TokenType 新增:

    TOKEN_IDENTIFIER, /**< 标识符(变量名) */

    Token 结构体扩展:

    typedef struct {
    TokenType type;
    int value;
    int col;
    char name[32]; // 标识符名称(仅 TOKEN_IDENTIFIER 有效)
    } Token;

    parse_next 标识符识别(在数字分支之后、负号分支之前):

    if (isalpha((unsigned char)*input_pos) || *input_pos == '_') {
    int i = 0;
    while (i < (int)sizeof(tok.name) 1 &&
    (isalnum((unsigned char)*input_pos) || *input_pos == '_')) {
    tok.name[i++] = *input_pos++;
    column++;
    }
    tok.name[i] = '\\0';
    tok.type = TOKEN_IDENTIFIER;
    tok.value = 0;
    update_expect(tok.type);
    return tok;
    }

    3.3 求值器扩展(eval.c)

    在 parse_factor 中新增 TOKEN_IDENTIFIER 分支:

    if (t.type == TOKEN_IDENTIFIER) {
    parse_consume();
    int val;
    if (sym_get(t.name, &val) != 0) {
    fprintf(stderr, " error: undefined variable '%s'\\n", t.name);
    set_error();
    return 0;
    }
    return val;
    }

    3.4 REPL 赋值检测(main.c)

    is_assignment 函数判断输入是否为 identifier = expr 格式:

    跳过空白 → 读取变量名(字母/下划线开头) → 跳过空白 → 检查 '=' → 跳过空白

    赋值流程:

    输入 "x = 5 + 3"
    → is_assignment() 识别出 var_name="x", rhs="5 + 3"
    → eval_expr("5 + 3") 求值 → 8
    → sym_set("x", 8)
    → 输出 " result: 8"

    3.5 vars 命令

    REPL 中新增 vars 命令,调用 sym_print() 打印所有变量:

    calc> vars
    x = 8
    y = 16

    4. 更好的错误提示

    4.1 设计目标

    场景calc4 的错误提示calc5 改进后
    非法字符 @ col 3: illegal character 显示 ^ 标记 + 定位
    未定义变量 error: undefined variable 'x'
    不匹配括号 无提示 error: expected ')'
    意外结束 error: unexpected end of expression
    多余 token col 5: … 显示 ^ 标记 + 定位

    4.2 eval_set_input API

    void eval_set_input(const char *input);

    在 REPL 中,每次求值前调用 eval_set_input(buf) 存储当前输入字符串引用。eval.c 内部通过 error_input 静态指针持有。

    4.3 print_error_context 函数

    static void print_error_context(int col)
    {
    if (!error_input) return;
    fprintf(stderr, " %s\\n", error_input);
    fprintf(stderr, " ");
    for (int i = 1; i < col; i++)
    fputc(' ', stderr);
    fprintf(stderr, "^\\n");
    }

    在 parse_factor 中遇到 TOKEN_ERROR 或意外 token 时调用:

    if (t.type == TOKEN_ERROR) {
    print_error_context(t.col);
    fprintf(stderr, " error: unexpected character at position %d\\n", t.col);
    set_error();
    return 0;
    }

    4.4 错误场景对照

    calc> 3 + @ 5
    3 + @ 5
    ^
    error: unexpected character at position 5

    calc> x + y
    error: undefined variable 'x'

    calc> (3 + 5
    error: unexpected end of expression

    4.5 错误后步骤打印不再执行

    修复了 calc4 中的一个问题:当 parse_term() 或 parse_unary() 返回后 eval_error 已被设置,外层函数不应继续打印步骤和计算结果。

    在 parse_expr 和 parse_term 中,每个二元运算的右操作数求值后立即检查 eval_error:

    int right = parse_term();
    if (eval_error) break; // ← 新增,跳过步骤打印
    int result = left + right;
    if (print_steps)
    printf(" %d + %d = %d\\n", left, right, result);

    5. 目录结构(更新后)

    calc/
    ├── Makefile # 构建脚本
    ├── parse.h # tokenizer 头文件(含 TOKEN_IDENTIFIER)
    ├── parse.c # tokenizer 实现(含标识符识别)
    ├── eval.h # 求值器头文件(含 eval_set_input)
    ├── eval.c # 递归下降求值器(含变量查找、错误上下文)
    ├── sym.h # 变量符号表头文件(新增)
    ├── sym.c # 变量符号表实现(新增)
    ├── main.c # 入口程序(含 is_assignment、vars 命令)
    ├── test.expr # 测试用例文件(15 个用例)
    ├── doc/
    │ ├── calc1.md # tokenizer 基础
    │ ├── calc2.md # 取模、负号区分、测试套件
    │ ├── calc3.md # 表达式求值器
    │ ├── calc4.md # 交互式 REPL
    │ └── calc5.md # 本次构建:变量绑定 + 增强错误提示
    └── build/
    └── calc

    6. 测试验证

    6.1 回归测试

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

    6.2 REPL 交互效果

    $ ./build/calc
    calc> x = 5 + 3
    5 + 3 = 8
    result: 8
    calc> y = x * 2
    8 * 2 = 16
    result: 16
    calc> x + y
    8 + 16 = 24
    result: 24
    calc> vars
    x = 8
    y = 16
    calc> 3 + @ 5
    3 + @ 5
    ^
    error: unexpected character at position 5
    calc> z + 1
    error: undefined variable 'z'
    calc> exit

    赞(0)
    未经允许不得转载:171主机测评 » AI开发C语言应用按步走,表达式计算器calc的第五步,变量绑定与增强错误提示
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址