如何编写一个(Lisp)解释器(在Python中)
这有什么重要性?正如Steve Yegge所说,"如果你不知道编译器是如何工作的,那么你就不知道计算机是如何工作的。" Yegge描述了可以通过编译器解决的8个问题(同样也可以通过解释器解决,或者用Yegge典型的讽刺来解决)。Scheme语法与大多数其他编程语言不同。请考虑:在这一页中,我们将覆盖Scheme语言及其解释的所有重要点(省略一些次要细节),但我们将采取两个步骤来达到目的,首先定义一个简化的语言,然后再定义接近完整的Scheme语言。在这个表格的语法列中,symbol必须是一个符号,number必须是一个整数或浮点数,其他斜体字可以是任何表达式。记号arg…表示arg的零次或多次重复。接下来是我们希望parse和eval能够执行的短示例(begin按顺序评估每个表达式并返回最终结果):我们的函数parse将接收一个程序的字符串表示作为输入,调用tokenize以获取一个tokens列表,然后调用read_from_tokens以组装一个抽象语法树。read_from_tokens查看第一个token;如果它是一个')',则表示语法错误。如果它是一个'(',那么我们开始构建一个包含子表达式的列表,直到找到匹配的')'。任何非括号token必须是一个符号或数字。我们将让Python在它们之间进行区分:对于每个非括号token,首先尝试将其解释为int,然后尝试为float,如果两者都不是,则它必须是一个符号。这里是解析器:我们现在准备实现eval。作为复习,我们重申Lispy计算器形式的表格:lambda特殊形式(一个模糊的命名选择,指的是Alonzo Church的lambda演算)创建一个过程。我们希望过程这样工作:当我们在这样的嵌套环境中查找变量时,我们首先查看最内层级别,如果在那里没有找到变量名称,我们就移动到下一个外部级别。过程和环境是交织在一起的,因此让我们一起定义它们:环境是dict的一个子类,因此它具有dict的所有方法。此外,还有两个方法:构造函数__init__通过采用参数名称的列表和相应的参数值列表来构建一个新环境,并创建一个新的环境,其中包含作为内部部分的{variable: value}对,并且还引用给定的外部环境。方法find用于查找变量的正确环境:内部环境或外部环境。为了看看这些是如何结合在一起的,以下是eval的新定义。注意,变量引用的条款已更改:我们现在必须调用env.find(x)来寻找变量x存在的级别;然后我们可以从该级别获取x的值。(定义的条款没有改变,因为定义始终在最内层环境中添加一个新变量。)有两个新条款:对于set!,我们找到变量存在的环境级别并将其设置为新值。使用lambda,我们使用给定的参数列表、主体和环境创建一个新的过程对象。为了理解过程和环境如何协同工作,请考虑此程序以及在评估(account1 -20.00)时形成的环境:每个矩形框表示一个环境,其颜色与在该环境中新定义的变量的颜色匹配。在程序的最后两行我们定义account1并调用(account1 -20.00);这表示创建一个100美元初始余额的银行账户,然后进行20美元的取款。在评估(account1 -20.00)的过程中,我们将评估高亮的黄色表达式。该表达式中有三个变量。amt可以立即在最内层(绿色)环境中找到。但balance并未在那里定义:我们必须查看绿色环境的外部env,即蓝色环境。最后,变量+在这两个环境中都找不到;我们需要再向外一步,进入全局(红色)环境。这一过程先在内部环境中查找,然后在外部环境中查找被称为词法作用域。Env.find(var)根据词法作用域规则找到正确的环境。小:Lispy非常小:117行非注释非空行;4K的源代码。(一个早期版本只有90行,但有较少的标准程序,或许有点过于简洁。)我在Java中的Scheme最小版本Jscheme共有1664行和57K的源代码。Jscheme最初被称为SILK(在五十千字节内的Scheme),但我只是通过计算字节码而非源代码才能保持在该限制之内。Lispy的表现更好;我认为它达到了Alan Kay在1972年声称可以在“一页代码中定义'世界上最强大的语言'。”(但是,我认为Alan会不同意,因为他会将Python编译器视为代码的一部分。)
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡