上一集我们已经见到了一个Lisp程序的大致外貌,在文末,我提到这一集中我们将会用Lisp写一个Lisp解释器,事实上这个解释器并不太长,虽然它有可能是你至今为止见过的最长的一个。' v a8 V. T" C. z
/ X7 V/ n: ]/ `: G) U: h& z
我已经有点等不及了,让我们先来看一下整个程序,然后再来讲解:" u2 x; Y- w; T2 m2 s3 y6 O3 \
) `& _6 U$ i* {2 b' \- y+ t
(defun eval (e a)3 ], S# s# f# _+ t; w
(cond 4 A2 F2 ~% k U, H! G- l* Q, y
((atom e) (assoc e a))' u' I9 J2 {% k5 m% ?
((atom (car e))" c$ d2 k/ h" j, P- u7 m
(cond ) Z# ~8 F' C! k4 |! q
((eq (car e) 'quote) (cadr e))
/ K5 K% X! m- W* R" q ((eq (car e) 'atom) (atom (eval (cadr e) a)))
5 L& o) v: k( J a, h2 z ((eq (car e) 'eq) (eq (eval (cadr e) a)
, E! u" y! Q3 Y* u1 M0 y; D) r* F (eval (caddr e) a)))
% f c2 V) x& A, h) R ((eq (car e) 'car) (car (eval (cadr e) a)))
% i6 X# O9 S |! w ((eq (car e) 'cdr) (cdr (eval (cadr e) a)))
% a% X5 `! Z; @ O2 _6 ^+ W ((eq (car e) 'cons) (cons (eval (cadr e) a)# J# c0 D* m. i8 E8 N& S1 [
(eval (caddr e) a)))0 R8 W9 v; I; Y
((eq (car e) 'cond) (evcon (cdr e) a))( y l4 ?6 x) @$ y) X
('t (eval (cons (assoc (car e) a)) Q( |: W4 z9 V" D0 K G
(cdr e))( N" R. [% W! j+ d9 @& g
a))))# V# b/ H& |6 w: [* t: s
((eq (caar e) 'label)6 j5 l; @8 z" t& p# W
(eval (cons (caddar e) (cdr e))
( U* {% @1 P7 q* ^1 A4 Q (cons (list (cadar e) (car e)) a)))
8 m2 g2 T8 e" \ ((eq (caar e) 'lambda)' h/ @- V: z9 C, J% Q! }9 | F
(eval (caddar e): Q8 C2 }% e% g( a; o
(append (pair (cadar e) (evlis (cdr e) a))
7 N2 I. [* [' p# e+ t a)))))
6 r5 [$ a" \4 B5 q+ ~
( m+ N- H% x4 }" [( p& B3 @7 i(defun evcon (c a)% q9 ~# e5 v- Y- v% P& t/ I
(cond ((eval (caar c) a)( d) X L# _7 H( Z6 e* O
(eval (cadar c) a))% f& g: m( N8 l3 x! ~; ?# Y
('t (evcon (cdr c) a))))4 L5 i9 u* q4 Z" P
' g' O, ]5 ~) G( k
(defun evlis (m a)
& s& z: B4 Q; v- X6 [ (cond ((null m) '())* T, g( ?( L R
('t (cons (eval (car m) a)
, Q1 |, F) b' K4 q( M, g (evlis (cdr m) a)))))9 `1 a) j; b: q" F' z8 K1 K) C& N
! r3 Q* s$ @: G$ `4 C! O. A% E(注:可能有的读者已经发现,Lisp并不要求你一定要在使用函数前先定义它)
4 p/ @4 R" `/ f: d+ t2 n# p0 i; K' x
; R. O9 z; {0 d8 T" d整个程序包含三个函数,主函数我们遵从Lisp(和Python、Perl)的惯例,叫它eval,它是整个程序的骨架。eval的定义比我们以前看到的任何一个函数都要长,让我们考虑它的每一部分是如何工作的。/ e1 p o$ R4 H2 c4 ~
0 J. y M2 y4 C8 _1 C, beval有两个自变量:e是要求值的表达式,a是由一些赋给原子的值构成的表,这些值有点象函数调用中的参数。这个形如pair返回值的表叫做上下文,正是为了构造和搜索这种表我们才在前一章写了pair和assoc。4 M& @" Q9 s; c6 ^
9 ~2 d R! S$ a: g. k# x! u
eval的骨架是一个有四个子句的cond表达式,如何对表达式求值取决于它的类型,第一个分支处理原子,如果e是原子, 我们在上下文中寻找它的值:, F$ v$ t) I {$ M4 j; c, N
) U7 S7 o l+ i3 {0 r8 ]
> (eval 'x '((x a) (y b))) a
/ \; M8 e1 m3 L+ Q$ L# W1 N) B第二个分支是另一个cond,它处理形如(a)的表达式,其中a是原子。这包括所有的基本操作符,每个对应一条分支。
3 ]% F% ~4 z9 h2 \
; Q" i7 l" U! m( O( f( a. [/ l; h> (eval '(eq 'a 'a) '()) t > (eval '(cons x '(b c)) '((x a) (y b))) (a b c)" M/ z9 y' g; G% l; ?0 I* E
这几个分支(除了quote)都调用eval来寻找自变量的值。8 S* `; S( U4 ~( S% [; v8 P
9 u0 T. e, s/ l Q. S( X
最后两个分支更复杂些。为了求cond表达式的值我们调用了一个叫evcon的辅助函数。它递归地对cond分支进行求值,寻找第一个元素返回t的子句,如果找到了这样的子句,它返回此分支的第二个元素。9 D! d& X- \- o1 r$ F
3 l( m4 C6 O$ C$ m9 f6 h> (eval '(cond ((atom x) 'atom) ('t 'list)) '((x '(a b)))) list $ ~! Q. {( \5 E
第二个分支的最后部分处理函数调用。它把原子替换为它的值(应该是lambda或label表达式)。然后对所得结果表达式求值。于是:
$ J5 i4 Q5 k" p7 K
$ w& A- A# L$ |, o2 n(eval '(f '(b c)) '((f (lambda (x) (cons 'a x)))))9 B) l& z- a" k8 W( I1 B4 a1 Z
变为:
/ Q" K3 K5 a9 g* @/ E1 K- C6 R* J V) X( [+ J2 @% ]# @
(eval '((lambda (x) (cons 'a x)) '(b c)) '((f (lambda (x) (cons 'a x)))))' e) {1 L& ^* t$ a4 }
它返回(a b c) ) [7 {: u* F: }6 ?, k' G0 I
( o: t0 d5 r$ y# xeval的最后两个cond分支处理第一个元素是lambda或label的函数调。用为了对label表达式求值,先把函数名和函数本身压入上下文,然后调用eval对一个内部有lambda的表达式求值,即:9 Z" c, [0 U& a2 W; l8 w, h
( a" {: y- H: n. e3 _7 B+ O(eval '((label firstatom (lambda (x) (cond ((atom x) x) ('t (firstatom (car x)))))) y) '((y ((a b) (c d))))). Q9 F+ n. G4 i: m+ R
变为
& j3 W, r1 x% E; q4 t3 d3 S* l5 V% g$ F
(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)))))8 E/ Q: X- D( r. c7 X8 l+ r7 P4 Z
最终返回a。
7 h; s+ O% Y0 P8 u3 K9 L" ?- i( [: P
最后,对形如((lambda (p1 p2 ... pn) e) a1 a2 ... an)的表达式求值,先调用evlis来求得自变量(a1 a2 ... an)对应的值(v1 v2 ... vn),把(p1 v1) (p2 v2) ... (pn vn)添加到上下文里,然后对e求值。于是:) c( P$ q! ~' s, q% Q
9 p# v j+ I" s7 w" r+ }(eval '((lambda (x y) (cons x (cdr y))) 'a '(b c d)) '())8 n2 ^- p6 K2 n" S# L* k/ A a
变为:
' R; A1 l4 L- N4 i; e& z7 k/ |. ?: }1 M) x m3 {
(eval '(cons x (cdr y)) '((x a) (y (b c d))))
S4 a; D! c$ q ~/ @9 H9 B最终返回(a c d)。
1 g* k) Z% k* Z$ V. a. H7 O( j& i+ {# v9 ~% u! }
讲了这么一大篇,如果你看懂了,说明你已经理解Lisp甚至FP的基本编程方式和思路,那么我们写了一个如此之长的程序究竟能干什么呢? ?) u' z# h( y# {+ j. y
8 @ j8 d5 C: M) R
我们在这里得到了一个非常优美的计算模型,eval函数实际上实现了整个语言,用它我们可以定义所需的任何其它函数。换句话说,我们现在有了一个自己的Lisp。
- F4 o1 k* P$ \4 p+ ?3 F; |7 B- ] k9 K" p3 Z, u
(注:由此可见,递归下降的语法分析是多么美好啊,因为它意味着你可以用几十、最多不过一两百行程序搞定一个复杂的分析器,对比LALR你将更有体会)
0 s: X+ _, K9 Z1 e; j4 x# T+ A9 G1 @1 h3 {6 t
下面我们该去哪儿?这个问题,请读者自己去寻找答案。 |