Tiny-Lua-Compiler: 可能是有史以来最小的 Lua 编译器
摘要
Tiny-Lua-Compiler 是一个用于教学的、自举的 Lua 5.1 编译器和虚拟机,完全用纯 Lua 编写。其设计目标是体积足够小以便于研究,同时又功能完备到足以处理真实的语言特性。
<p><a href="https://lobste.rs/s/np3vpy/tiny_lua_compiler_possibly_smallest_lua">评论</a></p>
查看缓存全文
缓存时间: 2026/05/10 22:56
bytexenon/Tiny-Lua-Compiler 来源:https://github.com/bytexenon/Tiny-Lua-Compiler Tiny Lua 编译器 (TLC) 一个集教育性质的 Lua 5.1 编译器、字节码发射器和虚拟机于一体的单文件 Lua 项目 灵感来源于 Jamie Kyle 的 The Super Tiny Compiler (https://github.com/jamiebuilds/the-super-tiny-compiler) 许可证:MIT Lua Tiny Lua Compiler (TLC) 是一个用纯 Lua 编写的完整 Lua 5.1 编译器。它对源代码进行词法分析,构建抽象语法树 (AST),将其降低为 Lua 5.1 函数原型,发射真实的 Lua 5.1 字节码,并可以在其自带的基于寄存器的虚拟机中执行这些原型。核心代码全部包含在 tlc.lua 中。大多数编译器学习材料可以分为两类。一类是玩具编译器,虽然易于完成,但跳过了使真实语言变得有趣的部分;另一类是生产级编译器,虽然真实可靠,但规模庞大,使得主要思想被架构和历史包袱所淹没。TLC 旨在介于两者之间。它足够小巧,你可以在一个周末内读完,但又足够真实,能够处理词法作用域、闭包、向上值 (upvalues)、可变参数、多返回值、方法调用、循环、尾调用、字节码编码和执行。它不是生产级编译器,也不打算取代标准的 Lua 实现。它是一个教育性质的编译器,力求诚实:小到足以理解,完整到值得研究。 ## 它可以编译自身 TLC 可以编译其自身的源代码,并在其自己的虚拟机中运行结果: lua local tlc = require("tlc") local tlc2 = tlc.run(io.open("tlc.lua"):read("*a")) tlc2.run("print('Hello from a compiler running inside itself')") 这意味着一个用 Lua 编写的编译器正在编译另一个用 Lua 编写的编译器,然后编译后的编译器又运行新的 Lua 代码,这一切都在不离开宿主进程的情况下完成。 ## 试用方法 bash git clone https://github.com/bytexenon/Tiny-Lua-Compiler.git cd Tiny-Lua-Compiler # 在 TLC 自己的虚拟机中运行代码。 lua5.1 -e "require('tlc').run(\"print('Hello from TLC!')\")" # 编译为二进制 .luac 块并使用标准 Lua 虚拟机运行它。 lua5.1 -e "io.open('out.luac','wb'):write(require('tlc').compile('print(42)'))" lua5.1 out.luac lua5.1 tests/test.lua 你还可以将 TLC 用作库,根据需要选择任意详细程度: lua local tlc = require("tlc") -- 单行命令:编译并运行。 tlc.run("print('Hello from TLC!')") -- 编译为标准 Lua 虚拟机可以加载的二进制 .luac 块。 local bytecode = tlc.compile("return 21 * 2") -- io.open("out.luac", "wb"):write(bytecode) -- 如果需要,保存到磁盘。 -- 逐步遍历流水线。 local tokens = tlc.tokenize("local x = 1 + 2; return x") local ast = tlc.parseTokens(tokens) local proto = tlc.generate(ast) local value = tlc.execute(proto) print(value) -- 3 ## 为什么这个文件值得一读 代码是线性执行的。首先是工具函数,然后是词法分析器、解析器、代码生成器、字节码发射器、虚拟机以及公共 API——按此顺序排列,井然有序。你可以追踪单个源程序通过每个阶段的过程而不会迷失方向。实现中还保留了许多玩具编译器通常会跳过的细节。字符分类使用预计算的查找表。运算符匹配使用 Trie 树进行最长前缀匹配——无需手动实现前瞻。表达式通过优先级爬升 (precedence climbing) 处理,而不是为每个级别编写语法规则。连接链被扁平化为单个 CONCAT 操作。浮点数通过手工方式打包为 IEEE 754 格式,而不使用 string.pack。向上值捕获和 OP_CLOSE 显式处理。这些并非锦上添花,而是真实编译器行为开始显现的地方。跳过它们,你只学到了编译的轮廓;保留它们,你学到了它实际如何工作。 ## TLC 涵盖的内容以及未涵盖的内容 TLC 涵盖了足够多的 Lua 5.1 特性,使其感觉真实: - 词法作用域、闭包、向上值捕获与关闭 - 数值和通用 for、while、repeat、do、break、return - if / elseif / else - 方法调用(: 语法)、表构造器 - 多返回值、可变参数 (...)、尾调用优化 - 长字符串、字符串转义、十六进制数、科学计数法 - 完整的 Lua 5.1 字节码发射——输出可在标准虚拟机中加载 它故意省略的内容同样重要。没有常量折叠。没有调试信息——即映射每条指令到源码行的表;没有它,错误消息不显示行号,但字节码本身是正确的。最大的遗漏是元方法分派。当 a 是表时写 a + b,标准 Lua 会检查 __add。TLC 的虚拟机完全跳过这一步——运算符仅对原生类型有效。这移除了一项真实特性,但避免了虚拟机演变成对象系统。这种权衡是刻意为之。TLC 试图成为一个你可以真正读完的真实编译器。 ## 正确性 测试套件使用 TLC 和标准 Lua 分别编译每个用例,然后并排比较结果。没有模拟期望——如果 TLC 产生不同的输出,测试失败。这捕捉到了教育性编译器通常能蒙混过关的错误:错误的运算符优先级、损坏的闭包语义、多返回值调整错误、循环控制流 bug,以及不正确的字面量解析等。 ## API lua local tlc = require("tlc") tlc.run(code, env?, ...?) tlc.compile(code) tlc.compileToProto(code) tlc.parse(code) tlc.tokenize(code) tlc.parseTokens(tokens) tlc.generate(ast) tlc.emit(proto) tlc.execute(proto, env?, ...?) docs/api.md 记录了公共 API,docs/ast.md 记录了 AST 结构。 ## 从哪里开始 阅读本文件了解整体概况,然后从头到尾阅读 tlc.lua。之后,docs/api.md 和 docs/ast.md 补充参考材料,tests 展示行为表面。TLC 运行于 Lua 5.1 至 5.5,尽管生成的字节码面向 Lua 5.1。欢迎贡献;见 CONTRIBUTING.md。如果你报告 bug,请包含输入代码、预期行为、实际行为和 Lua 版本。 ## 参见 - The Super Tiny Compiler (https://github.com/jamiebuilds/the-super-tiny-compiler) - 原始灵感;一个用 JavaScript 编写的约 200 行的编译器 - FiOne (https://github.com/Rerumu/FiOne) - 一个 Lua-in-Lua 虚拟机,比 TLC 更完整,但更注重可读性 - Lua 5.1 源码 (https://www.lua.org/source/5.1/) - 参考实现;llex.c、lparser.c 和 lvm.c 是最相关的文件 ## 许可证 MIT。见 LICENSE。
相似文章
Lunacy - 具备惰性基本块版本化与JIT的Lua 5.1解释器
Lunacy 是一个用Rust编写的Lua 5.1解释器,实现了惰性基本块版本化和即时编译器,详细信息见技术博客文章。
7行代码,3分钟:实现一种编程语言(2010)
本文介绍了一种基于 Lambda 演算的图灵完备函数式语言的极简 7 行解释器,展示了 eval/apply 设计模式。
我构建了一个将Python重写为面向模型表示的编译器
Vulpine是一个编译器,它将人类可读的Python代码转换为针对LLM优化的压缩宏表示,平均减少13.8%的token数,同时支持精确的结构重建。
一台从电脑文件夹运行、可训练小型LLM的迷你计算机
VirtualPC 是一个开源8位计算机模拟器,能够从汇编代码训练小型神经网络,在裸机层面展示机器学习。
Blaise – 一款面向 QBE 的现代、自举、无历史包袱的 Object Pascal 编译器
Blaise 是一款现代且自举的 Object Pascal 编译器,旨在通过提供单一语言模式、统一的内存模型以及基于 QBE 的原生代码生成,来消除遗留系统的负担。