
作者 | Peter Norvig 译者 | Tianyu 编辑 | Freesia
出品 | Python大本营(ID: pythonnews)
本文有两个目的:一是展示如何实现一个计算机语言的解释器,二是演示如何使用 Python 3 构造 Lisp 的一种方言 Schema,我把我的语言解释器称作 Lispy。几年前,作者曾展示过如何用 Java 和 Common Lisp 写 Schema 解释器。而本次的目的很纯粹,作者会尽可能简明扼要,就像 Alan Kay 所谓的 “软件中的麦克斯韦方程组”。
Schema 程序的语法和语义
语言的语法是指组成正确的语句或表达式的顺序;语义指那些表达式或语句的内在含义。例如,在数学表达式语言中(以及诸多编程语言中),一加二的语法是 “1 + 2”,而语义是指对两个数字执行相加操作,得到的结果为 3 。当我们计算一个数值时,也可以说我们在评估一种表达形式;我们可以说 “1+2” 估值为 3,并写成 “1 + 2” ⇒ 3. Schema 的语法不同于其他大多数编程语言。考虑如下情况:

Java 的语法规范十分繁杂(关键词、中缀运算符、三种括号、运算符优先级、点语法、引号、逗号、分号),但 Schema 的语法要简单得多:
- Schema 程序仅由表达式组成。没有表达式和语句之分。
- 数字(比如:1)和符号(比如:A)都可以称为原子表达式;它们无法再细分了。这和 Java 中的 counterpart 类似,但 Schema 不同,一些运算符号,如 + 和 > 也是标识符,和 A 及 fn 的地位是平等的。
- 还有列表表达式:一个 "(" ,后面接零或多个表达式,后面再接一个 ")"。列表的第一个元素决定了其含义是什么:
- 以关键词作为开头的列表,如 (if ...),是一种特殊形式,含义取决于关键词是什么。
- 以非关键词开头的列表,如 (fn ...),是函数的调用。
Schema 的妙处在于整个语言体系仅需 5 个关键词和 8 种语法形式。对比之下,Python 有 33 个关键词和 110 种语法形式,Java 有 50 个关键词和 133 种语法形式。那些括号也许看起来有些吓人,但 Schema 的语法具备着简单性与一致性。(有人开玩笑说 Lisp 就是“大量把人搞疯的括号”;而我认为 Lisp 象征着语法的纯粹性。) 在本文中,我们会介绍 Schema 语言及其解释器的所有特点,但中间要经过两个步骤,先定义一个简单的语言,再定义 Schema 语言的全部内容。
语言1:Lispy Calculator
Lispy Calculator 是 Schema 的一部分,仅使用了五种语法方式。因其基于 Lispy Calculator,只要你熟练使用前缀表示法,就可以做任何典型计算机可以做的运算。你可以做两件典型计算机语言所不能做的两件事:"if" 表达式和定义新变量。下面是一个示例程序,基于公式 π r2,计算半径为10的圆形面积:
(define r 10)
(* pi (* r r))
下面是一张有关全部表达式的表格:
Expression(表达式) | Syntax(语法) | Semantics and Example(语义和例子) |
variable reference | symbol | 一个标识符被解释为变量名;它的值是变量的值。例子:r ⇒ 10 (假设 r 已被定义为10) |
constant literal | number | 计算结果为数字本身。例子:12 ⇒ 12 or -3.45e+6 ⇒ -3.45e+6 |
conditional | (if test conseq alt) | 执行 test;如果结果为 true,计算返回 conseq;否则返回 alt。例子:(if (> 10 20) (+ 1 1) (+ 3 3)) ⇒ 6 |
definition | (define symbol exp) | 定义一个新变量,并计算表达式 exp 赋值给它。例子:(define r 10) |
procedure call | (proc arg...) | 如果表达式不是这些标识符 if, define 或 quote,那它就是一个过程。执行表达式及全部参数,那么该过程就会被调用,而参数值列表也被调用。例子:(sqrt (* 2 8)) ⇒ 4.0 |
在该表的语法一栏,标识符必须为符号,数字必须为整数或小数,而其它斜体 字可以为任何表达式,arg... 则表示零或多个 arg 的重复。
语言解释器到底是做什么的?
语言解释器包括两个部分:
- Parsing:parsing 组件获得字符串形式的输入,并根据语言的语法规则进行验证,然后将程序翻译成内部的表示形式。在一个简单的解释器中,内部的表示形式是一个树形结构(一般被称为抽象语法树),反应了程序语句和表达式的嵌套结构。在被称为编译器的语言翻译器中,常常有一系列内部的表示形式,以抽象语法树开头,然后紧接着一系列指令,可以直接被计算机执行。
- Execution:内部的表示形式是根据语言的语义规则进行处理的,因此才能执行计算。Lispy 的 execution 函数叫作 eval(注意