Ziglings 笔记 72: 编译期解析器 (Inline While)

我,编译器

这是 Ziglings 语法基础部分的最后一关(练习 72)。我们以一个极其硬核的概念结束:在编译期写一个解释器

在普通语言中,解析字符串通常发生在运行时,消耗 CPU 周期。但在 Zig 中,我们可以利用 comptimeinline while,在编译阶段就“吃掉”字符串,吐出纯粹的机器指令。

挑战:字符串变机器码

我们有一串指令:"+3 *5 -2 *2"。 我们需要计算它的结果。 要求:在生成的最终程序中,不能包含这个字符串,也不能包含解析它的循环。程序运行起来必须像是直接写了 value += 3; value *= 5... 一样快。

解决方案

我们需要使用 inline while 来遍历字符串,步长为 3(因为格式是 “符号 数字 空格”):

const print = @import("std").debug.print;

pub fn main() void {
    const instructions = "+3 *5 -2 *2";
    var value: u32 = 0;

    // 编译期索引
    comptime var i = 0;

    // 核心逻辑:inline while
    // 编译器会执行这个循环,直到 i >= instructions.len
    // 每次迭代步进 3 个字符
    inline while (i < instructions.len) : (i += 3) {

        // 1. 解析数字 (编译期计算)
        // '3' - '0' = 3
        const digit = instructions[i + 1] - '0';

        // 2. 解析操作符并生成代码 (编译期展开)
        switch (instructions[i]) {
            '+' => value += digit,
            '-' => value -= digit,
            '*' => value *= digit,
            else => unreachable,
        }
        
        // 注意:instructions 字符串和变量 i 在编译完成后就消失了
    }

    // 最终生成的代码逻辑等价于:
    // value += 3;
    // value *= 5;
    // value -= 2;
    // value *= 2;
    
    // 初始 0 + 3 = 3 -> 3 * 5 = 15 -> 15 - 2 = 13 -> 13 * 2 = 26
    print("{}\n", .{value});
}

核心知识点总结

1. 循环展开 (Loop Unrolling)

inline while 极其强大。在这个例子中,它根据字符串长度自动展开了代码。 如果 instructions 有 1000 个操作,编译器就会生成 1000 行对应的汇编指令。这消除了运行时的循环跳转开销(Branch Prediction Overhead)。

2. 嵌入式领域的应用

这种技术在嵌入式开发中非常有用。例如,你可以把复杂的寄存器配置写在一个易读的字符串或 JSON 文件中,然后用 Zig 在编译期把它解析成一连串高效的内存写入指令。

3. 完结撒花 🎉

至此,我们已经完成了 Ziglings 中关于语言特性的所有核心练习! 我们从 Hello World 开始,一路经过了:

  • 数组与切片
  • 指针与内存
  • 结构体与联合体
  • 错误处理与可选项
  • Comptime 元编程 (最终 Boss)

你现在已经具备了阅读和编写 Zig 代码的核心能力。


后续预告:虽然基础语法学完了,但 Zig 的标准库(Standard Library)是一座巨大的宝藏。接下来的旅程,我们将离开语法练习,通过实际构建项目来探索 std.fs (文件), std.http (网络), std.Thread (并发) 以及著名的 Zig 构建系统。