上一集我们已经见到了一个Lisp程序的大致外貌,在文末,我提到这一集中我们将会用Lisp写一个Lisp解释器,事实上这个解释器并不太长,虽然它有可能是你至今为止见过的最长的一个。
5 M5 m, w$ t/ P2 p u* [5 Q2 U1 M3 U* a. U9 g
我已经有点等不及了,让我们先来看一下整个程序,然后再来讲解:7 e# \! @4 z' F: r
. n7 t! C2 L5 E- h! r(defun eval (e a)6 i5 y$ a7 Q& a c& y
(cond
5 P# H5 m7 A6 p" C ((atom e) (assoc e a))
4 W( A1 b( X3 x% c1 z5 b ((atom (car e))
" W, j- {9 b7 W+ M) G# \9 ]4 M (cond
! R; O" L3 v& Z& u- Z, L% E ((eq (car e) 'quote) (cadr e))7 S5 m$ `8 m% d: A' Y
((eq (car e) 'atom) (atom (eval (cadr e) a)))
7 }. m* E! d3 B; V: C ((eq (car e) 'eq) (eq (eval (cadr e) a)
! S2 k* v. x7 o. {, n (eval (caddr e) a))). f' g( r# C/ @
((eq (car e) 'car) (car (eval (cadr e) a)))
# [7 ~- E$ \; _ N4 X+ W* X ((eq (car e) 'cdr) (cdr (eval (cadr e) a)))3 O& G6 |* ?: l2 l8 T. _0 b! G
((eq (car e) 'cons) (cons (eval (cadr e) a)! K$ A: t; ?0 V7 F5 k2 \7 b- r8 u
(eval (caddr e) a)))6 O' |8 C4 t. }! U. ]5 \) ]
((eq (car e) 'cond) (evcon (cdr e) a))& M; {, f% D: t. c2 L4 ?0 A N
('t (eval (cons (assoc (car e) a)
: l* V7 E* j* k! N (cdr e))
, k! |# a; S. F* P7 Y. u" [ a))))
# m# l# u2 c) x3 N ((eq (caar e) 'label)3 j+ g, z0 \3 _0 n& i, y
(eval (cons (caddar e) (cdr e))0 E, F% G- g5 u3 f. B/ L
(cons (list (cadar e) (car e)) a)))( X5 o) A: L0 q- j
((eq (caar e) 'lambda)
: W8 Z( E) _( ^2 E2 L' \ [ (eval (caddar e)
- ~7 I& }" p4 l1 m) [* ]. b5 C (append (pair (cadar e) (evlis (cdr e) a))8 A' o+ j9 O) l( h- |
a)))))
4 L% K2 P7 T6 r6 X0 p0 d3 }0 k9 x
, l# I8 P# C2 U0 S, p(defun evcon (c a), W" P% h0 m0 M* Z& r- j; s
(cond ((eval (caar c) a)) X ^8 `0 U8 }4 j0 B# l3 T
(eval (cadar c) a))
# U# b- y' Y7 T" O7 j6 _ ('t (evcon (cdr c) a)))). I5 G5 B, p* n+ `! Q0 P
2 Q& R0 k, V/ F( |% |5 K+ u
(defun evlis (m a)) p: v% s$ R" ?+ S% l2 z. l3 r7 X
(cond ((null m) '())7 k1 I5 f9 n- S |7 r$ c
('t (cons (eval (car m) a)5 G5 A; i5 \4 h' G3 Z# J9 V; q
(evlis (cdr m) a)))))5 A+ A: A( p1 M+ u' ~( f# p% ?
3 O8 G" s4 B+ ?) s(注:可能有的读者已经发现,Lisp并不要求你一定要在使用函数前先定义它)% E2 m' _ }( R, y/ ^1 r& A0 A
' e) ^* _, Y' _$ C, R$ X2 E整个程序包含三个函数,主函数我们遵从Lisp(和Python、Perl)的惯例,叫它eval,它是整个程序的骨架。eval的定义比我们以前看到的任何一个函数都要长,让我们考虑它的每一部分是如何工作的。& {7 z, t+ \) K3 x1 }
( Q0 b8 E( |( _8 S- g- feval有两个自变量:e是要求值的表达式,a是由一些赋给原子的值构成的表,这些值有点象函数调用中的参数。这个形如pair返回值的表叫做上下文,正是为了构造和搜索这种表我们才在前一章写了pair和assoc。6 G( ^. B$ V8 ]4 g% u, v; @+ \% R3 [
% j0 G4 Y) ]3 x' c0 ~$ j6 deval的骨架是一个有四个子句的cond表达式,如何对表达式求值取决于它的类型,第一个分支处理原子,如果e是原子, 我们在上下文中寻找它的值:+ ^1 U4 d6 D& N" h; z
6 D ~& ~. e$ \" R7 g> (eval 'x '((x a) (y b))) a
+ @* t( F. G+ s' q# T( G第二个分支是另一个cond,它处理形如(a)的表达式,其中a是原子。这包括所有的基本操作符,每个对应一条分支。9 s7 K e; b+ N* s7 r. D
& _( @8 u7 B. c8 v/ o% c5 t M> (eval '(eq 'a 'a) '()) t > (eval '(cons x '(b c)) '((x a) (y b))) (a b c)+ Z8 b+ O2 ~" k" S5 l$ ^ R
这几个分支(除了quote)都调用eval来寻找自变量的值。' ^; X+ v( o1 X2 Z |+ |2 I' L7 b
+ X0 A/ f- p( B
最后两个分支更复杂些。为了求cond表达式的值我们调用了一个叫evcon的辅助函数。它递归地对cond分支进行求值,寻找第一个元素返回t的子句,如果找到了这样的子句,它返回此分支的第二个元素。
: x3 ]5 r I0 A0 c: |2 s/ Q, H. P; a. ^5 ?
> (eval '(cond ((atom x) 'atom) ('t 'list)) '((x '(a b)))) list / Z, c6 r9 S `3 \3 g$ U; i
第二个分支的最后部分处理函数调用。它把原子替换为它的值(应该是lambda或label表达式)。然后对所得结果表达式求值。于是:+ N g, _ {6 }" G# ^
1 i, ]1 d9 P& i9 G6 y(eval '(f '(b c)) '((f (lambda (x) (cons 'a x)))))/ _6 } M* \) Y( O# F, ?
变为:: G! f; t, h6 E1 x
0 m. M* _5 h4 P6 d3 n$ q
(eval '((lambda (x) (cons 'a x)) '(b c)) '((f (lambda (x) (cons 'a x)))))# C3 ?5 b3 c; w, c, X
它返回(a b c) ' @# s" T6 d- n4 a; C3 T5 k9 x8 p! ~
7 u, U; P/ M, j3 A; `
eval的最后两个cond分支处理第一个元素是lambda或label的函数调。用为了对label表达式求值,先把函数名和函数本身压入上下文,然后调用eval对一个内部有lambda的表达式求值,即:
' v" Z1 U3 P$ C$ Z6 @9 ]; ]
: S* }3 P0 ~& n& C6 H(eval '((label firstatom (lambda (x) (cond ((atom x) x) ('t (firstatom (car x)))))) y) '((y ((a b) (c d)))))
- i7 q. t, r, ?! w( W变为 7 f9 t4 K1 }5 N7 P3 }) s% l2 ~ n
0 x. U" a7 @4 z& w. B) e5 p(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)))))
: q2 t n9 k- C/ K1 d最终返回a。; r9 w+ C: M! w
+ e% S& Q/ \* [" f5 J$ T
最后,对形如((lambda (p1 p2 ... pn) e) a1 a2 ... an)的表达式求值,先调用evlis来求得自变量(a1 a2 ... an)对应的值(v1 v2 ... vn),把(p1 v1) (p2 v2) ... (pn vn)添加到上下文里,然后对e求值。于是:
3 d, r9 X0 }2 D* H8 [6 B; R+ t+ Q
- @$ x9 O! i: y5 |1 r(eval '((lambda (x y) (cons x (cdr y))) 'a '(b c d)) '())
0 ?1 z. e. A4 H# a; G' B变为:3 }( p# \ k3 h
0 v, P& @' V& q c
(eval '(cons x (cdr y)) '((x a) (y (b c d))))( a( c8 z; O$ u, M
最终返回(a c d)。% l. R) {/ j. b+ h, U" ^- k
& f0 L7 A3 D& G1 ]! B6 n1 [% Z讲了这么一大篇,如果你看懂了,说明你已经理解Lisp甚至FP的基本编程方式和思路,那么我们写了一个如此之长的程序究竟能干什么呢?
1 t9 I- ~* n( k- |/ @5 i9 g
+ J# n& _/ R" k我们在这里得到了一个非常优美的计算模型,eval函数实际上实现了整个语言,用它我们可以定义所需的任何其它函数。换句话说,我们现在有了一个自己的Lisp。
8 b' c3 X) |/ P- P
E% n+ P0 ~. S(注:由此可见,递归下降的语法分析是多么美好啊,因为它意味着你可以用几十、最多不过一两百行程序搞定一个复杂的分析器,对比LALR你将更有体会)
) b8 k' h" U+ ?7 ?8 R
6 S9 J! S* H$ R. o9 r下面我们该去哪儿?这个问题,请读者自己去寻找答案。 |