Skip to content

Latest commit

 

History

History
207 lines (141 loc) · 6.45 KB

File metadata and controls

207 lines (141 loc) · 6.45 KB

学习日志 — 按时间顺序的真实记录

这是完整的、按时间顺序的学习过程记录。包含所有错误、困惑、修复和顿悟。


03:55 — 安装 gforth

$ brew install gforth
==> Pouring gforth--0.7.3_3.arm64_tahoe.bottle.1.tar.gz
🍺 /opt/homebrew/Cellar/gforth/0.7.3_3: 246 files, 4.1MB

安装顺利。版本 0.7.3,2008 年的软件,但依然好用。

03:56 — 第一段代码

3 4 + .    \ → 7

第一反应:"这也太反人类了。" 操作数在前,操作符在后。但写第二行时开始理解——这不是"反人类",是直接操作计算过程。

: square  ( n -- n^2 )  dup * ;
5 square .    \ → 25

:; 原来是定义 word 的"语法"。但它们自己也是 word。一切皆 word。

03:57 — 阶乘和 FizzBuzz

阶乘很顺利:

: factorial  dup 1 <= if drop 1 else dup 1- recurse * then ;
10 factorial .    \ → 3628800

FizzBuzz 的嵌套 IF 很丑(三个 then then then),但能跑。

03:58 — 第一个错误:do...loop

写完变量和数组后,想在解释模式直接打印数组:

5 0 do i myarr @ . loop

报错了!

:1: Interpreting a compile-only word
5 0 >>>do<<< i . loop

原来 do...loop 是编译时 word(compile-only),不能在解释模式直接用。必须包在 : ... ; 里。这是 Forth 编译/解释双模式的第一个坑。

修复:

: show-arr  5 0 do i myarr @ . loop CR ;
show-arr    \ 正常工作

03:59 — CREATE...DOES> 的顿悟时刻

: array  ( n -- ) create cells allot does> swap cells + ;
5 array myarr

这是我学 Forth 最兴奋的一刻。一行代码不是创建了一个数组,而是创建了数组这个概念。以后每次说 5 array xxx,就诞生一个新数组。

04:00 — 音乐 DSL

写了一个迷你音乐记谱语言:

: C 262 ;  : D 294 ;  \ ...
: quarter  60000 tempo @ / ;
: note  ." ♪ " swap . ." Hz for " . ." ms " ;
C quarter note    \ ♪ 262 Hz for 500 ms

实际运行输出和预期完全一致。改成 160 bpm 后时值自动从 500ms 变成 375ms。

04:01 — 元编程实验和更多错误

错误:重定义内置 word

: constant  ( n -- ) create , does> @ ;

gforth 警告:redefined Constant with constant

我不小心覆盖了 gforth 的内置 constant。在 Forth 里这是合法的——你可以重定义任何东西。但 gforth 会警告你。

错误:array-map 高阶 word

我试图写一个对数组每个元素执行操作的 word:

: array-map  ( xt n addr -- )
  swap 0 do
    dup i cells + dup @
    rot rot swap >r >r
    r@ execute
    r> r> !
  loop drop ;

问题do...loop 在 return stack 上存储循环计数器,而我又在循环体内用 >r r> 操作 return stack。两者冲突了。这是 Forth 编程的一个著名坑——return stack 是共享资源,do...loop 会偷偷使用它。

我没有在这次学习中解决这个问题。承认失败。

状态机实验的小警告

variable state  0 state !

gforth 又警告了:redefined state——内置已有 state 变量(用于表示编译/解释模式)。我的代码覆盖了它。虽然在这个实验中没有副作用,但在真实项目中可能导致灾难。

04:02 — 读 JonesForth

开始读 /tmp/jonesforth/jonesforth.S。前 100 行是 Jones 的哲学宣言:

"You really can write a complete FORTH in, say, 2000 lines of code."

读到 NEXT 宏(第 305 行)时被震撼:

.macro NEXT
lodsl
jmp *(%eax)
.endm

两条指令。3 字节。这就是整个 Forth 的执行引擎。

04:02-04:03 — 启动工程师团队

同时启动三个子 agent:

  • Alpha → 哲学与历史
  • Beta → 动手代码
  • Gamma → 源码分析

给每个团队发了我先学到的东西作为"起步指引"。

04:03-04:05 — 继续实验,等待团队

写了状态机实验(遇到了 redefined state 警告)。 写了 return stack 实验(swap-vars、countdown)。 继续读 JonesForth 的 Direct/Indirect Threaded Code 部分。

04:05 — Alpha 完成

收到 17KB 的哲学与历史报告。质量非常高。

04:05 — Beta 完成

收到 534 行的实战报告。写了链表、Sierpinski 三角形(用 bitwise AND)、河内塔。 注意:Beta 的素数筛代码可能有 bug(我验证时它挂起了),但 Sierpinski 和其他实验是正确的。这也是真实的——不是每段代码都完美。

04:06 — Gamma 完成

收到 27KB 的技术分析。覆盖了 JonesForth 完整架构、sectorforth(512 字节的 Forth)、bootstrapping 理论。

04:06-04:10 — 整合和汇报

给 Parafee 写了最终讲解。打包成 GitHub 仓库。


04:18 — 解决所有遗留问题

✅ 问题 1: array-map(return stack 冲突)

根因:在 do...loop 内用 >r r> 与循环计数器冲突。 修复:用变量 map-xtmap-addr 存储执行令牌和基地址,避免 return stack。 额外发现5 myarr 返回第 5 个元素地址(越界),必须用 0 myarr 获取基地址。编译模式下要用 ['] 而不是 '

调试过程经历了 3 次失败:

  1. 第一次:忘了 do...loop 不能在解释模式用
  2. 第二次:' double 在编译模式下会读取下一个 token 导致空字符串错误
  3. 第三次:5 myarr 算出越界地址,Invalid memory address
  4. 第四次:全部修复,['] double 5 0 myarr array-map 正确运行

✅ 问题 2: Beta 的素数筛 bug

根因:原始代码中 dup +loopdup 位置不对,导致栈上残留值或步长为 0,无限循环。 修复:重写 mark-composites,确保 dup+loop 之前正确提供步长。 输出:2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 ✓

✅ 问题 3: Fibonacci 逻辑 bug

根因:不只是 off-by-one。over + swap 的栈操作顺序本身就是错的

  • over + swap(a b) 上:(a b a)(a a+b)(a+b a) — 旧值在栈顶!
  • tuck +(a b) 上:(b a b)(b a+b) — 新值在栈顶 ✓

修复tuck + 代替 over + swap验证:fib(10)=55 ✓

✅ 问题 4: 大型 Forth 程序组织

答案

  1. include 分文件
  2. vocabulary 做命名空间
  3. Chuck Moore 法则:每个 word ≤ 一行
  4. 交互式测试:定义后立即验证

所有修复代码在 experiments/06-solved-problems.fth,全部通过验证。