我如何在不懂原理的情况下编写一个Forth

Lobsters Hottest 工具

摘要

本文详细讲述了作者通过利用Lua的C API并参照JonesForth,在最初并不了解如何构建Forth语言的情况下,编写MoonForth(一种Forth编程语言实现)的经历。

<p><a href="https://lobste.rs/s/ccy1nr/how_i_wrote_forth_without_knowing_how">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/09/15 15:15

# 我是如何写出一个Forth(却不知其所以然)的(vtrlx.ca) 来源:https://vtrlx.ca/w/2026-09-10-how-i-wrote-moonforth.html ## Victoria Lacroix [主页](https://vtrlx.ca/) [文章](https://vtrlx.ca/posts.html) [应用](https://vtrlx.ca/apps.html) ## 2026-09-10 前文:什么是Forth?(https://vtrlx.ca/w/2026-09-08-what-is-forth.html) 过去几周,我一直在编写一个Forth编程语言的实现。在着手编写MoonForth之前,我从未写过Forth,也并不真正知道如何编写。即使在完成基础部分并能用MoonForth编写真正的程序后,我仍然无法告诉你我是如何做到的。这其中有几个原因。 [MoonForth on Codeberg](https://codeberg.org/vtrlx/moonforth/) 首先,我没有对过程做充分的笔记。其次,即使我做了笔记,我认为这也没太大意义,因为编写一个Forth的过程异常简单直接,以至于几乎没什么可记录的。我在这里呈现的内容主要来自记忆。 在评估了我的选择并发现关于如何编写自己的Forth的详细指南寥寥无几之后,我决定遵循Forth圈子里常见的建议,从JonesForth的源代码开始。 [JonesForth (非官方Github镜像)](https://github.com/nornagon/jonesforth/) JonesForth是用x86汇编语言编写的Forth。它完成了工作,代码中散布的大量注释有助于解释发生的细微之处——尽管我觉得它有时过于冗长。 因为我的目标是用C语言编写一个Forth,我知道JonesForth的汇编语言不会直接适用于我的项目。我也发现解释器和编译器有点难以理解,所以我干脆先绕过它们。我想,如果在其他方面取得足够进展,我以后会有更大的动力去弄清楚那些部分,并且可能有足够的洞察力让后续步骤更容易。 对我来说很有帮助——实际上这也是促使我这么做的部分原因——在于Lua内部使用栈在函数之间传递值。在我看来,在Lua的C API之上编写Forth的最简单起点,就是编写直接操作该栈的函数,并打算让这些函数成为某些内置核心Forth词的实现。几个小时后,我就有了一小组操作Lua栈的函数。尽管还无法实际运行它们,但很容易就假定这些就是我的第一批Forth词。在实现重要的核心词时,我也决定尽可能使用我自己已有的词。当我开始编写更复杂的词时,我自己的函数与调用Lua自身的API混在一起。甚至在我运行一行代码之前,我已经在使用我的Forth的骨架来进一步扩展它了。 一旦简单的栈操作函数就位,编写更复杂的函数就变得更容易了。因为我写的所有东西都被设想成尽可能直接类比于Forth自身的栈操作词,这也意味着即使没有关于如何在此基础上构建Forth的概念,我也能够逆向应用Forth的因子化概念。我查看了JonesForth的源代码,观察硬编码的词如何工作,然后简单地为MoonForth在Lua的C API上编写我自己的实现,打算以后再弄明白。 最终,我恍然大悟。 C函数可以转换为指针,指针可以作为用户数据推送到Lua中。因为Forth词只是一系列其他词,我可以将一个自定义词的定义存储为一个指向C函数的指针表。因为Lua表总是引用,我也可以简单地将其他词定义的表存储到使用它们的更复杂的词中。采用这种方式进行编译的另一个好处是,词的重定义可以像大多数Forth系统一样工作,即新词遮蔽旧词时,不会改变任何使用旧词的已编译词。还有一个巧妙的技巧:我应该可以轻松地将字符串和数字字面量存储在词定义中。解释器只需要读取词定义中每个存储项目的类型,就能确定如何处理它。 就在这个时候,我开始担心——因为一旦你弄清了数据结构以及实际与该数据结构交互的方法,代码就会变得非常容易编写——我可能真的能成功。 尽管这是一个有点令人沮丧的反复试验的过程,但编写解释器并不困难。当它不可避免地无法工作时,我只需要用打印语句(因为我就是这种类型的开发者)跟踪程序的执行,然后逐一排除错误。在决定尝试基于Lua的C API编写Forth的三个工作日后,我运行了我的第一行代码。 编译器很快也到来了。没过多久就让它和解释器对齐,基于对世界共同的理解协同工作。已经对立即词的概念有了想法,实现它们也相当容易。 然后我看了JonesForth对流程控制的实现。像最酷的Forth一样,JonesForth直接用更简单的词在Forth中实现了流程控制。MoonForth也不例外。JonesForth的代码对MoonForth甚至不特别适用,因为它无法直接访问词定义。解决这个障碍相当容易,只需编写那些允许分支指令稍后回填的词,就像其他Forth实现流程控制的方式一样。实现方式可能与JonesForth大相径庭,但结果是MoonForth现在也有了if/else/then语句和循环。就在这一刻,我关闭了JonesForth源代码的浏览器标签页。不再需要指导了。在一周内,我从不知如何写Forth,到在自己的Forth中实现核心语言特性。 本千禧年最著名、流传最广的Forth文章之一是Dave Gauer关于Forth历史的文章。 [Forth:那个编写自己的编程语言](https://ratfactor.com/forth/the_programming_language_that_writes_itself.html) 在这篇回顾中,Dave断言Forth——就其本质而言——是一种"编写自己"的编程语言。在我第一次读这篇文章时,我并不理解这个断言意味着什么。我现在明白了。只要对Forth应该如何工作有一个模糊的概念,任何会写代码的人都应该能相当容易地构建一个Forth。接下来需要构建的东西总是清晰可见的,并且通常只需要注意到自己的Forth缺少什么就能激发灵感。发现这些缺口就像尝试编写一个程序却发现有东西阻碍了进展一样简单。如果你知道你面临的问题,知道解决方案应该是什么样子,并且知道如何将源数据操作成想要的结果,那么对结果进行编程就只是扩展你已有东西的简单事项。当编程语言很小而且是你自己编写的时候,这是一项令人愉快的轻松任务。 ## 已有实现 MoonForth不是第一个将Forth带到Lua上的尝试。 Eduardo Ochs在2008年写过如何从Lua引导Forth。 [用40行Lua代码引导一个Forth](http://angg.twu.net/miniforth-article.html) 乍一看,网上也流传着一些用Lua编写的Forth实现。 [vifino/luaforth on GitHub](https://github.com/vifino/luaforth) [iigura/tinyLuaForth on GitHub](https://github.com/iigura/tinyLuaForth) MoonForth不是用Lua编写的。相反,它是用C编写的,并使用Lua的C API来实现其目标。不过,MoonForth并非首创——存在另一个名为Luarth的项目,似乎旨在实现相同的目标。 [aabacchus/luarth on GitHub](https://github.com/aabacchus/luarth) 如果Luarth不是不完整——无法定义自定义词,并且也缺少其他核心语言特性——我本可以使用这个实现,而完全放弃编写MoonForth。 MoonForth与Luarth的一个显著区别在于,后者将其内置词实现为完整的Lua函数。由于Lua处理自身调用的方式,Lua函数无法操作栈。另一方面,MoonForth将内置词实现为普通的C函数,并通过简单地解引指针来调用它们。因为Lua调用栈完全不涉及(除了调用Lua内部时),MoonForth的词可以自由操作栈而不干扰Lua的内部状态。我怀疑这可能是我自己的项目能够推进而Luarth没有的原因之一。无论如何,它是一个启发——例如,MoonForth自己的lua-call词与Luarth的功能完全相同。 ## 为什么还要写另一个Forth?难道还不够多吗? 在Forth爱好者中,一个常被引用的梗是Forth爱好者倾向于编写自己的Forth,而不是学习现有的。另一句流传的话是,编写自己的Forth就像试图仅通过解剖来理解一种动物——Forth是一个必须通过使用才能理解的活的编程系统。在经验尤其丰富的Forth爱好者中,编写自己的Forth而不是学习任何现有Forth的冲动,往往被嘲笑。 编写MoonForth的原因是为了拥有一个能够轻松与我足够了解和理解的高级语言交互的Forth,同时其本身又不构建在该高级语言之上。这种架构决策更多是出于概念而非性能考虑。我认为用一门本身只是构建在Lua之上的语言来编写使用Lua库的应用程序是没有意义的——在那种情况下,为什么不直接使用Lua呢? 至于为什么我想要构建一个Forth... 嗯。 你在科技行业工作吗?我不在,但像许多被迫游离在行业边缘的其他人一样,我无法容忍时尚垃圾软件(fashslop)在软件栈中蔓延。 尽管许多Forth爱好者自己也屈服于了Vibe编码(viberotting),但Forth在关于摆脱不道德软件提供商依赖的对话中占据着一个有趣的位置。因为Forth可以很容易地由单个开发者使用少量工具构建,它为尝试替代计算机生态系统提供了一种简单的方式。正是从这种背景出发,我对Forth特别感兴趣,尽管如果没有一个能与我通常使用的高级编程环境交互的Forth,我永远无法在其中感到舒适。因此,MoonForth是一个宝贵的机会,让我学习如何实现Forth,如何用Forth解决问题,当然还有如何使用Lua的C API。 至于为什么坚持使用Lua而不是更深入地探究软件栈,是因为我确信Lua很可能相对不受云端代码(clod code)祸害的影响。Lua的维护者有着悠久的历史,即使对于理想的功能也拒绝大多数提交的补丁,更愿意自己做所有事情。Lua也花了很长时间没有增长太多,几乎没有外部压力去改变。在这种情况下,维护者或首席开发人员不太可能觉得有必要求助聊天机器人来处理大规模更改,因为Lua一直对此概念过敏。这种曾经让我很容易学习这门语言的宝贵特质,已成为我紧紧依附于整个生态系统的极具说服力的理由。 我确信我对形势的判断是正确的。如果不是,我可能会转向一个不再支持的Lua版本,直到这阵风潮过去。 在Lua上构建Forth的另一个原因是可移植性。Forth通常为特定硬件构建以驱动其特性。MoonForth的唯一依赖是Lua本身——因此它应该可以在任何能找到Lua的地方使用。仅这一点就与更传统的Forth相比具有显著优势。我还确保了MoonForth与Lua 5.1兼容,这开启了与该语言的许多其他替代实现(如LuaJIT)的互操作性。 ## MoonForth的当前状态 它能工作。可以用它编写真正的程序。我设法用MoonForth编写了一个简单的GNOME应用,以及一个用于LÖVE游戏引擎的概念验证程序。我还没有做的是在MoonForth中编写任何严肃的东西。现在我已经为后世记录了它的早期开发过程,下一步就是真正使用它。如果我无法让它运行真正的软件,那这一切都将毫无意义。那将是徒劳的。看看我能否胜任这项任务。 [回复此文章](mailto:[email protected]?subject=How%20I%20Wrote%20a%20Forth%20(Without%20Knowing%20How))

相似文章

Forth 与 Lisp 的迁移之道

Lobsters Hottest

本章节选自《Let Over Lambda》,探讨如何使用宏在 Lisp 中实现 Forth 编程语言,强调语法的二元性和元编程技术。旨在向 Lisp 读者教授 Forth 概念,并讨论 Forth 设计背后的哲学。

教孩子们Forth编程

Lobsters Hottest

一位程序员分享了他们教初中和高中生Forth编程语言的经验,解释了为什么选择它而不是Python或Scratch,以及他们如何为12节课的课程设计教学大纲。