把语言缩到极小
Brainfuck由乌尔班·米勒于1993年设计,其早期环境是Amiga计算机和Aminet软件档案。塞巴斯蒂安·莫尔的计算机语言研究指出,米勒当时是瑞士物理学学生,第一版编译器只有296字节。这个项目把语言语法与实现体积推向极小规模,兴趣在于测试设计边界,而非提供一套方便编写商业应用的工具。
保存于Aminet的1993年第二次发行包中,其中编译器缩至240字节,另附解释器、汇编与C源代码及示例。米勒在更新说明中感谢克里斯·施奈德提出压缩编译器的建议。这一致谢把极简实现背后的协作具体化:语言有明确的设计者,编译器的小型化也吸收了其他程序员的建议。
八种指令与一条数据带
语言通过指针选择数组中的一个单元。加号与减号改变该单元的值,大于号与小于号移动指针;句点输出字符,逗号读取字符。方括号组成循环,是否继续取决于当前单元是否为零。语言不另设可读的变量名或乘法关键词,复杂操作必须由这些步骤拼成。
发行说明把指针及数组初始化为零,并说明普通非指令字符会被忽略。读程序时必须区分“移动到另一个单元”和“改变单元中的数”,两者虽然都只占一个符号,却改变不同的状态。循环也不是重复固定次数:程序员需要自己安排计数单元、减量操作及指针回到哪里,才能使循环在预期位置结束。
问候程序怎样造出一个字母
原发行包的hello.b开头为“>+++++++++[<++++++++>-]<.”。从零状态逐步读它:指针先向右移动,在第二格放入9;进入方括号后,每一轮回到第一格加8,再返回第二格减1。九轮结束时,第二格归零,第一格积累为72;最后左移并输出,于是得到ASCII字符H。
这里的72并没有作为数字常量写进程序,而是以九次加八构造出来。程序接着改变同一格的数值,依次产生其余字符。只跟踪开头两格,就能看出它以循环换取连续加号的缩短,也能看出阅读成本:读者需要在脑中或纸上保存每格数值及指针位置,单看符号串很难立即识别一句问候。
参考:[2]
编译器和解释器各做什么
第二版缩小编译器付出了代价:要求Amiga OS 2.0,把操作单位改为字节,并生成固定65536字节的可执行文件。编译器自身很小,不表示它产生的每个程序同样只有几百字节。说明书还提醒它不检查括号是否配对,因此文本能够送入编译步骤,并不保证结果可以正常执行。
C解释器提供了另一个观察入口。其switch语句将各符号分派到指针移动、加减和输入输出操作;遇到左括号时扫描对应右括号,再反复解释循环体。源文件也保留括号不平衡和指针越界的报错,并可在带附加参数时用井号输出部分数组值。调试设施由具体实现提供,不能把井号算作语言核心的第九条指令。
计算能力与实现边界
丹尼尔·特姆金的研究强调,Brainfuck每个符号的含义很简单,但组合起来却会迅速掩盖程序目的,连设置一个整数也需要程序员作出选择。莫尔同时提醒,图灵完备性讨论须忽略实际内存上限;抽象计算能力既不意味着有限机器能处理任意大的输入,也不意味着所有数学问题都可求解。
实现之间也并非完全一致。莫尔列出数组边界、单元位宽和输入结束处理等差异;原C源文件自身还有指针范围检查。因此,一段代码在某个解释器成功,不足以证明它适用于全部变体。比较程序结果时,除了八种符号的含义,还应说明内存配置和输入输出约定,避免将实现选择误当作统一标准。
后来成为编译技术的小实验
这种极简语言后来也进入编译器研究。雅各布·施魏格2023年在慕尼黑大学的学士论文,以Brainfuck构建多层中间表示框架MLIR的入门项目。他把基本指令映射为操作,把循环体表示为可包含内部操作的区域,再经多层转换得到LLVM中间表示。
论文的目标是用一个足够小、又能覆盖框架多种功能的语言展示编译流程。其结论明确把实现称为初步编译器,并将更多优化列为后续工作。这个例子说明Brainfuck的延续价值:早期的体积挑战,后来可以转化为研究语义、表示与转换的可控对象;它的目标已从1993年压缩Amiga编译器体积,转向展示现代编译流程。
参考:[4]