mini-dog-c编译器开发 - 05 解释器与代码生成

本篇为 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. 函数调用

函数调用是最复杂的部分,需要:

  1. 从环境中查找函数

  2. 创建新的局部环境,绑定参数

  3. 执行函数体

  4. 返回结果

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:语法分析,生成 AST:

Program:
  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 求值右侧 CallExpr

    • eval_call 查找 add,创建新环境 local_env

    • 绑定 a = Value(10)b = Value(20)

    • eval_block 执行 return a + b

      • eval_expression 求值 a + bValue(30)

      • 遇到 return,立即返回 Value(30)

    • Value(30) 存入环境,变量 sum = Value(30)

10. 小结

本篇介绍了 mini-dog-c 解释器的实现:

  • Value 系统:用联合体表示运行时各种类型的值

  • 环境管理:作用域链实现变量的查找和遮蔽

  • 表达式求值:按操作符类型分发处理,注意操作数的释放

  • 函数调用:新建局部环境,绑定参数,执行函数体

  • 内存管理:核心是所有权转移——谁分配谁释放,借用引用不释放

解释器是编译器中最贴近"运行时语义"的部分,也是内存管理最容易出问题的部分。下一篇(也是最后一篇)我们将写测试用例并做整体总结。

代码转载自:https://pan.quark.cn/s/8ce4326d996e 对于在 CentOS 7 系统中修改网卡配置文件后无法使设置生效的情况,经过实践验证,可以通过使用 nmcli 命令来进行调整。完成修改之后,需要重新启动虚拟机以使更改生效,这样操作流程即告完成。如果设置仍然无法生效,则表明虚拟机在启动过程中所获取的 IP 地址配置并非针对 eth0,此时可以对其它网卡的配置文件进行修改或将其移除。在 CentOS 7 系统中,网络配置的管理机制早期版本存在差异,主要体现为采用了 Network Manager 服务来负责网络接口的管理。在某些情形下,尽管修改了 `/etc/sysconfig/network-scripts` 目录下的 `ifcfg-eth0` 文件,但网络配置却未能即时生效。此类问题的发生通常源于 CentOS 7 采用了不同于以往的配置读取方法。接下来将具体阐述如何借助 nmcli 命令来处理这一挑战。 以 root 用户身份登录系统并打开终端界面。nmcli 是 Network Manager 提供的命令行界面工具,它支持在命令行环境下执行网络连接的建立、编辑、查询及管理任务。针对修改 eth0 网卡配置的需求,可以遵循以下步骤进行操作: 1. 导航至 `/etc/sysconfig/network-scripts` 目录: ``` cd /etc/sysconfig/network-scripts ``` 2. 检查该目录内是否存在 `ifcfg-eth0.bak` 文件,该备份文件可能是先前调整配置时遗留下来的,若存在可能造成冲突。若发现该文件,可以选择将其删除: ``` [root@localhost netw...
代码转载自:https://pan.quark.cn/s/46fd08fb879c 网管教程 从入门到精通软件篇 ★一。★详尽的xp修复控制台指令及其应用!!! 放入xp(2000)的光盘,安装时选择R,执行修复! Windows XP(涵盖 Windows 2000)的控制台指令是在系统遭遇某些意外状况时的一种极具效用的诊断、检测以及恢复系统功能的工具。笔者确实一直期望能够将这方面的指令进行归纳,此次由老范辛苦整理了这份极具价值的秘籍。 Bootcfg bootcfg 命令用于启动配置故障恢复(对大多数计算机而言,即 boot.ini 文件)。 带有特定参数的 bootcfg 命令仅在运用故障恢复控制台时方可使用。能够在命令行界面下运用带有不同参数的 bootcfg 命令。 用法: bootcfg /default 设定默认引导选项。 bootcfg /add 向引导清单中增添 Windows 安装。 bootcfg /rebuild 重复整个 Windows 安装流程并让用户选择需添加的项目。 注意:运用 bootcfg /rebuild 之前,应先借助 bootcfg /copy 命令备份 boot.ini 文件。 bootcfg /scan 探查用于 Windows 安装的全部磁盘并展示结果。 注意:这些结果被静态存储,并用于当前会话。若在当前会话期间磁盘配置发生变动,为获取更新的探查结果,必须先重启计算机,然后再次探查磁盘。 bootcfg /list 列示引导清单中已有的项目。 bootcfg /disableredirect 在启动引导程序中禁用重定向。 bootcfg /redirect [ PortBaudRrate] |[ useBio...
代码下载链接: https://pan.quark.cn/s/fc524f791b68 AA制程,即Active Alignment,被理解为主动对准,是一种用于确定零部件装配中相对位置的方法。在摄像头封装阶段,涉及图像传感器、镜座、马达、镜头、线路板等多个部件的重复组装,而传统的封装设备如CSP及COB等,均是依据设备设定的参数进行零部件的移动装配,因而零部件的叠加误差会逐渐增大,最终在摄像头上表现为拍照最清晰的位置可能偏离画面中心、四边清晰度不均等现象。伴随智能手机和其他高端电子产品的普及,摄像头模组的性能正日益受到重视。高分辨率、卓越的低光表现以及稳定视频输出是现代用户所期望的。在摄像头模组的制造环节,各部件的精准定位对成像质量具有决定性作用。因此,一种名为“AA制程”(Active Alignment)的前沿技术被开发出来,成为摄像头精密对准的核心技术。 AA制程,即Active Alignment,是一种在摄像头封装过程中应用的主动对准方法。该方法在多个组件装配阶段发挥作用,涵盖图像传感器、镜座、马达、镜头和线路板等部件。传统的封装方式,例如CSP(Chip Scale Package)和COB(Chip On Board),依赖于设备预设的参数进行组装,但随着组件数量的增加,误差也会累积,最终影响摄像头的表现。例如在成像质量上可能出现中心位置偏移、四角清晰度不一致等问题。 AA制程技术的核心在于实时监测主动调整。在组装过程中,它借助先进的检测设备持续监控半成品的状态,并根据实时信息对组装部件进行精确修正,从而显著降低装配误差。通过这种技术,能够确保摄像头模组中各组件的相对位置准确无误,从而使得最终的成像效果更加稳定,特别是在中心区域和四角的清晰度上...
内容概要:本文介绍了一套基于Matlab实现的光子晶体90度弯曲波导的二维时域有限差分法(2D FDTD)仿真代码,旨在通过数值模拟手段深入研究光子晶体波导中的光传播特性。该资源聚焦于电磁场光子学领域的仿真技术应用,系统实现了FDTD算法在复杂介质结构中的建模过程,涵盖空间网格剖分、时间步进迭代、完美匹配层(UPML)边界条件处理、总场散射场(TFSF)激励源设置、介电常数分布定义及电磁场演化可视化等核心模块,能够有效分析光在90度弯曲波导中的传输效率、模式分布反射损耗等关键性能指标。; 适合人群:具备电磁场理论基础和Matlab编程能力的研究生、科研人员以及从事光子晶体器件设计仿真的工程技术人员。; 使用场景及目标:①用于教学演示FDTD方法的基本原理算法流程,帮助理解麦克斯韦方程的离散化求解过程;②支撑科研工作中对光子晶体弯曲波导结构的传输特性进行仿真分析性能优化;③作为开发更复杂光子集成器件(如分束器、滤波器)数值仿真工具的基础框架; 阅读建议:建议使用者结合经典FDTD教材(如Taflove著作)深入理解算法理论,并在Matlab环境中逐模块调试代码,重点关注电场磁场的交替更新过程、UPML吸收边界的设计实现以及TFSF源的引入方式,从而全面提升对时域电磁仿真机制的掌握应用能力。
内容概要:本文围绕直驱式永磁同步电机(PMSM)的矢量控制仿真模型展开研究,基于Simulink平台构建了完整的电机控制系统仿真模型,涵盖电机本体建模、坐标变换(如Clark变换Park变换)、磁场定向控制(FOC)、电流环速度环的PI调节、空间矢量脉宽调制(SVPWM)等核心技术环节,旨在实现对电机转矩转速的高精度、动态响应良好的控制。通过系统化仿真验证控制策略的有效性鲁棒性,深入分析各模块间的信号流向控制逻辑,为电机驱动系统的设计优化提供理论依据和技术支撑,是理论联系工程实践的重要桥梁。; 适合人群:具备电机学、电力电子自动控制基础知识,熟悉Simulink/MATLAB仿真环境,从事电气工程、自动化、新能源车辆、智能制造等方向的研究生、科研人员及工程技术人员。; 使用场景及目标:①深入理解永磁同步电机矢量控制的核心原理系统架构;②掌握在Simulink中从零开始搭建复杂电机控制系统的方法技巧;③应用于课程设计、毕业论文、科研项目中的控制算法验证、参数整定性能优化;④为后续的硬件在环(HIL)测试或实物系统开发奠定仿真基础。; 阅读建议:建议结合经典电机控制理论教材同步学习,注重理论推导仿真实现的对应关系,动手实践模型搭建、参数调试波形分析,特别关注PI控制器参数整定对系统稳定性、动态响应速度和抗干扰能力的影响,通过反复仿真迭代加深对控制机理的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值