上一集我们已经见到了一个Lisp程序的大致外貌,在文末,我提到这一集中我们将会用Lisp写一个Lisp解释器,事实上这个解释器并不太长,虽然它有可能是你至今为止见过的最长的一个。
9 x, b+ y" w% J/ B F1 W$ P4 W) |" I" Z# m% G
我已经有点等不及了,让我们先来看一下整个程序,然后再来讲解:
' R, Q" K0 A; j) N, w V9 `9 u8 [7 M+ L2 \" x9 R3 N' ]
(defun eval (e a)
3 i& J- g" ^+ A" w0 D (cond
O( n) p) H0 E1 ]8 @6 V2 y ((atom e) (assoc e a))4 y( {: H3 V' S0 T1 @ |
((atom (car e))% M2 a+ s) o" }1 {+ L- I* }
(cond
2 P: {- w: V% f6 w5 ^3 m+ J/ S ((eq (car e) 'quote) (cadr e))
" _! i7 Q1 \5 L# J# ^ ((eq (car e) 'atom) (atom (eval (cadr e) a)))
- L/ \3 \# q6 q7 Z' o ((eq (car e) 'eq) (eq (eval (cadr e) a)
5 K- H% p: q, P" e- U (eval (caddr e) a)))0 G& s0 h) ~' @& h
((eq (car e) 'car) (car (eval (cadr e) a)))4 E; H8 C0 } h7 q
((eq (car e) 'cdr) (cdr (eval (cadr e) a)))( c. h8 O& Q3 g, ~
((eq (car e) 'cons) (cons (eval (cadr e) a)# |6 p. i6 |% {
(eval (caddr e) a)))
6 ~% C: B+ R- B x5 Y0 m! J, ]/ Q+ Z ((eq (car e) 'cond) (evcon (cdr e) a)); U" y/ a: F* U/ ^8 E
('t (eval (cons (assoc (car e) a)
3 \9 _0 V9 \2 S! t (cdr e))
# l9 b6 n7 L6 k8 _# \0 h a))))
7 `: [5 P8 L- E2 D& C& N ((eq (caar e) 'label)
8 N, l: D- C) G9 N* P (eval (cons (caddar e) (cdr e))
I" R; R) N( q6 I2 Z8 m9 z n (cons (list (cadar e) (car e)) a)))4 O! }5 P6 B# N' I' e
((eq (caar e) 'lambda)
9 H% \4 T: M9 a* O6 A (eval (caddar e)5 z$ U7 C- n0 X: b. i: V
(append (pair (cadar e) (evlis (cdr e) a))/ [/ a. T; A( U U( ^
a)))))
8 H1 x$ \/ ~1 F- b% w7 @0 A6 e
* {# d+ q6 w8 l9 k; F* u% f(defun evcon (c a)
3 c# M5 B8 Y) ] (cond ((eval (caar c) a)
+ P% H i; u, l (eval (cadar c) a))
9 s0 w; w/ C" T( Y) @ ('t (evcon (cdr c) a))))
( L# e# G4 a# S* R' _6 l: @, b3 u( F. z' B8 K# A
(defun evlis (m a)
# G% B. ^7 H" w; h) K3 z (cond ((null m) '()) s5 Z& a8 u0 @* s
('t (cons (eval (car m) a)
2 y3 l: o4 g/ o. X3 D" n; K0 | (evlis (cdr m) a)))))0 o( C( P6 r- W% Q" I
( M, p4 d% t* Z F
(注:可能有的读者已经发现,Lisp并不要求你一定要在使用函数前先定义它)
- j1 w# Z9 f5 F7 B
. k) d+ ]( M& s; f- E$ j4 A; K整个程序包含三个函数,主函数我们遵从Lisp(和Python、Perl)的惯例,叫它eval,它是整个程序的骨架。eval的定义比我们以前看到的任何一个函数都要长,让我们考虑它的每一部分是如何工作的。
% Z, f- V$ s% C3 c9 E9 D6 ^& l( U4 c! R9 v' F+ u0 a
eval有两个自变量:e是要求值的表达式,a是由一些赋给原子的值构成的表,这些值有点象函数调用中的参数。这个形如pair返回值的表叫做上下文,正是为了构造和搜索这种表我们才在前一章写了pair和assoc。
4 ?6 G+ ^( W3 n5 b5 g0 }# z7 {: T! ]" M/ [0 H, \
eval的骨架是一个有四个子句的cond表达式,如何对表达式求值取决于它的类型,第一个分支处理原子,如果e是原子, 我们在上下文中寻找它的值:
5 g5 {7 n! U7 N% l0 Y$ l# p5 x* K" ]4 s* o* _( |
> (eval 'x '((x a) (y b))) a: V6 W- D. ]' |( D2 G" X. |0 a
第二个分支是另一个cond,它处理形如(a)的表达式,其中a是原子。这包括所有的基本操作符,每个对应一条分支。
1 _7 X/ b9 w' D/ p ~
8 u# u' t, H& g* u, L> (eval '(eq 'a 'a) '()) t > (eval '(cons x '(b c)) '((x a) (y b))) (a b c)
3 J x7 g# E. e2 ] d这几个分支(除了quote)都调用eval来寻找自变量的值。
+ I3 l2 ^6 E" a# V: e; L b
" b, j5 b* ~. d9 v; U3 w3 }最后两个分支更复杂些。为了求cond表达式的值我们调用了一个叫evcon的辅助函数。它递归地对cond分支进行求值,寻找第一个元素返回t的子句,如果找到了这样的子句,它返回此分支的第二个元素。
( C# I2 W9 P3 q5 ]
( S+ P' L9 T; h; m: t- u> (eval '(cond ((atom x) 'atom) ('t 'list)) '((x '(a b)))) list 9 q2 ~# x& n. Y+ X# w, ]
第二个分支的最后部分处理函数调用。它把原子替换为它的值(应该是lambda或label表达式)。然后对所得结果表达式求值。于是:
9 a7 [) |% q3 G' E! R! z
m+ e( s* B2 K(eval '(f '(b c)) '((f (lambda (x) (cons 'a x)))))7 s7 X2 y* M; x+ a
变为:
6 Y+ [$ h- p4 d% g7 U S- N% {# Q0 g7 @# B8 I5 h
(eval '((lambda (x) (cons 'a x)) '(b c)) '((f (lambda (x) (cons 'a x)))))
# y# E0 n5 J5 f它返回(a b c) * P* S- q, n h
8 x1 u( R" G7 ^6 Geval的最后两个cond分支处理第一个元素是lambda或label的函数调。用为了对label表达式求值,先把函数名和函数本身压入上下文,然后调用eval对一个内部有lambda的表达式求值,即:# V2 Y, G- O9 X* K, D
, u1 |3 A7 E5 _; Y1 j3 o+ r0 S+ c
(eval '((label firstatom (lambda (x) (cond ((atom x) x) ('t (firstatom (car x)))))) y) '((y ((a b) (c d)))))' G6 W1 v7 n# U6 N7 K0 {* _2 T
变为 8 o' d/ p+ J! }- ~& a, p
3 n0 z7 w3 `/ L2 I% S9 N+ c! M/ i) o- [(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)))))" |5 l+ [, K3 ^9 D4 _$ m
最终返回a。8 U! b3 c* ^4 i5 ?% U
, v8 G: l, D* P+ a' f5 j7 g3 X最后,对形如((lambda (p1 p2 ... pn) e) a1 a2 ... an)的表达式求值,先调用evlis来求得自变量(a1 a2 ... an)对应的值(v1 v2 ... vn),把(p1 v1) (p2 v2) ... (pn vn)添加到上下文里,然后对e求值。于是:: A, \, {* @( D2 g- h2 }
/ P3 a1 O5 m. p I(eval '((lambda (x y) (cons x (cdr y))) 'a '(b c d)) '())& g9 P$ b* p9 m$ Z
变为:
' N$ c k* z ~' x; d' s6 F# M2 \
5 F5 r* Y K% x; O6 g# ?5 j! n(eval '(cons x (cdr y)) '((x a) (y (b c d))))/ i( m# ^" b7 t
最终返回(a c d)。5 ^' R$ G2 H. }2 [
! x' |9 W% [4 i# q+ x+ w: e
讲了这么一大篇,如果你看懂了,说明你已经理解Lisp甚至FP的基本编程方式和思路,那么我们写了一个如此之长的程序究竟能干什么呢?2 T9 q4 j6 o0 e1 @0 ]) B
1 j! R, p! U" r- n/ T我们在这里得到了一个非常优美的计算模型,eval函数实际上实现了整个语言,用它我们可以定义所需的任何其它函数。换句话说,我们现在有了一个自己的Lisp。: {8 z& d; W" b+ P
2 V6 y: r" V8 \$ a& s4 U8 g(注:由此可见,递归下降的语法分析是多么美好啊,因为它意味着你可以用几十、最多不过一两百行程序搞定一个复杂的分析器,对比LALR你将更有体会)
' o: W* l) X" b# | l
' u; y( N, i; F l下面我们该去哪儿?这个问题,请读者自己去寻找答案。 |