上一集我们已经见到了一个Lisp程序的大致外貌,在文末,我提到这一集中我们将会用Lisp写一个Lisp解释器,事实上这个解释器并不太长,虽然它有可能是你至今为止见过的最长的一个。* v% u: E+ q& t' J9 N7 D
2 e6 H0 c8 ]8 K3 |2 J我已经有点等不及了,让我们先来看一下整个程序,然后再来讲解:8 c8 y5 ?; U' b" ?& D
i5 c. T8 g# @ u* l
(defun eval (e a)
/ `" q; R6 V ^8 i9 C0 R! t% y (cond & V, k9 G+ U- O: O5 t
((atom e) (assoc e a))
# k" Z/ n" ]) y- g0 V8 Z3 L ((atom (car e))
{7 V: g* A- {1 _: x5 X (cond : P' r. N, F+ Y7 b( K! ^- e( I
((eq (car e) 'quote) (cadr e))
* P- T, i1 a0 { ((eq (car e) 'atom) (atom (eval (cadr e) a)))/ F5 X/ G% J4 I1 t$ o) r
((eq (car e) 'eq) (eq (eval (cadr e) a)
. {0 R; L1 B# x+ c (eval (caddr e) a)))
9 v+ T+ G+ ]/ ?6 m7 p ((eq (car e) 'car) (car (eval (cadr e) a)))
9 C6 N c5 c+ b; G* ` ((eq (car e) 'cdr) (cdr (eval (cadr e) a)))/ z" h7 X% P' h
((eq (car e) 'cons) (cons (eval (cadr e) a)1 \' b; h9 T$ N1 ], B
(eval (caddr e) a)))
: s0 ^- X. _! @& M0 r |2 R% v4 ` ((eq (car e) 'cond) (evcon (cdr e) a))
$ P; r; t% [. i( B" ? ('t (eval (cons (assoc (car e) a)
) E8 `1 U4 s! a1 h (cdr e))7 h. P8 \" L5 n# Y0 T* T
a))))
c% _! v& b0 A) a' ~5 G ((eq (caar e) 'label)0 ]( l* C7 M+ _1 E% P( s
(eval (cons (caddar e) (cdr e))+ j( N+ e7 ]: z& F/ P
(cons (list (cadar e) (car e)) a)))
" S3 I( o |8 X& H) @ ((eq (caar e) 'lambda)
9 ~+ I" m, ^5 @2 A. J) X (eval (caddar e)
/ t$ k* v) a5 k) E% p (append (pair (cadar e) (evlis (cdr e) a))
: h0 s7 {7 F* y2 R, G6 x# Q5 o a)))))
* r0 P3 l; O8 H |4 o1 y* y3 I$ s" A, Q! }
(defun evcon (c a)
1 L M, z. S1 V$ {0 r% g (cond ((eval (caar c) a)! P5 Z Q1 n' R3 Q
(eval (cadar c) a))
. C) t7 D: Z6 N ('t (evcon (cdr c) a))))$ o! T6 H3 g4 c, S& ]
- w& O ^! `* G8 q: V# F
(defun evlis (m a)6 m A7 g/ E8 k! E8 ]
(cond ((null m) '())
x. i1 M( [9 o, j# C9 M' r2 T/ `, C ('t (cons (eval (car m) a)
% U6 e5 U. e8 ] (evlis (cdr m) a)))))
* @# K( H' k, U" }
; q+ f) U/ p- H/ F(注:可能有的读者已经发现,Lisp并不要求你一定要在使用函数前先定义它)' p9 A" n) r- x
3 k# }4 `. ^1 s( r4 B整个程序包含三个函数,主函数我们遵从Lisp(和Python、Perl)的惯例,叫它eval,它是整个程序的骨架。eval的定义比我们以前看到的任何一个函数都要长,让我们考虑它的每一部分是如何工作的。3 O2 G3 h/ D. V( c, ?7 l
' G9 ]5 f/ e! B9 ? ceval有两个自变量:e是要求值的表达式,a是由一些赋给原子的值构成的表,这些值有点象函数调用中的参数。这个形如pair返回值的表叫做上下文,正是为了构造和搜索这种表我们才在前一章写了pair和assoc。
( D, G3 J) _2 Z+ v8 \( c
/ e6 S* c- j5 p- f8 x* beval的骨架是一个有四个子句的cond表达式,如何对表达式求值取决于它的类型,第一个分支处理原子,如果e是原子, 我们在上下文中寻找它的值:
. n0 |9 a9 \' r# t7 H' J" f# \( p1 [* S4 I: z
> (eval 'x '((x a) (y b))) a
$ ^( \3 o1 @% \ P G, \% c第二个分支是另一个cond,它处理形如(a)的表达式,其中a是原子。这包括所有的基本操作符,每个对应一条分支。; H( v" i9 ^8 q% w+ z3 X' i
# C6 z: c6 K$ E4 J6 k
> (eval '(eq 'a 'a) '()) t > (eval '(cons x '(b c)) '((x a) (y b))) (a b c)
5 b4 l* ?% l: ~( Z1 l, ^4 T8 L: R这几个分支(除了quote)都调用eval来寻找自变量的值。
6 L U9 N* M0 z p2 \& Y$ N- C8 z2 [( w7 h4 o+ j _9 w& m j. q
最后两个分支更复杂些。为了求cond表达式的值我们调用了一个叫evcon的辅助函数。它递归地对cond分支进行求值,寻找第一个元素返回t的子句,如果找到了这样的子句,它返回此分支的第二个元素。% g }, x4 u" a* x9 N* Y& A
" G- D, ?, }/ g6 L4 A8 C# Q. C# F4 K> (eval '(cond ((atom x) 'atom) ('t 'list)) '((x '(a b)))) list 2 F( |( u9 |/ M n6 c1 m9 Q
第二个分支的最后部分处理函数调用。它把原子替换为它的值(应该是lambda或label表达式)。然后对所得结果表达式求值。于是:
) F+ b. w. w, B* s! A, I x8 y4 q6 p6 S4 C/ R9 j
(eval '(f '(b c)) '((f (lambda (x) (cons 'a x)))))3 B$ r; A; Y; S/ @2 W
变为:) _. \" M* X2 r/ L/ r. c
3 j+ K( a. E# l# S0 j
(eval '((lambda (x) (cons 'a x)) '(b c)) '((f (lambda (x) (cons 'a x)))))
$ b4 \: a* A$ A% W2 B; W6 m5 q( A它返回(a b c) 3 a( D; C- c, W
- ^$ U9 [9 f( G: ^! U$ H" ^- Q* L
eval的最后两个cond分支处理第一个元素是lambda或label的函数调。用为了对label表达式求值,先把函数名和函数本身压入上下文,然后调用eval对一个内部有lambda的表达式求值,即:
7 ]6 E8 K C( W4 U
. K0 o f7 L- [+ w. @(eval '((label firstatom (lambda (x) (cond ((atom x) x) ('t (firstatom (car x)))))) y) '((y ((a b) (c d)))))2 U/ {4 Y8 `7 E1 b. c0 ^, ^: e
变为
) O9 k1 D U+ w& v' j+ m4 n( ~$ ]3 W! t5 y _' k4 [' }
(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)))))
1 `/ h7 x1 ?& y E9 F) u+ s- i最终返回a。
* V9 K* P2 {: V( v. H% u% q
# w1 S. L8 D/ x最后,对形如((lambda (p1 p2 ... pn) e) a1 a2 ... an)的表达式求值,先调用evlis来求得自变量(a1 a2 ... an)对应的值(v1 v2 ... vn),把(p1 v1) (p2 v2) ... (pn vn)添加到上下文里,然后对e求值。于是:4 i$ a3 h" V: O3 o- D3 Y, o- T# G
- I8 ?$ z* ^4 q+ X/ i
(eval '((lambda (x y) (cons x (cdr y))) 'a '(b c d)) '()): t# e: J- V$ j- A* j# j6 y! M
变为:
8 Q3 Y% A! h+ p$ r4 J2 w6 @" I, f# a8 _6 z7 L! H& ^0 A9 U
(eval '(cons x (cdr y)) '((x a) (y (b c d))))
) C- `( j5 o! U) m7 [. A c5 ]最终返回(a c d)。% z2 G% j$ [/ k {% q+ v& f
% H; z4 |8 B: o/ d讲了这么一大篇,如果你看懂了,说明你已经理解Lisp甚至FP的基本编程方式和思路,那么我们写了一个如此之长的程序究竟能干什么呢?
: k% _- F9 M* p. }; y/ r D" P2 b$ G9 O8 [+ Z
我们在这里得到了一个非常优美的计算模型,eval函数实际上实现了整个语言,用它我们可以定义所需的任何其它函数。换句话说,我们现在有了一个自己的Lisp。/ ]( y3 k+ c3 {1 G9 b
/ y3 E) \/ b9 a(注:由此可见,递归下降的语法分析是多么美好啊,因为它意味着你可以用几十、最多不过一两百行程序搞定一个复杂的分析器,对比LALR你将更有体会)
8 ?& e$ j Z1 s* M7 {7 d+ S
( j, Q) y) x* K0 J下面我们该去哪儿?这个问题,请读者自己去寻找答案。 |