编程语言研究书库

第 12 章拆解 — 手搓一门语言:zcalc 解释器

手搓一门语言:zcalc 解释器

这一章讲什么: 编程语言是怎么被造出来的——不是原理课,是手工课。 先写一页 EBNF 文法当蓝图,然后三段流水线:词法器(字节 → token)、 解析器(token → 语法树)、求值器(树 → 值),最后包一个 REPL。 书的定位说得很准:这是系统编程书的合格收官,因为它把前十一章的每门手艺 (流处理、显式内存、哈希表、递归)都用在一道题上1。

主线例子:书上自带的三角形程序,从源码字符一路走到返回值 Large,每个阶段都有具体中间产物可对账。

先交代来源(不影响阅读): 原书第 13 章「Unlock Your Exclusive Benefits」是 Packt 的购书福利解锁说明(凭发票到 packtpub.com/unlock 兑换免费内容),不含技术内容,本篇仅作登记,不拆解2。

1. 顶层全景:三段流水线 + 四个源文件

  源码字节流  ──lexer.zig──▶  token 序列  ──parser.zig──▶  AST  ──evaluator.zig──▶  运行时值
  "let a = 10;"              .Let .Ident a                Stmt[0]=Let{a,10}        env{a:10}
                              .Assign .Int 10                                        → Void
                              .SemiColon                                             → ReturnWrapper
                                                                                      → 40

  repl.zig = 三段合并 + 交互外壳(文件模式批跑 / 无参数进 REPL)

  图说:lexer/parser/evaluator 三个文件各自自包含、各带 main()——刻意的冗余,
  让每个阶段可以独立编译、独立调试、独立验证;repl.zig 才是最终成品。[^3]

2. 文法先行的道理:EBNF 当蓝图

造语言第一步不是写代码,是写文法——用 EBNF(扩展巴科斯-瑙尔范式)精确描述什么字符串合法。记法当场学会:= 左边是规则名,右边是定义;| 分隔候选('true' | 'false' 二选一);括号分组;? 表示可选(0 或 1 次);* 零次或多次;引号里的是终结符(源码里真实出现的字面字符),其余是规则名3。

zcalc 的文法里有两处设计值得点名:

  • 语句与表达式严格分家:语句(let/赋值/return/if)是控制流的骨架,执行动作、改环境,但不产生值;表达式是计算引擎,必须能归约成一个原始值4。语言里 let 声明和 = 赋值分开不是啰嗦——环境因此能明确区分「引入新标识符」和「改已有状态」,未定义变量这类运行时错误才抓得住5。
  • if 的分支强制花括号:文法层面消灭了臭名昭著的悬垂 else(dangling-else)歧义,作用域边界对解析器和程序员都刚性清晰6。

分层表达式天然编码优先级:Equality → Relational → Addition → Multiplication → Primary 一层层下降,乘除比加减绑得紧,比较最低;树的形状本身就是运算顺序,求值器永远不用做任何优先级排序7。括号在 Primary 层,给程序员手动推翻内置优先级的机会8。

3. 词法器:两个字节的游标

程序一开始只是无差别的字节流。词法(lexing) 把原始字符分组成有意义的词(token)——空格丢弃、注释跳过、数字和关键字识别,给下一阶段一个干净可预期的词汇序列9。

Lexer 的状态只有三个:输入切片、position(正看的字符)、read_position(下一个字符),外加缓存当前字符的 ch。双指针是词法器的心脏:消费当前字符的同时能偷看下一个——区分 =(Assign)和 ==(Eq)全靠这一眼10。哨兵设计:ch 用 0 表示输入结束,干净映射到 .Eof token,readChar() 里零特判11。

nextToken() 的主循环:skipWhitespace() 后按 ch 分派;双字符运算符先 peekChar() 再决定;标识符/数字读出切片(窗口直指原输入缓冲,词法器零堆分配)后提前返回——因为辅助函数已把游标停在了词素外第一个字符,不需要尾部 readChar()12。关键字识别抽成纯函数 lookupIdent():四次等值比较,全不中就是用户标识符——主分派因此永远不用懂关键字13。

字符串是唯一的分配点:读到 \ 就吞掉下一字节做转义翻译(\n→换行、" 和 \ 按字面),反斜杠后的字节永远作为转义的一部分被消费,所以转义的反斜杠能正确保护后面的引号,不干扰终止符搜索;EOF 先到时回报 error.UnterminatedString,而不是把到 EOF 为止的一切静默当字符串14。

实测(math.small):四行源码出 22 个 token,从 .Let "let" 到 .Eof "" 一览无余——给语言加新语法时,这个输出就是解析器将收到的输入,是最好的诊断工具15。

4. 解析器:递归下降与那棵树的形状

token 序列是平的,平就没有层级——它表达不了「第三行的乘法必须先于加法算」16。解析就是把文法强加到平序列上,建起抽象语法树(AST);递归下降解析器把每条文法规则直接翻译成一个函数调用,读文法就是在读解析器的执行流17。

两个类型撑起全部节点:Stmt(let/assign/return/expression/if 五变体)与 Expr(整数字面、字符串、标识符、Binary 四变体)18。Binary 的两个孩子必须是堆上指针——Zig 的标记联合不能直接内嵌递归类型,编译器算不出大小;allocator.create(Expr) 两次,孩子只以 *Expr 被知晓,递归尺寸依赖就此斩断19。所有 AST 内存来自进程 arena,退出一次释放——所以全章没有 deinit;REPL 整个会话共用一个 arena 不复位,内存随会话增长,学习可接受,长命解释器不行20。

解析器持两个 token 槽:cur 和 peek(词法器双指针的同款设计),init 里连调两次 nextToken() 预装21。分派里最有味道的是 .Ident 分支:一次 look-ahead——peek 是 .Assign 就是赋值语句,否则是表达式语句;「这是消解该歧义所需的最小上下文,无需回溯」22。parseLetStatement 进三步(expectPeek 到标识符、过 =、落到值表达式的第一个 token),可选分号用条件 peek 而非强制——对省略更宽容23。parseBlock 与 parseProgram 结构相同,只差终止条件(.RBrace 对 .Eof),自然可组合24。

表达式层的模式四条函数一个模子:parseAddition 先取 parseMultiplication() 当左操作数,while peek 是 +/- 就循环——左结合由此而来:左操作数在返回前累积了 successive 右端;优先级由调用链保证(Equality 最低、Primary 最高),等树建完,高优先级操作已坐得更深、被求值器先算25。

实测 math.small:四语句,第三条打印成 Binary{...}——树的形状记录了 a*2 与 b*4 是两个独立乘法、它们的结果才是加法的两个操作数。这个由解析器的优先级分层烙进树形的结构事实,正是求值器需要的:它从上到下走一遍,正确的运算顺序从节点所在的深度自然涌现26。

5. 求值器:环境、真值表与 ReturnWrapper

运行时值四变体外加一个秘密成员:Integer/String/Boolean/Void,以及 ReturnWrapper——不是用户可见类型,是控制流信号:遇到 return,把值装箱,沿每一次递归调用向上传播,直到最外层循环拆箱交出最终结果27。程序的内存是包着 StringHashMap 的 Environment,分配器存在环境内部,ReturnWrapper 的堆分配和字符串拼接都从这同一个 arena 取,不必给每个求值函数单传分配器28。

eval() 按语句变体分派:let/assign 都是「求值表达式 → 存环境」,唯一差别是映射键(解析期已验证);return 装箱;if 查 isTruthy() 后委托给 consequence 或可选的 alternative29。evalBlock 一遇 ReturnWrapper 立即短路——这就是 return 能退出整个外层块而不只是当前语句的机制30。真值规则:布尔直查、整数非零为真、其余一切(含字符串与 Void)默认真——简单约定,条件求值可预期31。

表达式求值里,Identifier 查不到就打印 Runtime Error: Identifier not found 并返回错误——运行时错误,不是解析期32。Binary 全在这一层(dispatch):双整数全运算符可用,@divTrunc 截断除法;字符串唯一接受的运算符是 +(拼接),其余组合一律 error.TypeMismatch33。

书对求值器局限的坦白是全章最实用的一页,三个坑都摆明:

  • 布尔不能当操作数:true == false 甚至 (1 < 2) == (3 < 4) 都是类型错误,不是你以为的 true——比较产生布尔,但求值器没有任何分支接受布尔当操作数34。
  • 字符串不对称:"foo" + "bar" 成功得 foobar,"foo" == "foo" 反而 TypeMismatch(没有字符串比较分支)35。
  • 10 / 0 不报错,直接 panic——截断除法不查零除数。记住任意「运算符 × 操作数」组合只有三种结局:算出值、TypeMismatch、除零硬崩36。

6. 主线例子:三角形程序走全程

书上用一段脚本串起三阶段,这里把它走到底(中间产物均可对账)37:

源码: let base = 10; let height = 5;
      let area = (base * height) / 2;
      if (area > 20) { return "Large"; } else { return "Small"; }

第 1 阶段 lexer:   逐字符 → .Let .Ident(base) .Assign .Int(10) .SemiColon …
                   空白全丢,关键字与标识符分家

第 2 阶段 parser:   三句 Let + 一棵 If 树;
                   括号让乘法在递归中自成一树,除法节点以它为左孩子——
                   (base*height)/2 的形状由文法烙定,不是求值器排出来的

第 3 阶段 evaluator:
                   env ← {base:10, height:5}
                   area: 查 base 得 10 ×2 前先看树——左子树 a*b? 不对,本例是
                         (10×5)=50,50/2=25 → env ← {…, area:25}
                   if: 25 > 20 为真 → 进 consequence,忽略 alternative
                   return "Large" → 装箱 ReturnWrapper → 逐层上冒 → 拆箱 → 输出 Large

图说:第二个例子 math.small(let c = a * 2 + b * 4; return c;)得 40——左子树 a*2=20,右子树 b*4=20,根 + 合成 40,返回值装箱再拆箱打印38。

7. REPL:把三段粘成一个工具

三段各自可跑之后,缺的是胶水:repl.zig 合并三阶段为单一编译单元,按启动时有无文件参数选模式——有参数批跑脚本,无参数进交互,零配置开关39。两个构件:printVal() 按值变体格式化输出,ReturnWrapper 递归解包——无论 return 套多深,打印的永远是拆箱后的内容40;readLine() 逐字节读到换行/EOF,完全空的输入返回 null 表示会话结束41。

REPL 模式的关键设计:一个环境贯穿整个会话——第一行声明的变量对之后每一行都可见,这才是交互感的来源42。文件模式解析失败打 Parsing Error 干净退出,不抛栈43。

7.1 REPL 的两种模式与退出语义

repl.zig 一个文件两种人格:带文件名参数走批处理(读文件、解析、逐句求值,遇 ReturnWrapper 打印拆箱值立即退出——与批处理求值器语义完全对齐)44;无参数进交互(打印提示符、读一行、trim 后查退出命令)45。readLine 逐字节读,完全空的输入返回 null 表示会话结束;REPL 用 arena,行与行之间不复位——变量跨行存活(环境贯穿会话)正是交互感的来源,代价是内存随会话增长46。

7.2 printVal 的递归解包

printVal() 按值变体格式化:整数、带引号字符串、布尔各一行,Void 静默;ReturnWrapper 递归解包内层值——无论 return 套了多少层,打印的永远是最终内容47。

8. 作者的判断与证据

  • 判断: 树遍历求值(tree-walking)是第一种解释器的正确选择——递归访问 AST 节点、以调用栈为隐式求值栈,最简单最易读;生产语言通常再进一步:把 AST 编译成字节码、跑在带显式值栈的虚拟机上——分离编译期与执行期、开启树遍历做不了或做不好的优化、对计算密集程序快得多48。这是路线图陈述,两种都未在本章实现字节机。
  • 判断: 分段自包含(冗余的 lexer/parser/evaluator)让每阶段可独立验证,「隔离正是它们好造、好想的原因」49。证据:三个阶段各有可跑的诊断输出。
  • 诚实清单: 布尔操作数、字符串比较、除零三个坑全部明码标在正文而非脚注343536;REPL 的 arena 不复位导致内存随会话增长,明说不适合长命解释器20。

9. 边界与局限

  • zcalc 无函数、无复合数据结构、无浮点;return 暗示了「带值退出上下文」的架构,但函数定义不在文法里50。
  • 错误处理分两期:解析期(语法错)与运行期(未定义变量/类型不匹配),没有静态类型检查——10 + "x" 要到执行才炸。
  • 整数字面只有连续数字,无负数字面( - 是一元还是二元都没处理,文法里没有一元运算)。
  • 字符串转义只此一家 \",\n 在字符串里是两个字面字节 \ 和 n——扩展留给读者,readString() 是唯一要改的地方51。
  • 解释器无性能数字(连基准都没有);树遍历对计算密集程序慢是路线图里的断言,本章未量化。

10. 可带走的

  1. 先写文法再写代码:EBNF 里每条规则 = 解析器里一个函数,读文法就是读执行流。
  2. ? 可选、* 重复、| 候选、引号终结符——四符号读懂任何 EBNF。
  3. 语句(不产值、控流)与表达式(归约成值)分家,环境才能抓未定义变量。
  4. 词法器双指针:消费当前、偷看下一;ch=0 哨兵免 EOF 特判。
  5. token 的 literal 是指进原缓冲的窗口——词法器零分配。
  6. 递归类型进联合必须堆指针:create 两次斩断尺寸依赖。
  7. 优先级不用算:分层下降函数链把优先级烙进树的深度;while 累积左操作数 = 左结合。
  8. 表达式语句 vs 赋值:一次 look-ahead 是消歧所需的最小上下文。
  9. return 用包装类型当控制流信号,遇箱即短路——块级退出就这么简单。
  10. 每阶段独立可跑是解释器开发的正确节奏:lexer 看 token、parser 看树形、evaluator 看结果。
  11. REPL 的「活」来自跨行持久的环境。

11. 原文地图

主题原书章原文位置
收官定位Chapter 12: Developing a Domain-Specific Languagetext/138-ch12-chapter-12-developing-a-domain-specific-language.txt:35(搜「not a departure」)
EBNF 记法The EBNF grammar of zcalctext/139-fm-the-ebnf-grammar-of-zcalc.txt:62(搜「straightforward」)
语句表达式分家The EBNF grammar of zcalctext/139-fm-the-ebnf-grammar-of-zcalc.txt:238(搜「separation」)
悬垂 else 的消灭The EBNF grammar of zcalctext/139-fm-the-ebnf-grammar-of-zcalc.txt:138(搜「dangling-else」)
分层编码优先级The EBNF grammar of zcalctext/139-fm-the-ebnf-grammar-of-zcalc.txt:260(搜「stratified」)
三文件冗余设计The EBNF grammar of zcalctext/139-fm-the-ebnf-grammar-of-zcalc.txt:427(搜「isolation」)
词法器的角色Lexing: breaking strings into tokenstext/140-fm-lexing-breaking-strings-into-tokens.txt:25(搜「specialized filter」)
双指针心脏Lexing: breaking strings into tokenstext/140-fm-lexing-breaking-strings-into-tokens.txt:82(搜「two-pointer」)
字符串转义Lexing: breaking strings into tokenstext/140-fm-lexing-breaking-strings-into-tokens.txt:250(搜「protects」)
token 流诊断Lexing: breaking strings into tokenstext/140-fm-lexing-breaking-strings-into-tokens.txt:452(搜「diagnostic」)
平序列无层级Lexing: breaking strings into tokenstext/140-fm-lexing-breaking-strings-into-tokens.txt:504(搜「no hierarchy」)
递归下降=文法翻译Parsing: abstract syntax trees (ASTs) and recursiontext/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:23(搜「function calls」)
递归类型堆指针Parsing: abstract syntax trees (ASTs) and recursiontext/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:96(搜「cannot directly embed」)
arena 与无 deinitParsing: abstract syntax trees (ASTs) and recursiontext/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:146(搜「single allocator」)
look-ahead 消歧Parsing: abstract syntax trees (ASTs) and recursiontext/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:280(搜「minimum context」)
左结合的 whileParsing: abstract syntax trees (ASTs) and recursiontext/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:416(搜「left-associativity」)
树形即运算顺序Parsing: abstract syntax trees (ASTs) and recursiontext/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:530(搜「baked」)
ReturnWrapper 控制流Evaluation: tree-walking executiontext/142-fm-evaluation-tree-walking-execution.txt:25(搜「control-flow」)
evalBlock 短路Evaluation: tree-walking executiontext/142-fm-evaluation-tree-walking-execution.txt:152(搜「short-circuits」)
真值规则Evaluation: tree-walking executiontext/142-fm-evaluation-tree-walking-execution.txt:175(搜「truth」)
布尔操作数之坑Evaluation: tree-walking executiontext/142-fm-evaluation-tree-walking-execution.txt:329(搜「surprising gap」)
除零 panicEvaluation: tree-walking executiontext/142-fm-evaluation-tree-walking-execution.txt:385(搜「panics」)
树遍历 vs 字节码Evaluation: tree-walking executiontext/142-fm-evaluation-tree-walking-execution.txt:399(搜「tree-walking」)
math.small 得 40Evaluation: tree-walking executiontext/142-fm-evaluation-tree-walking-execution.txt:425(搜「40」)
REPL 持久环境Evaluation: tree-walking executiontext/142-fm-evaluation-tree-walking-execution.txt:660(搜「persisting」)
第 13 章=营销页Chapter 13: Unlock Your Exclusive Benefitstext/143-ch13-chapter-13-unlock-your-exclusive-benefits.txt:5(搜「exclusive benefits」)

本篇拆解依据原书第 12 章(含各小节);第 13 章为营销页,仅登记。每条出处可用 node scripts/book-verify.mjs systems-programming-with-zig 回核。

Footnotes

  1. 出处:「Chapter 12: Developing a Domain-Specific Language」第 35 段(text/138-ch12-chapter-12-developing-a-domain-specific-language.txt:35,搜「not a departure」)。 ↩

  2. 出处:「Chapter 13: Unlock Your Exclusive Benefits」第 5 段(text/143-ch13-chapter-13-unlock-your-exclusive-benefits.txt:5,搜「exclusive benefits」)。该章为 Packt 购书福利解锁指引(凭发票访问 packtpub.com/unlock),无技术内容,如实登记、不作拆解。 ↩

  3. 出处:「The EBNF grammar of zcalc」第 62 段(text/139-fm-the-ebnf-grammar-of-zcalc.txt:62,搜「straightforward」)。 ↩

  4. 出处:「The EBNF grammar of zcalc」第 238 段(text/139-fm-the-ebnf-grammar-of-zcalc.txt:238,搜「separation」)。 ↩

  5. 出处:「The EBNF grammar of zcalc」第 126 段(text/139-fm-the-ebnf-grammar-of-zcalc.txt:126,搜「crucial distinction」)。 ↩

  6. 出处:「The EBNF grammar of zcalc」第 138 段(text/139-fm-the-ebnf-grammar-of-zcalc.txt:138,搜「dangling-else」)。 ↩

  7. 出处:「The EBNF grammar of zcalc」第 260 段(text/139-fm-the-ebnf-grammar-of-zcalc.txt:260,搜「stratified」)与第 264 段(text/139-fm-the-ebnf-grammar-of-zcalc.txt:264,搜「physical shape」)。 ↩

  8. 出处:「The EBNF grammar of zcalc」第 210 段(text/139-fm-the-ebnf-grammar-of-zcalc.txt:210,搜「manually override」)。 ↩

  9. 出处:「Lexing: breaking strings into tokens」第 9 段(text/140-fm-lexing-breaking-strings-into-tokens.txt:9,搜「lexing」)。 ↩

  10. 出处:「Lexing: breaking strings into tokens」第 82 段(text/140-fm-lexing-breaking-strings-into-tokens.txt:82,搜「two-pointer」)。 ↩

  11. 出处:「Lexing: breaking strings into tokens」第 131 段(text/140-fm-lexing-breaking-strings-into-tokens.txt:131,搜「sentinel」)。 ↩

  12. 出处:「Lexing: breaking strings into tokens」第 189 段(text/140-fm-lexing-breaking-strings-into-tokens.txt:189,搜「window」)与第 422 段(text/140-fm-lexing-breaking-strings-into-tokens.txt:422,搜「does not belong」)。 ↩

  13. 出处:「Lexing: breaking strings into tokens」第 294 段(text/140-fm-lexing-breaking-strings-into-tokens.txt:294,搜「pure function」)。 ↩

  14. 出处:「Lexing: breaking strings into tokens」第 250 段(text/140-fm-lexing-breaking-strings-into-tokens.txt:250,搜「protects」)与第 288 段(text/140-fm-lexing-breaking-strings-into-tokens.txt:288,搜「UnterminatedString」)。 ↩

  15. 出处:「Lexing: breaking strings into tokens」第 452 段(text/140-fm-lexing-breaking-strings-into-tokens.txt:452,搜「diagnostic」)。 ↩

  16. 出处:「Lexing: breaking strings into tokens」第 504 段(text/140-fm-lexing-breaking-strings-into-tokens.txt:504,搜「no hierarchy」)。 ↩

  17. 出处:「Parsing: abstract syntax trees (ASTs) and recursion」第 23 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:23,搜「function calls」)。 ↩

  18. 出处:「Parsing: abstract syntax trees (ASTs) and recursion」第 47 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:47,搜「five variants」)与第 73 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:73,搜「Binary node」)。 ↩

  19. 出处:「Parsing: abstract syntax trees (ASTs) and recursion」第 96 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:96,搜「cannot directly embed」)。 ↩

  20. 出处:「Parsing: abstract syntax trees (ASTs) and recursion」第 146 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:146,搜「single allocator」)与第 160 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:160,搜「grows」)。 ↩ ↩2

  21. 出处:「Parsing: abstract syntax trees (ASTs) and recursion」第 184 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:184,搜「look-ahead」)。 ↩

  22. 出处:「Parsing: abstract syntax trees (ASTs) and recursion」第 280 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:280,搜「minimum context」)。 ↩

  23. 出处:「Parsing: abstract syntax trees (ASTs) and recursion」第 299 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:299,搜「advances three times」)与第 342 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:342,搜「forgiving」)。 ↩

  24. 出处:「Parsing: abstract syntax trees (ASTs) and recursion」第 390 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:390,搜「termination condition」)。 ↩

  25. 出处:「Parsing: abstract syntax trees (ASTs) and recursion」第 416 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:416,搜「left-associativity」)与第 404 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:404,搜「precedence level」)。 ↩

  26. 出处:「Parsing: abstract syntax trees (ASTs) and recursion」第 530 段(text/141-fm-parsing-abstract-syntax-trees-asts-and-recursion.txt:530,搜「baked」)。 ↩

  27. 出处:「Evaluation: tree-walking execution」第 25 段(text/142-fm-evaluation-tree-walking-execution.txt:25,搜「control-flow」)。 ↩

  28. 出处:「Evaluation: tree-walking execution」第 63 段(text/142-fm-evaluation-tree-walking-execution.txt:63,搜「same arena」)。 ↩

  29. 出处:「Evaluation: tree-walking execution」第 115 段(text/142-fm-evaluation-tree-walking-execution.txt:115,搜「isTruthy」)。 ↩

  30. 出处:「Evaluation: tree-walking execution」第 152 段(text/142-fm-evaluation-tree-walking-execution.txt:152,搜「short-circuits」)与第 167 段(text/142-fm-evaluation-tree-walking-execution.txt:167,搜「entire enclosing block」)。 ↩

  31. 出处:「Evaluation: tree-walking execution」第 193 段(text/142-fm-evaluation-tree-walking-execution.txt:193,搜「truthy by default」)。 ↩

  32. 出处:「Evaluation: tree-walking execution」第 257 段(text/142-fm-evaluation-tree-walking-execution.txt:257,搜「surfaces」)。 ↩

  33. 出处:「Evaluation: tree-walking execution」第 317 段(text/142-fm-evaluation-tree-walking-execution.txt:317,搜「far more limited」)。 ↩

  34. 出处:「Evaluation: tree-walking execution」第 329 段(text/142-fm-evaluation-tree-walking-execution.txt:329,搜「surprising gap」)。 ↩ ↩2

  35. 出处:「Evaluation: tree-walking execution」第 345 段(text/142-fm-evaluation-tree-walking-execution.txt:345,搜「asymmetry」)。 ↩ ↩2

  36. 出处:「Evaluation: tree-walking execution」第 385 段(text/142-fm-evaluation-tree-walking-execution.txt:385,搜「panics」)。 ↩ ↩2

  37. 出处:「The EBNF grammar of zcalc」第 276 段(text/139-fm-the-ebnf-grammar-of-zcalc.txt:276,搜「base」)与第 397 段(text/139-fm-the-ebnf-grammar-of-zcalc.txt:397,搜「bubble」)。 ↩

  38. 出处:「Evaluation: tree-walking execution」第 425 段(text/142-fm-evaluation-tree-walking-execution.txt:425,搜「40」)。 ↩

  39. 出处:「Evaluation: tree-walking execution」第 553 段(text/142-fm-evaluation-tree-walking-execution.txt:553,搜「without any configuration flags」)。 ↩

  40. 出处:「Evaluation: tree-walking execution」第 604 段(text/142-fm-evaluation-tree-walking-execution.txt:604,搜「recurses」)。 ↩

  41. 出处:「Evaluation: tree-walking execution」第 642 段(text/142-fm-evaluation-tree-walking-execution.txt:642,搜「null」)。 ↩

  42. 出处:「Evaluation: tree-walking execution」第 660 段(text/142-fm-evaluation-tree-walking-execution.txt:660,搜「persisting」)。 ↩

  43. 出处:「Evaluation: tree-walking execution」第 683 段(text/142-fm-evaluation-tree-walking-execution.txt:683,搜「Parsing Error」)。 ↩

  44. 出处:「Evaluation: tree-walking execution」第 672 段(text/142-fm-evaluation-tree-walking-execution.txt:672,搜「FILE MODE」)。 ↩

  45. 出处:「Evaluation: tree-walking execution」第 698 段(text/142-fm-evaluation-tree-walking-execution.txt:698,搜「REPL MODE」)。 ↩

  46. 出处:「Evaluation: tree-walking execution」第 660 段(text/142-fm-evaluation-tree-walking-execution.txt:660,搜「interactive quality」)。 ↩

  47. 出处:「Evaluation: tree-walking execution」第 604 段(text/142-fm-evaluation-tree-walking-execution.txt:604,搜「recurses」)。 ↩

  48. 出处:「Evaluation: tree-walking execution」第 399 段(text/142-fm-evaluation-tree-walking-execution.txt:399,搜「tree-walking」)与第 405 段(text/142-fm-evaluation-tree-walking-execution.txt:405,搜「bytecode」)。 ↩

  49. 出处:「Evaluation: tree-walking execution」第 531 段(text/142-fm-evaluation-tree-walking-execution.txt:531,搜「isolation」)。 ↩

  50. 出处:「The EBNF grammar of zcalc」第 226 段(text/139-fm-the-ebnf-grammar-of-zcalc.txt:226,搜「lacks」)。 ↩

  51. 出处:「The EBNF grammar of zcalc」第 176 段(text/139-fm-the-ebnf-grammar-of-zcalc.txt:176,搜「literal bytes」)。 ↩

On this page