上一集我们已经见到了一个Lisp程序的大致外貌,在文末,我提到这一集中我们将会用Lisp写一个Lisp解释器,事实上这个解释器并不太长,虽然它有可能是你至今为止见过的最长的一个。/ f) [! T( L% b/ S+ E
' P9 ?2 G! n2 Z( b2 p我已经有点等不及了,让我们先来看一下整个程序,然后再来讲解:
N. _5 G, t6 K3 K
% V- K5 c: Q0 {' }7 F& i(defun eval (e a)
; v! m! ?: l+ F1 t2 U* X0 X (cond 1 ^( ?* ~" v2 _: Y, I) p N. B' T9 j
((atom e) (assoc e a))
) Y. Z+ {/ S& g" ?% w% T ((atom (car e))
! f; d: J' e- E1 D! v* H% i (cond % f) k- V0 j8 V
((eq (car e) 'quote) (cadr e))1 {) i* J' P+ L/ a0 X) K- I
((eq (car e) 'atom) (atom (eval (cadr e) a)))7 t' y. Y1 k7 t8 B; F, m' G7 a
((eq (car e) 'eq) (eq (eval (cadr e) a)
, k: r7 a1 b; e( b% [ (eval (caddr e) a)))) P! i: N1 A n9 y2 R
((eq (car e) 'car) (car (eval (cadr e) a)))1 ?" Y# M1 w6 @# ^" V% a6 ]1 y9 D
((eq (car e) 'cdr) (cdr (eval (cadr e) a))); r8 b; b5 [, T0 S8 ]7 q
((eq (car e) 'cons) (cons (eval (cadr e) a)8 t6 P2 F9 b( l% i$ d% P% J+ T% v) ~
(eval (caddr e) a)))
! |5 p% L3 q$ g1 p- o3 l6 k& c, F ((eq (car e) 'cond) (evcon (cdr e) a))( z G6 X6 e. |. E. A& K0 o
('t (eval (cons (assoc (car e) a)
0 o' w9 |/ m# q# g4 g (cdr e))
) R) R @5 r& @4 S a))))3 v* M& d# z2 _
((eq (caar e) 'label)8 z; r7 v- E$ u; p' x2 X& g
(eval (cons (caddar e) (cdr e))' d! N, ?7 ~! a
(cons (list (cadar e) (car e)) a)))
/ b" \ p& r! y/ Z( ` p# S ((eq (caar e) 'lambda)
8 v3 F6 l: ^( D% G6 [$ c" o) a6 {, | (eval (caddar e)
2 C0 _2 b7 \' Q/ ~3 N- {% s (append (pair (cadar e) (evlis (cdr e) a))8 ]7 H8 H# `' i$ e5 [
a)))))
" q: d4 d I9 j4 v+ L
2 _) h# ]/ I% c) Y) c# }. v8 D8 H(defun evcon (c a)8 l1 b# y4 }0 f
(cond ((eval (caar c) a)
+ t7 g6 m& ~' w: z, z4 a (eval (cadar c) a))
+ p* r0 c/ |, }5 v. H ('t (evcon (cdr c) a))))
3 ?1 L% F' ?4 D* r
. j. H2 x, w; W7 `0 W: b, a(defun evlis (m a)& s; X! T6 I/ D. }% Z: B
(cond ((null m) '())- b; m* Y* p; Z, i
('t (cons (eval (car m) a)
5 M9 u% P1 q# B7 p (evlis (cdr m) a)))))' L! N5 b: g7 b6 N
, i$ w3 p6 D4 p7 m7 Z
(注:可能有的读者已经发现,Lisp并不要求你一定要在使用函数前先定义它)
, v2 @' N- t& [8 k1 ^- A0 }) }- l" Y; J- \$ d" ?1 ^# z% o8 ?
整个程序包含三个函数,主函数我们遵从Lisp(和Python、Perl)的惯例,叫它eval,它是整个程序的骨架。eval的定义比我们以前看到的任何一个函数都要长,让我们考虑它的每一部分是如何工作的。4 ^5 S$ [/ W6 z- v5 m
( _! z: p" A* |% J3 J3 U2 t
eval有两个自变量:e是要求值的表达式,a是由一些赋给原子的值构成的表,这些值有点象函数调用中的参数。这个形如pair返回值的表叫做上下文,正是为了构造和搜索这种表我们才在前一章写了pair和assoc。
; w4 |. ?5 z5 J9 [, j) l
# j3 J9 {/ Z( l$ _2 J! K8 m ^eval的骨架是一个有四个子句的cond表达式,如何对表达式求值取决于它的类型,第一个分支处理原子,如果e是原子, 我们在上下文中寻找它的值:
. ]( O' k& j8 p" O2 g- a! c# L7 W* z" U. @
> (eval 'x '((x a) (y b))) a, P, e8 \9 m2 |& T# e: V
第二个分支是另一个cond,它处理形如(a)的表达式,其中a是原子。这包括所有的基本操作符,每个对应一条分支。
L* w' D1 A8 o) p6 i' l9 w0 }) L4 }3 Q$ A/ Q) R! a, l
> (eval '(eq 'a 'a) '()) t > (eval '(cons x '(b c)) '((x a) (y b))) (a b c). A. _4 i# K" g! o! N# @" H
这几个分支(除了quote)都调用eval来寻找自变量的值。
; w; j( O; W" r! g7 M: d, }( I; D. A4 W V, O
最后两个分支更复杂些。为了求cond表达式的值我们调用了一个叫evcon的辅助函数。它递归地对cond分支进行求值,寻找第一个元素返回t的子句,如果找到了这样的子句,它返回此分支的第二个元素。
/ ?8 s5 v7 M7 e" T- z" L7 M* ]5 w* t4 [
> (eval '(cond ((atom x) 'atom) ('t 'list)) '((x '(a b)))) list 5 z: z9 x/ _! |- a
第二个分支的最后部分处理函数调用。它把原子替换为它的值(应该是lambda或label表达式)。然后对所得结果表达式求值。于是:
# h. J$ W- P* V8 z0 R7 C/ T* I8 z7 j/ k/ }' F+ B; ~( m$ x
(eval '(f '(b c)) '((f (lambda (x) (cons 'a x)))))
8 @8 f; u' s1 \6 {: l/ f变为:
" K4 \0 N* Y* T" L0 N ^
7 B" d' p* w, _(eval '((lambda (x) (cons 'a x)) '(b c)) '((f (lambda (x) (cons 'a x)))))
( G5 g7 d5 Q: o7 `& e: P5 q它返回(a b c) ! M R: e- Q( W4 T
$ N" y! T8 ?1 @* W5 L) Seval的最后两个cond分支处理第一个元素是lambda或label的函数调。用为了对label表达式求值,先把函数名和函数本身压入上下文,然后调用eval对一个内部有lambda的表达式求值,即:
6 W, @5 c9 w4 {/ L) F' \
2 _% u0 o# u6 b& j(eval '((label firstatom (lambda (x) (cond ((atom x) x) ('t (firstatom (car x)))))) y) '((y ((a b) (c d)))))
1 P U$ j) \2 {, a# Z, K变为
' a' w) G3 t& Y* a& `& a7 e, b A
0 v: `6 p' O8 w. z+ l- @(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)))))
3 X& p8 _. F* x$ n, b最终返回a。
; k0 f C' F7 z' p% |$ M j2 j2 G- r( n1 {: c# b
最后,对形如((lambda (p1 p2 ... pn) e) a1 a2 ... an)的表达式求值,先调用evlis来求得自变量(a1 a2 ... an)对应的值(v1 v2 ... vn),把(p1 v1) (p2 v2) ... (pn vn)添加到上下文里,然后对e求值。于是:
4 x9 m: ]/ a3 w! [. j7 F6 o
! O+ \% r& \9 h: y* F0 p+ S% A(eval '((lambda (x y) (cons x (cdr y))) 'a '(b c d)) '())* w/ i! V- |7 A% T: b
变为:
; m( o9 z( c% h; w8 }6 X
% S$ V7 j! @/ |" w5 f4 |: T3 t(eval '(cons x (cdr y)) '((x a) (y (b c d))))( z6 Y& u6 f. b6 U, S( R
最终返回(a c d)。
5 s% J* k4 S9 T2 N( ~& Z9 z
* W/ S) h; p( L" T讲了这么一大篇,如果你看懂了,说明你已经理解Lisp甚至FP的基本编程方式和思路,那么我们写了一个如此之长的程序究竟能干什么呢?
0 z- C9 G5 z7 m+ w1 ? d, @* e# J. ^6 P5 U
我们在这里得到了一个非常优美的计算模型,eval函数实际上实现了整个语言,用它我们可以定义所需的任何其它函数。换句话说,我们现在有了一个自己的Lisp。
9 _$ q' j) ~/ s4 H( J, p7 [1 g1 v# x# |# r+ o1 I
(注:由此可见,递归下降的语法分析是多么美好啊,因为它意味着你可以用几十、最多不过一两百行程序搞定一个复杂的分析器,对比LALR你将更有体会)
; h8 `2 j9 @3 V; Q& [8 w8 {: C \/ _" h& ~# k' D
下面我们该去哪儿?这个问题,请读者自己去寻找答案。 |