本文翻译自 Picol: A Tcl interpreter in 500 lines of code,原载于 Hacker News。
简介
Picol 是 antirez(Redis 作者)在 2007 年 3 月 15 日发布的一个 Tcl 风格解释器,仅用 500 行 C 代码实现。最近他重新审视这段代码,发现这是一个相当不错的 C 编程示例,于是放到 GitHub 上归档,并附上原文的要点。
设计原则
在编写这个解释器时,antirez 遵循了几个明确的设计原则:
-
使用正常的 C 编程风格 - Picol 中的代码采用标准的 C 缩进和注释风格,没有为了压缩行数而牺牲可读性。
-
设计类似真实解释器 - 这个项目的核心目的之一是让新手程序员能够通过阅读代码学习如何编写 Tcl 解释器。重点在于写出易于理解的程序,而不仅仅是简短的程序。
-
能够运行非平凡程序 - 解释器不能只会设置几个变量然后打印 “Hello World”,必须能够执行有实际意义的程序。
Picol 解释器特性
解析器
解析器与 Tcl 非常相似,支持变量插值:
set a "pu"
set b {ts}
$a$b "Hello World!"
交互式 Shell
Picol 提供了交互式 shell,直接运行不带参数的程序即可进入:
# 编译
gcc -O2 -Wall -o picol picol.c
# 交互模式
./picol
# 运行脚本文件
./picol filename.tcl
支持的功能列表
- 变量插值 - 支持
"2+2 = [+ 2 2]"或"My name is: $foobar"这样的语法 - 过程定义 - 支持
return,如果缺少return则返回最后执行命令的结果 - 控制流 -
if、if...else、while,以及break和continue - 递归 - 支持递归调用
- 作用域 - 过程内的变量作用域与 Tcl 相同,有真实的调用帧(call frame)
- 内置命令 -
set、+、-、*、/、==、!=、>、<、>=、<=、puts
代码示例
斐波那契数列
proc fib {x} {
if {== $x 0} {
return 0
}
if {== $x 1} {
return 1
}
return [+ [fib [- $x 1]] [fib [- $x 2]]]
}
puts [fib 20]
平方函数
proc square {x} {
* $x $x
}
循环与条件
set a 1
while {<= $a 10} {
if {== $a 5} {
puts {Missing five!}
set a [+ $a 1]
continue
}
puts "I can compute that $a*$a = [square $a]"
set a [+ $a 1]
}
核心设计解析
1. 手写解析器
源码的第一个重要部分是手写的解析器。核心函数 picolGetToken 调用各种解析函数来处理 Tcl 程序的不同部分,并在解析结构中返回 token 类型和起止指针。
2. 执行引擎
picolEval 函数使用解析器执行程序:
- 每遇到分隔符 token 就形成新参数
- 否则将 token 连接到最后一个参数(这就是插值的实现方式)
- 遇到 EOL(行尾)token 时,在解释器的命令链表中查找并执行命令
3. 变量和命令替换
picolEval 本身执行变量和命令替换。解析器能够返回已经去掉 $ 和 [] 的变量和命令 token,然后:
- 在调用帧中查找变量并替换值
- 如果是命令替换,递归调用
picolEval,用结果替换原始 token
4. 命令结构
命令由名称和实现该命令的 C 函数指针描述。命令结构中还包含一个 void* 私有数据指针,用于存储命令的私有数据。
这样可以用单个 C 函数实现多个 Picol 命令。用户定义的过程(procedure)也是类似命令的结构,但通过传递参数列表和过程体作为私有数据,一个 C 函数就能实现所有用户定义过程。
5. 过程调用
解释器结构包含一个调用帧结构,本质上是指向变量链表(变量是包含 name 和 value 两个字段的结构)的指针。
调用过程时:
- 创建新的调用帧,放在旧调用帧顶部
- 过程返回时,销毁顶部调用帧
个人思考
这个项目展示了几个重要的编程理念:
极简主义的威力 - 500 行代码实现一个功能完整的解释器,证明理解核心原理后,复杂系统可以大幅简化。对于想学习解释器实现的开发者来说,这是一个绝佳的入门材料。
代码可读性优先 - antirez 没有为了追求代码行数而牺牲可读性,这种权衡体现了成熟工程师的判断力。在实际项目中,可维护性往往比”炫技”更重要。
学习底层原理的价值 - 虽然现代开发很少需要手写解析器,但理解解析器、解释器的工作原理,对理解编程语言的特性、调试工具链问题都有帮助。
正如 Sir Tony Hoare 所说:
Inside every large program there is a small program trying to get out.
每个大型程序内部,都有一个小程序试图挣脱出来。
总结
Picol 是一个精巧的教学项目,用极少的代码展示了 Tcl 解释器的核心设计。如果你对解释器、编译器感兴趣,或者想学习如何手写解析器,这个项目值得仔细研读。
完整源码可以在 GitHub - antirez/picol 找到。