本篇为 mini-dog-c 编译器开发系列第五篇介绍解释器的实现包括环境管理、表达式求值和函数调用。1. 解释器 vs 编译器很多人容易混淆解释器和编译器其实区别很简单编译器把源代码翻译成另一种形式通常是机器码或字节码然后由硬件或虚拟机执行解释器直接遍历源代码的中间表示这里是 AST一边分析一边执行不生成额外的目标代码mini-dog-c 的编译流程实际上是源代码 → Token → AST → 解释执行没有生成机器码或字节码这一步。2. 值Value系统解释器需要一种运行时表示数据的方式。mini-dog-c 定义了一个Value类型typedef enum { VALUE_INT, // 整数 VALUE_DOUBLE, // 浮点数 VALUE_CHAR, // 字符 VALUE_BOOL, // 布尔 VALUE_STRING, // 字符串 VALUE_FUNCTION, // 函数 VALUE_NULL, // 空值 } ValueType; ​ typedef struct { ValueType type; union { int int_value; double double_value; char char_value; bool bool_value; struct { char *string; int length; } string_value; struct { char *name; char **params; int param_count; ASTNode *body; void *closure; } function; } data; } Value;每种字面量类型都对应一个value_create_*函数Value *value_create_int(int v) { Value *value (Value *)malloc(sizeof(Value)); value-type VALUE_INT; value-data.int_value v; return value; } ​ Value *value_create_string(const char *str) { Value *value (Value *)malloc(sizeof(Value)); value-type VALUE_STRING; value-data.string_value.string strdup_custom(str); value-data.string_value.length strlen(str); return value; }3. 环境Environment变量需要存储在哪里答案是环境。环境是一个作用域概念保存了变量名到值的映射struct Env { Env *parent; // 父作用域用于闭包 char *names[ENV_VAR_MAX]; // 变量名 Value *values[ENV_VAR_MAX]; // 对应的值 int count; // 当前作用域变量数量 }; typedef struct Env Env;全局环境解释器初始化时创建一个全局环境用于存储全局变量和函数定义。局部环境函数调用时创建一个新的局部环境父环境指向调用者的环境Env *env_create(Env *parent) { Env *env (Env *)malloc(sizeof(Env)); env-parent parent; env-count 0; return env; } ​ bool env_define(Env *env, const char *name, Value *value) { env-names[env-count] strdup_custom(name); env-values[env-count] value; env-count; return true; } ​ Value *env_get(Env *env, const char *name) { for (int i 0; i env-count; i) if (strcmp(env-names[i], name) 0) return env-values[i]; if (env-parent) return env_get(env-parent, name); // 向上查找 return NULL; } ​ bool env_set(Env *env, const char *name, Value *value) { for (int i 0; i env-count; i) if (strcmp(env-names[i], name) 0) { env-values[i] value; return true; } return env_define(env, name, value); // 未找到则定义到当前作用域 }4. 二元表达式求值二元表达式的求值模式很固定先求左操作数再求右操作数然后根据操作符类型计算结果static Value *eval_binary(Evaluator *eval, Env *env, TokenType op, ASTNode *left_node, ASTNode *right_node) { Value *left eval_expression(eval, env, left_node); Value *right eval_expression(eval, env, right_node); ​ Value *result NULL; ​ switch (op) { case TOKEN_PLUS: if (left-type VALUE_STRING right-type VALUE_STRING) { // 字符串连接 size_t len strlen(left-data.string_value.string) strlen(right-data.string_value.string) 1; char *buf (char *)malloc(len); strcpy(buf, left-data.string_value.string); strcat(buf, right-data.string_value.string); result value_create_string(buf); free(buf); } else if (left-type VALUE_INT right-type VALUE_INT) { result value_create_int(left-data.int_value right-data.int_value); } else { double lv left-type VALUE_INT ? left-data.int_value : left-data.double_value; double rv right-type VALUE_INT ? right-data.int_value : right-data.double_value; result value_create_double(lv rv); } break; ​ case TOKEN_MINUS: if (left-type VALUE_INT right-type VALUE_INT) result value_create_int(left-data.int_value - right-data.int_value); else { double lv left-type VALUE_INT ? left-data.int_value : left-data.double_value; double rv right-type VALUE_INT ? right-data.int_value : right-data.double_value; result value_create_double(lv - rv); } break; ​ case TOKEN_STAR: if (left-type VALUE_INT right-type VALUE_INT) result value_create_int(left-data.int_value * right-data.int_value); else { double lv left-type VALUE_INT ? left-data.int_value : left-data.double_value; double rv right-type VALUE_INT ? right-data.int_value : right-data.double_value; result value_create_double(lv * rv); } break; ​ case TOKEN_SLASH: if (right-type VALUE_INT right-data.int_value 0) result value_create_null(); // 除零返回 null else if (left-type VALUE_INT right-type VALUE_INT) result value_create_int(left-data.int_value / right-data.int_value); else { double lv left-type VALUE_INT ? left-data.int_value : left-data.double_value; double rv right-type VALUE_INT ? right-data.int_value : right-data.double_value; result value_create_double(lv / rv); } break; ​ case TOKEN_EQ: if (left-type VALUE_INT right-type VALUE_INT) result value_create_bool(left-data.int_value right-data.int_value); else if (left-type VALUE_BOOL right-type VALUE_BOOL) result value_create_bool(left-data.bool_value right-data.bool_value); else result value_create_bool(false); break; ​ case TOKEN_NE: if (left-type VALUE_INT right-type VALUE_INT) result value_create_bool(left-data.int_value ! right-data.int_value); else if (left-type VALUE_BOOL right-type VALUE_BOOL) result value_create_bool(left-data.bool_value ! right-data.bool_value); else result value_create_bool(true); break; ​ case TOKEN_LT: if (left-type VALUE_INT right-type VALUE_INT) result value_create_bool(left-data.int_value right-data.int_value); else { double lv left-type VALUE_INT ? left-data.int_value : left-data.double_value; double rv right-type VALUE_INT ? right-data.int_value : right-data.double_value; result value_create_bool(lv rv); } break; ​ case TOKEN_GT: if (left-type VALUE_INT right-type VALUE_INT) result value_create_bool(left-data.int_value right-data.int_value); else { double lv left-type VALUE_INT ? left-data.int_value : left-data.double_value; double rv right-type VALUE_INT ? right-data.int_value : right-data.double_value; result value_create_bool(lv rv); } break; } ​ value_free(left); value_free(right); return result; }关键细节操作数用完后要value_free释放否则会造成内存泄漏。同时注意eval_expression返回的值所有权归调用者所以eval_binary负责释放左右操作数结果的所有权交给上一级。5. 函数调用函数调用是最复杂的部分需要从环境中查找函数创建新的局部环境绑定参数执行函数体返回结果static Value *eval_call(Evaluator *eval, Env *env, const char *name, ASTNode **args, int arg_count) { Value *func env_get(env, name); if (!func || func-type ! VALUE_FUNCTION) return value_create_null(); ​ // 创建新的局部环境父环境指向调用者的环境 Env *local_env env_create(env); ​ // 绑定参数 for (int i 0; i arg_count i func-data.function.param_count; i) { Value *arg_val eval_expression(eval, env, args[i]); env_define(local_env, func-data.function.params[i], arg_val); } ​ // 执行函数体 Value *result eval_block(eval, local_env, func-data.function.body); ​ env_free(local_env); return result ? result : value_create_null(); }一个重要问题参数求值时使用的是调用者的环境env而不是新创建的局部环境local_env。这样a 10; foo(a)中a能正确解析为 10。6. 代码块求值static Value *eval_block(Evaluator *eval, Env *env, ASTNode *block) { Value *result NULL; for (int i 0; i block-data.block.statement_count; i) { ASTNode *stmt block-data.block.statements[i]; ​ switch (stmt-type) { case AST_VAR_DECL: { Value *init eval_expression(eval, env, stmt-data.var_decl.initializer); env_define(env, stmt-data.var_decl.name, init); break; } ​ case AST_RETURN_STMT: if (result) value_free(result); return stmt-data.return_stmt.value ? eval_expression(eval, env, stmt-data.return_stmt.value) : value_create_null(); ​ case AST_IF_STMT: { Value *cond eval_expression(eval, env, stmt-data.if_stmt.condition); bool cond_true (cond cond-type VALUE_BOOL cond-data.bool_value); if (cond) value_free(cond); ASTNode *branch cond_true ? stmt-data.if_stmt.then_branch : stmt-data.if_stmt.else_branch; if (result) value_free(result); if (branch) return eval_block(eval, env, branch); result value_create_null(); break; } ​ case AST_EXPR_STMT: { if (result) value_free(result); result stmt-data.return_stmt.value ? eval_expression(eval, env, stmt-data.return_stmt.value) : value_create_null(); break; } ​ case AST_FN_DECL: { Value *fn value_create_function( stmt-data.fn_decl.name, stmt-data.fn_decl.params, stmt-data.fn_decl.param_count, stmt-data.fn_decl.body, NULL ); env_define(env, stmt-data.fn_decl.name, fn); break; } } } return result ? result : value_create_null(); }注意return语句会立即返回通过层层return把控制流和值直接传出去不需要遍历完整个块。7. 主循环Value *evaluator_run(Evaluator *eval) { for (int i 0; i eval-ast-data.program.statement_count; i) { ASTNode *stmt eval-ast-data.program.statements[i]; ​ switch (stmt-type) { case AST_VAR_DECL: { Value *init eval_expression(eval, eval-global_env, stmt-data.var_decl.initializer); env_define(eval-global_env, stmt-data.var_decl.name, init); break; } ​ case AST_FN_DECL: { Value *fn value_create_function( stmt-data.fn_decl.name, stmt-data.fn_decl.params, stmt-data.fn_decl.param_count, stmt-data.fn_decl.body, NULL ); env_define(eval-global_env, stmt-data.fn_decl.name, fn); break; } ​ case AST_EXPR_STMT: { Value *r eval_expression(eval, eval-global_env, stmt-data.return_stmt.value); break; } ​ case AST_RETURN_STMT: return stmt-data.return_stmt.value ? eval_expression(eval, eval-global_env, stmt-data.return_stmt.value) : value_create_null(); ​ case AST_IF_STMT: { Value *cond eval_expression(eval, eval-global_env, stmt-data.if_stmt.condition); bool cond_true (cond cond-type VALUE_BOOL cond-data.bool_value); if (cond) value_free(cond); ASTNode *branch cond_true ? stmt-data.if_stmt.then_branch : stmt-data.if_stmt.else_branch; if (branch) return eval_block(eval, eval-global_env, branch); break; } ​ case AST_BLOCK_STMT: return eval_block(eval, eval-global_env, stmt); } } return value_create_null(); }8. 内存管理所有权与生命周期解释器最难的部分是内存管理。每个eval_expression调用返回一个Value*调用者负责释放。规则如下操作语义值由谁释放eval_binary释放左右操作数返回结果由上层释放eval_unary释放操作数返回结果eval_expression字面量返回新分配的值env_get返回借用的指针不释放env_set接管传入值的所有权赋值表达式是内存管理最容易出错的地方case AST_ASSIGN_EXPR: { Value *v eval_expression(eval, env, node-data.assign_expr.value); if (env_set(env, node-data.assign_expr.name, v)) { // env_set 成功值已存入环境caller 不 free Value *stored env_get(env, node-data.assign_expr.name); return stored ? stored : value_create_null(); } else { value_free(v); // env_set 失败时需要自己释放 return value_create_null(); } }env_get返回借用borrowed引用所以如果直接返回env_get的结果不需要释放如果env_set失败则必须释放。9. 示例程序执行过程以一个具体程序为例let x 10; let y 20; fn add(a, b) { return a b; } let sum add(x, y);Step 1词法分析生成 Token 序列。Step 2语法分析生成 ASTProgram: VarDecl: x IntLiteral(10) VarDecl: y IntLiteral(20) FnDecl: add(params: [a, b]) Block: ReturnStmt: BinaryExpr() Ident(a) Ident(b) VarDecl: sum CallExpr(add, [Ident(x), Ident(y)])Step 3解释执行。evaluator_run遍历顶层语句let x 10在全局环境定义x Value(10)let y 20在全局环境定义y Value(20)fn add(a, b) { ... }在全局环境定义add Value(Fn)let sum add(x, y)eval_expression求值右侧CallExpreval_call查找add创建新环境local_env绑定a Value(10)b Value(20)eval_block执行return a beval_expression求值a b→Value(30)遇到return立即返回Value(30)Value(30)存入环境变量sumValue(30)10. 小结本篇介绍了 mini-dog-c 解释器的实现Value 系统用联合体表示运行时各种类型的值环境管理作用域链实现变量的查找和遮蔽表达式求值按操作符类型分发处理注意操作数的释放函数调用新建局部环境绑定参数执行函数体内存管理核心是所有权转移——谁分配谁释放借用引用不释放解释器是编译器中最贴近运行时语义的部分也是内存管理最容易出问题的部分。下一篇也是最后一篇我们将写测试用例并做整体总结。