最近在鼓捣自制的 Z80 单板机,跑的是 CamelForth。用着用着,总觉得它的交互体验有点别扭,不太符合我对 Forth 系统的一贯认知。最直观的一点就是命令执行后的反馈格式。
比如,输入 1 2 + .<回车> 这个标准表达式,原版 CamelForth 的输出会是下面这样:

实际上,我们更习惯看到的结果应该是这样,一个简洁的 “ok” 紧随其后,而不是换行后再显示:

如果说这还只是美观上的小瑕疵,那么在定义新词(Word)时,这个问题就更让人难受了。写一个简单的循环计数器测试代码:
: COUNTER ( n -- n )
BEGIN
DUP .
1+
KEY?
UNTIL
KEY DROP
;
在交互模式下输入这串定义,屏幕上的反馈简直让人抓狂:

这显然不能忍!于是,我决定动手研究一下 CamelForth 的 源码分析,找出问题根源。经过一番追踪,问题出在 QUIT 这个核心交互循环的实现上。我的目标是让它在执行完命令后,直接在同一行输出 “ok”,并且只在真正需要换行时才换行(比如进入编译模式后)。
修改的过程并不复杂,主要集中在源码中的两个文件。最终的修改效果令人满意:

现在,无论是直接执行表达式还是定义新词,交互反馈都变得清晰、整洁,完全符合预期。看着终端里规整的输出,心情都舒畅了。
以下是具体的代码补丁。为了方便追溯,我顺手在启动信息里也加上了自己的标记和日期。
diff --git a/camel80h.asm.m4 b/camel80h.asm.m4
index bd43bb6..445d79f 100644
--- a/camel80h.asm.m4
+++ b/camel80h.asm.m4
@@ -679,10 +679,10 @@ INTER9: DW DROP,EXIT
QUIT1: DW TIB,DUP,TIBSIZE,ACCEPT,SPACE
DW INTERPRET
DW STATE,FETCH,ZEROEQUAL,qbranch,QUIT2
- DW CR,XSQUOTE
- DB 4,"OK",13,10
+ DW XSQUOTE
+ DB 3," ok"
DW TYPE
-QUIT2: DW branch,QUIT1
+QUIT2: DW CR,branch,QUIT1
;C ABORT i*x -- R: j*x -- clear stk & QUIT
; S0 SP! QUIT ;
diff --git a/main.asm b/main.asm
index df920cf..d46e814 100644
--- a/main.asm
+++ b/main.asm
@@ -51,5 +51,6 @@ SECTION rodata_user
signon_msg:
DEFM 13, 10
DEFM "RC2014 - CamelForth BootROM - v20230819", 13, 10
- DEFM "Ported to RC2014 ROM by Justin Skists", 13, 10, 0
+ DEFM "Ported to RC2014 ROM by Justin Skists", 13, 10
+ DEFM "Patched by BG1REN / 2026-02-21", 13, 10, 0
补丁解析:
-
camel80h.asm.m4 中的修改:
- 移除了输出 “ok” 之前的
CR(回车换行)指令。
- 将输出的字符串从
"OK",13,10(包含回车换行符)改为 " ok"(仅为一个前导空格和文本)。
- 将原本在
QUIT2 标签处的无条件跳转 branch,QUIT1,修改为 CR,branch,QUIT1。这意味着只有在不打印 “ok” 的情况下(即处于编译状态时),才会先执行换行再跳回循环开始。
-
main.asm 中的修改:
- 在启动信息中追加了一行,记录这次补丁的作者和日期,便于后续维护。
这次对 Z80 CPU 上运行的 CamelForth 交互逻辑的调整,虽然改动量很小,却显著提升了使用体验。它让我再次感受到,深入理解一个 栈式虚拟机 的内部机制后,进行定制化修改是多么直接有效。如果你也在复古计算或嵌入式 Forth 系统开发中遇到了类似问题,希望这个案例能带来一些启发。更多关于底层开发和系统移植的讨论,欢迎在 云栈社区 交流分享。