上一集我们已经见到了一个Lisp程序的大致外貌,在文末,我提到这一集中我们将会用Lisp写一个Lisp解释器,事实上这个解释器并不太长,虽然它有可能是你至今为止见过的最长的一个。4 L. o# b7 m" z* _# \6 n' W
% @2 j A& L& [8 P H( D我已经有点等不及了,让我们先来看一下整个程序,然后再来讲解:5 v% i9 O* G) ^& `1 W2 c# |
- G6 Q7 r$ B8 L* @: V
(defun eval (e a)
' r0 b& K( h! A- N# U& R6 v4 a (cond
6 y0 i: {1 r& x' e' V8 K% ~ ^% ?/ w ((atom e) (assoc e a))% J5 `2 y: H+ S
((atom (car e))
\+ s) E! U% b (cond 1 u5 }* |7 ^# J+ m% E) |
((eq (car e) 'quote) (cadr e))$ M! f( k, Z1 y1 S6 W- j% w
((eq (car e) 'atom) (atom (eval (cadr e) a)))/ ?2 ]5 X6 p6 |) @! A, A7 |9 `
((eq (car e) 'eq) (eq (eval (cadr e) a)
9 Y. U/ z" b: A# @) q. B$ y (eval (caddr e) a)))
l1 K" ~6 w3 C& n% _/ U" x ((eq (car e) 'car) (car (eval (cadr e) a)))' A! N0 {! N( o/ a
((eq (car e) 'cdr) (cdr (eval (cadr e) a)))
; l$ W# x8 I5 O4 Z, t A- t ((eq (car e) 'cons) (cons (eval (cadr e) a)
8 J% n- m% I6 {) v1 A9 q# x (eval (caddr e) a)))3 N) \) r) a, X ]
((eq (car e) 'cond) (evcon (cdr e) a)) G" _4 r' i; v4 ]7 j
('t (eval (cons (assoc (car e) a)
" J1 W, s' j" g# N4 J8 H (cdr e))
1 `, T) @# `8 }( Y& `0 p! H a))))* y2 C6 m: R+ `$ `# F# \
((eq (caar e) 'label)
8 e d; P8 R% k, N (eval (cons (caddar e) (cdr e))
* A$ F( X- i4 m" C) y- }+ i) @; D (cons (list (cadar e) (car e)) a)))
9 f2 S5 j! I$ W3 e) q8 X' p3 { ((eq (caar e) 'lambda)
$ s. \* ?+ @* ?' j (eval (caddar e)
# D8 W) @ Y5 ? (append (pair (cadar e) (evlis (cdr e) a))9 ]( o4 A. N, f& c0 x+ K
a)))))9 b5 m" e) |3 t6 w
& \: N( o; X7 @ i4 I1 d" {$ i(defun evcon (c a)7 ?( J0 l: n" s, R$ @& i3 e4 c
(cond ((eval (caar c) a)
+ ?) h5 D! f2 _4 j4 R (eval (cadar c) a))
0 x1 b6 c* G9 ^6 w. t ('t (evcon (cdr c) a))))* H5 a- W& O. g7 K; w \, w
5 _8 v1 p, i9 B" ?3 `8 Q1 z(defun evlis (m a)
, v6 y/ s6 |/ B7 D (cond ((null m) '())6 `; h& d2 y" h( [+ l# O
('t (cons (eval (car m) a)
8 i6 L$ A: K+ Y* v( m! { (evlis (cdr m) a)))))! U& a2 q) ~8 c1 T( J! E4 F2 ~, v
$ R; ^8 K* [4 j% |( Z# h
(注:可能有的读者已经发现,Lisp并不要求你一定要在使用函数前先定义它)8 X, W1 o! P' E
5 b5 U; Y: d, B- }! G. O5 p
整个程序包含三个函数,主函数我们遵从Lisp(和Python、Perl)的惯例,叫它eval,它是整个程序的骨架。eval的定义比我们以前看到的任何一个函数都要长,让我们考虑它的每一部分是如何工作的。
$ l( C4 P/ E0 a) D
. v; ]$ _, ^, o9 q" U7 Eeval有两个自变量:e是要求值的表达式,a是由一些赋给原子的值构成的表,这些值有点象函数调用中的参数。这个形如pair返回值的表叫做上下文,正是为了构造和搜索这种表我们才在前一章写了pair和assoc。
) Y `7 B2 ]/ L" Q0 n& p/ v D
) X; L+ u- `$ l) L- v& ^$ Q- Teval的骨架是一个有四个子句的cond表达式,如何对表达式求值取决于它的类型,第一个分支处理原子,如果e是原子, 我们在上下文中寻找它的值:# Z! l$ }" Y2 z, o6 W/ E
$ Q5 c# _6 P4 V
> (eval 'x '((x a) (y b))) a2 |6 X3 [/ U: C5 o
第二个分支是另一个cond,它处理形如(a)的表达式,其中a是原子。这包括所有的基本操作符,每个对应一条分支。& q2 v4 V3 q$ C/ `" T9 w
7 Q: T W" \5 U0 o* W& I$ e/ M S> (eval '(eq 'a 'a) '()) t > (eval '(cons x '(b c)) '((x a) (y b))) (a b c)0 |! y6 ~2 r/ t$ ?+ l# s5 ?( ]) z5 u
这几个分支(除了quote)都调用eval来寻找自变量的值。# d6 ?- |0 o, N( n6 ^
0 k+ x: f) P# K
最后两个分支更复杂些。为了求cond表达式的值我们调用了一个叫evcon的辅助函数。它递归地对cond分支进行求值,寻找第一个元素返回t的子句,如果找到了这样的子句,它返回此分支的第二个元素。' M- i$ `6 o2 P8 @# V2 [$ C
' t4 X) w( _- N
> (eval '(cond ((atom x) 'atom) ('t 'list)) '((x '(a b)))) list 5 _9 P# b+ O- m
第二个分支的最后部分处理函数调用。它把原子替换为它的值(应该是lambda或label表达式)。然后对所得结果表达式求值。于是:
! w6 U S6 |0 K. v2 C0 p
1 l5 c7 S$ j8 A k6 I% v(eval '(f '(b c)) '((f (lambda (x) (cons 'a x)))))
' K! Z# S& X* \* m$ i* T变为:8 s2 p- P: h1 a; r( M( J
/ x9 @- T3 V+ S. r+ C! Y(eval '((lambda (x) (cons 'a x)) '(b c)) '((f (lambda (x) (cons 'a x)))))
3 c5 s2 E/ t, \ n* U它返回(a b c)
; f$ C- r1 f' V* Y) A
4 h6 O( V2 K" {: A0 yeval的最后两个cond分支处理第一个元素是lambda或label的函数调。用为了对label表达式求值,先把函数名和函数本身压入上下文,然后调用eval对一个内部有lambda的表达式求值,即:" `6 C5 l6 Y# K) z$ D
( a7 N& B' v5 m: L
(eval '((label firstatom (lambda (x) (cond ((atom x) x) ('t (firstatom (car x)))))) y) '((y ((a b) (c d)))))
. d b' y$ W$ @! e1 L& j变为 ( `4 i9 `; }, [0 H" l
8 R- L6 n0 }0 w7 z3 ^
(eval '((lambda (x) (cond ((atom x) x) ('t (firstatom (car x))))) y) '((firstatom (label firstatom (lambda (x) (cond ((atom x) x) ('t (firstatom (car x))))))) (y ((a b) (c d)))))
& W& N8 r# E2 U. H" x最终返回a。
# b/ m' Q1 |1 h1 `. I3 U
1 v9 ]- e& O: S X最后,对形如((lambda (p1 p2 ... pn) e) a1 a2 ... an)的表达式求值,先调用evlis来求得自变量(a1 a2 ... an)对应的值(v1 v2 ... vn),把(p1 v1) (p2 v2) ... (pn vn)添加到上下文里,然后对e求值。于是:
, r# K/ q! i, e6 B+ ~0 [. z( b+ }% V" u8 o0 X2 \
(eval '((lambda (x y) (cons x (cdr y))) 'a '(b c d)) '())
9 A2 D% D) ]* A, P3 m2 v8 u变为:+ k. c2 \7 k4 [. e7 d
! a+ H0 M7 g, n(eval '(cons x (cdr y)) '((x a) (y (b c d))))
# ?0 h. |# P+ y) T6 _最终返回(a c d)。* Y1 Y4 E" @# q" b# H) B
. x2 p" l1 T) d* ?: @: G. t
讲了这么一大篇,如果你看懂了,说明你已经理解Lisp甚至FP的基本编程方式和思路,那么我们写了一个如此之长的程序究竟能干什么呢?( o$ I. ^( x( y. M( h( D1 Z( `( @
/ T, Y6 K1 `# d: Y) I3 ~7 U0 V
我们在这里得到了一个非常优美的计算模型,eval函数实际上实现了整个语言,用它我们可以定义所需的任何其它函数。换句话说,我们现在有了一个自己的Lisp。
4 k3 A# ~; p5 q# p8 M' R; w* b- L* ^" S7 R
(注:由此可见,递归下降的语法分析是多么美好啊,因为它意味着你可以用几十、最多不过一两百行程序搞定一个复杂的分析器,对比LALR你将更有体会)7 h7 l* d/ z7 o/ `$ f+ Z, a$ I, X
9 R B1 [2 x# ~; {6 O
下面我们该去哪儿?这个问题,请读者自己去寻找答案。 |