上一集我们已经见到了一个Lisp程序的大致外貌,在文末,我提到这一集中我们将会用Lisp写一个Lisp解释器,事实上这个解释器并不太长,虽然它有可能是你至今为止见过的最长的一个。. e6 m/ t5 [: f
2 X# j1 o9 f4 J. V3 T; [我已经有点等不及了,让我们先来看一下整个程序,然后再来讲解:
; f: L9 i; s+ _3 W# m
- F. I3 O5 Y, Z) T7 L; y(defun eval (e a)) U- Z, \# a) e. k" {8 ?' a6 X! p; r
(cond
- Y! `( p1 |- m7 M0 n ((atom e) (assoc e a))
4 i, t* f, f4 b) ` ((atom (car e))8 y4 ?, ?+ X2 c: j2 R+ J
(cond
& p; }" X, l0 Q. \9 O ((eq (car e) 'quote) (cadr e))
: r7 w: Y/ O0 B, e ((eq (car e) 'atom) (atom (eval (cadr e) a)))( D% q# A" h* p5 m8 n, B3 c; Q
((eq (car e) 'eq) (eq (eval (cadr e) a): d& O) j7 Q3 J. z# S
(eval (caddr e) a)))
. F1 g! |8 j; C8 |2 j+ }# _8 z ((eq (car e) 'car) (car (eval (cadr e) a)))
; G8 Z6 D7 G% V9 A/ A& ^5 H ((eq (car e) 'cdr) (cdr (eval (cadr e) a)))
2 g- L0 R8 F. Y, i ((eq (car e) 'cons) (cons (eval (cadr e) a)1 R5 d( _3 d$ e: Z' L: b) J
(eval (caddr e) a)))- b+ t9 `* U0 L: |: G% a' M
((eq (car e) 'cond) (evcon (cdr e) a))0 B; w- p8 P2 I
('t (eval (cons (assoc (car e) a)
0 \' b* @1 X) D) X8 L (cdr e))) u8 C( i6 o7 n) l7 _7 ? C o' z
a))))* T! P% G7 T: o2 w3 C3 z
((eq (caar e) 'label)( z( P+ X y# t! _ T' D2 } J' w
(eval (cons (caddar e) (cdr e))9 c- s$ {! U! R% i. K
(cons (list (cadar e) (car e)) a)))3 d3 E) D& K9 Z9 S8 @: P
((eq (caar e) 'lambda), s9 Z$ q* Z/ u9 g# y- W
(eval (caddar e)( T* K% e7 }2 l$ R& \
(append (pair (cadar e) (evlis (cdr e) a))
: t. x* y+ j9 b& R O6 U8 L a))))); u: U, ^+ d: C& F, H6 S
1 ~% t: {* w3 C+ z
(defun evcon (c a)
' {% S4 y, k: S# s: w5 M, V (cond ((eval (caar c) a)
3 o* N* c/ v; z7 T2 s+ u( @' ?/ j4 P (eval (cadar c) a))
& X% I2 q# a, W+ @1 M0 J, y ('t (evcon (cdr c) a))))$ \/ Y# P0 ?7 |
6 `, }- M, c- e(defun evlis (m a)
* N |5 y+ |1 r4 O* a* S9 V (cond ((null m) '()), e1 }6 x2 O8 j) t6 b2 y! ~6 p
('t (cons (eval (car m) a): Q8 j" J, x2 e" ~4 [; H5 ?
(evlis (cdr m) a)))))! \# \3 B, k* S( I
1 f0 l" ]0 F- f
(注:可能有的读者已经发现,Lisp并不要求你一定要在使用函数前先定义它)7 ], i2 l2 j4 s: `: E
4 p# B% W* R7 w$ f0 Z
整个程序包含三个函数,主函数我们遵从Lisp(和Python、Perl)的惯例,叫它eval,它是整个程序的骨架。eval的定义比我们以前看到的任何一个函数都要长,让我们考虑它的每一部分是如何工作的。
! `0 |" |7 c0 w% D2 w. ~! R) c" |4 R, f$ j
eval有两个自变量:e是要求值的表达式,a是由一些赋给原子的值构成的表,这些值有点象函数调用中的参数。这个形如pair返回值的表叫做上下文,正是为了构造和搜索这种表我们才在前一章写了pair和assoc。
& n0 ]( H% v2 R$ o6 ]- l/ R/ x- `) U, L% O2 {7 H
eval的骨架是一个有四个子句的cond表达式,如何对表达式求值取决于它的类型,第一个分支处理原子,如果e是原子, 我们在上下文中寻找它的值:5 L$ ~) d& e! v! l( O H1 ^
! b+ a4 |- p/ L6 N& U
> (eval 'x '((x a) (y b))) a, d+ ^& `4 L# a
第二个分支是另一个cond,它处理形如(a)的表达式,其中a是原子。这包括所有的基本操作符,每个对应一条分支。
W* b" B$ X6 g" y4 i1 B& _0 i. j* K! D2 E0 J+ w8 V
> (eval '(eq 'a 'a) '()) t > (eval '(cons x '(b c)) '((x a) (y b))) (a b c)8 Y6 h1 d1 k+ a9 a" o- b( B
这几个分支(除了quote)都调用eval来寻找自变量的值。: m( V5 I3 f) |; [; `
9 i* l b6 d. n# ?7 \0 x最后两个分支更复杂些。为了求cond表达式的值我们调用了一个叫evcon的辅助函数。它递归地对cond分支进行求值,寻找第一个元素返回t的子句,如果找到了这样的子句,它返回此分支的第二个元素。
$ ~' O- y5 H. h0 I5 Y7 [9 c' {
& t, l0 F7 c) s* ~5 g" G' z3 q: ^4 Y> (eval '(cond ((atom x) 'atom) ('t 'list)) '((x '(a b)))) list 5 P+ w) ^3 Y8 @* {4 E
第二个分支的最后部分处理函数调用。它把原子替换为它的值(应该是lambda或label表达式)。然后对所得结果表达式求值。于是:
K) B+ O+ h( `2 g8 d4 C! ^+ M
4 B8 |+ S5 u% u2 G5 ]/ Q(eval '(f '(b c)) '((f (lambda (x) (cons 'a x)))))" c, _2 x. O( c$ n
变为:% P3 K! c2 U0 e2 O( D8 e
% a" ^& A6 V" [& Z: ^3 J$ h, O
(eval '((lambda (x) (cons 'a x)) '(b c)) '((f (lambda (x) (cons 'a x)))))
2 V2 @+ x& Q$ F7 D' X% F它返回(a b c)
( f" U. U) Y' n! @! u3 L! {
9 n5 s8 `1 k$ K/ w" u8 J4 E8 K) Xeval的最后两个cond分支处理第一个元素是lambda或label的函数调。用为了对label表达式求值,先把函数名和函数本身压入上下文,然后调用eval对一个内部有lambda的表达式求值,即:. i$ ], T# I1 h( o
1 a, j6 s3 G5 X8 R, ?4 Q(eval '((label firstatom (lambda (x) (cond ((atom x) x) ('t (firstatom (car x)))))) y) '((y ((a b) (c d))))): o" r/ e! Q+ t8 M8 @, ^" F
变为
# T% h4 h0 w( Q" P4 g2 R) g
! `% ~+ K- d+ U. I6 J' v: y! V(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)))))& [' G$ n' G9 _* M
最终返回a。
, R; z2 \/ F# s) |+ s& Y1 S* U& ]: J8 I* w* K2 s
最后,对形如((lambda (p1 p2 ... pn) e) a1 a2 ... an)的表达式求值,先调用evlis来求得自变量(a1 a2 ... an)对应的值(v1 v2 ... vn),把(p1 v1) (p2 v2) ... (pn vn)添加到上下文里,然后对e求值。于是:
6 T$ u# W8 \/ M% c( D
" [& }7 H' R+ t(eval '((lambda (x y) (cons x (cdr y))) 'a '(b c d)) '())
; C+ w" i( f: }" P4 w w2 ] z1 g变为:
i4 }. J1 e- n% Y2 [2 c2 N. K9 n$ y+ }) P$ f M% s& b
(eval '(cons x (cdr y)) '((x a) (y (b c d))))
; \- X8 W, l, _2 W! B' H最终返回(a c d)。
8 M6 a0 z/ E& X2 o% t5 W2 }. T% I/ Q- c4 X. r, |3 c
讲了这么一大篇,如果你看懂了,说明你已经理解Lisp甚至FP的基本编程方式和思路,那么我们写了一个如此之长的程序究竟能干什么呢?
5 N7 |: j# }/ }# Q& p$ m a0 Y; C
1 E# ] _; U8 E% E0 a* u4 i我们在这里得到了一个非常优美的计算模型,eval函数实际上实现了整个语言,用它我们可以定义所需的任何其它函数。换句话说,我们现在有了一个自己的Lisp。7 u7 z# V3 ]+ v" W9 u& K
, [- G" G! j5 D3 o1 W1 P4 m7 P' {
(注:由此可见,递归下降的语法分析是多么美好啊,因为它意味着你可以用几十、最多不过一两百行程序搞定一个复杂的分析器,对比LALR你将更有体会)$ Z7 Z# L( [4 V$ M# S1 ]$ T. P
. q8 y k) ]/ @" [) T: G
下面我们该去哪儿?这个问题,请读者自己去寻找答案。 |