上一集我们已经见到了一个Lisp程序的大致外貌,在文末,我提到这一集中我们将会用Lisp写一个Lisp解释器,事实上这个解释器并不太长,虽然它有可能是你至今为止见过的最长的一个。4 X0 Q5 a. ~7 y, z. E
* s$ n& t1 K1 w5 P' l
我已经有点等不及了,让我们先来看一下整个程序,然后再来讲解:
3 q* ^7 u9 R& w' Z: d5 }3 y
% K6 B9 N" ~3 U(defun eval (e a). i- R; ~& u+ m( A* T; n( V1 B
(cond
6 u# l( G5 A3 g1 v ((atom e) (assoc e a))0 `7 @5 y% K G/ {
((atom (car e)), q& s7 Z: \. g, j" V0 N
(cond ; j3 i# b8 h+ j/ b; A) H
((eq (car e) 'quote) (cadr e))
+ S3 `+ n1 Z# u' L: t# {; z- { ((eq (car e) 'atom) (atom (eval (cadr e) a)))$ {5 Y$ E+ v6 a
((eq (car e) 'eq) (eq (eval (cadr e) a)/ ]6 n' x5 {6 c' J6 h4 v) N9 }
(eval (caddr e) a)))
4 H: l( e% k0 T+ A$ U9 b ((eq (car e) 'car) (car (eval (cadr e) a)))
( \* l0 ?. U* b- r# J ((eq (car e) 'cdr) (cdr (eval (cadr e) a)))+ m3 y Y2 y. J
((eq (car e) 'cons) (cons (eval (cadr e) a)
5 p4 T! N n3 }3 p8 }* G0 R (eval (caddr e) a)))+ C2 X( s$ c: x0 f& f6 ~
((eq (car e) 'cond) (evcon (cdr e) a))
) [ F4 _8 S; F5 } ('t (eval (cons (assoc (car e) a)
) I3 q9 t5 N/ ^4 _ (cdr e))* A: \5 [% W/ s! y
a))))/ r, D% H+ a6 [6 b% R, N. {# I
((eq (caar e) 'label)
9 V+ b7 j7 s8 U (eval (cons (caddar e) (cdr e))
6 D# m d% q. o- N! Q* V4 k( V) L (cons (list (cadar e) (car e)) a)))- {% d4 m; m' g. U% y0 W. J
((eq (caar e) 'lambda)
- \% O/ W$ o: R) |# G6 Q (eval (caddar e)
! d2 K/ E( {9 ^ (append (pair (cadar e) (evlis (cdr e) a))) A2 y j, y$ C8 I7 z, {$ p
a)))))- q9 q$ ?' P, W6 c' M, r+ Z/ `; W ~, i
7 Y: s) m$ n6 |2 o2 o1 M+ I1 f
(defun evcon (c a)3 v& n5 P8 f. u4 |+ }1 @. _
(cond ((eval (caar c) a)
Q7 z D1 t$ N9 |5 R3 e (eval (cadar c) a))
2 o& x' l9 [: R ('t (evcon (cdr c) a))))2 g- P: H7 b4 {, P. T
3 l5 L( o' T7 t: G/ A ~(defun evlis (m a)
; G0 Z1 r3 W' n1 T- z6 a" _4 ]0 r (cond ((null m) '())4 W3 @5 u+ c1 G* c- y
('t (cons (eval (car m) a)4 X0 n0 d# P1 z& `
(evlis (cdr m) a)))))" p) S/ J3 e/ }8 i. u) ^( s
( ?& Z6 h* N) L* B, \(注:可能有的读者已经发现,Lisp并不要求你一定要在使用函数前先定义它)+ k4 I* [0 S8 o; S8 j2 D
4 O, n1 g& U8 ?
整个程序包含三个函数,主函数我们遵从Lisp(和Python、Perl)的惯例,叫它eval,它是整个程序的骨架。eval的定义比我们以前看到的任何一个函数都要长,让我们考虑它的每一部分是如何工作的。1 K4 }4 R5 o' _" U$ h7 e
- A% ?/ F9 v# d# P j5 z6 @
eval有两个自变量:e是要求值的表达式,a是由一些赋给原子的值构成的表,这些值有点象函数调用中的参数。这个形如pair返回值的表叫做上下文,正是为了构造和搜索这种表我们才在前一章写了pair和assoc。
5 R3 P7 z' P7 _8 Q6 ]0 P
" q3 v1 ?& E3 v$ X3 Y8 u& y1 peval的骨架是一个有四个子句的cond表达式,如何对表达式求值取决于它的类型,第一个分支处理原子,如果e是原子, 我们在上下文中寻找它的值:
) T" I9 c S# ~: O, u: |" I2 e( O
> (eval 'x '((x a) (y b))) a
' Z; A, g3 @2 Z( @( y第二个分支是另一个cond,它处理形如(a)的表达式,其中a是原子。这包括所有的基本操作符,每个对应一条分支。
4 z! c- _( R; T. Z% A1 d
6 g, f: |1 {1 A7 J- M: u# Q> (eval '(eq 'a 'a) '()) t > (eval '(cons x '(b c)) '((x a) (y b))) (a b c)! {% x Y c3 q5 R
这几个分支(除了quote)都调用eval来寻找自变量的值。, X3 y8 B8 u" ]" s
* v! u& K" S2 @2 Q( h0 T6 q& R; _最后两个分支更复杂些。为了求cond表达式的值我们调用了一个叫evcon的辅助函数。它递归地对cond分支进行求值,寻找第一个元素返回t的子句,如果找到了这样的子句,它返回此分支的第二个元素。/ }2 @, ~; `! G
$ h" H, n/ B, D> (eval '(cond ((atom x) 'atom) ('t 'list)) '((x '(a b)))) list 8 T( m4 O' I- R
第二个分支的最后部分处理函数调用。它把原子替换为它的值(应该是lambda或label表达式)。然后对所得结果表达式求值。于是:
6 |' o8 C) s0 O& ]2 E
# M) F8 G7 ?! p, n- E5 R( Z(eval '(f '(b c)) '((f (lambda (x) (cons 'a x)))))# v3 d# R' D. s/ ?8 }9 y6 A( F
变为:
' _) X0 }2 X% u$ e# w: w8 e. \/ Z/ |4 \' P9 a- w7 k
(eval '((lambda (x) (cons 'a x)) '(b c)) '((f (lambda (x) (cons 'a x)))))) w$ U( Y, m& s- U- ]
它返回(a b c) * K, N: {/ I, S
7 l" o* b- j! }* o) l7 y" T
eval的最后两个cond分支处理第一个元素是lambda或label的函数调。用为了对label表达式求值,先把函数名和函数本身压入上下文,然后调用eval对一个内部有lambda的表达式求值,即:7 U+ h5 G. O7 a1 O; y& ]; w, t
& T* ?, y9 Y( u; h# ~( x, K7 ?5 h
(eval '((label firstatom (lambda (x) (cond ((atom x) x) ('t (firstatom (car x)))))) y) '((y ((a b) (c d)))))5 P6 r" r/ }/ @1 w" ]. ~4 |
变为
' d9 z3 ^ a6 ~# f) z% o$ y K! ? [# 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)))))
4 c' h5 P5 n! l h" ^0 I# t最终返回a。$ W5 K) c& a+ Q- x* d
0 m9 w$ l% K5 c9 _" Z
最后,对形如((lambda (p1 p2 ... pn) e) a1 a2 ... an)的表达式求值,先调用evlis来求得自变量(a1 a2 ... an)对应的值(v1 v2 ... vn),把(p1 v1) (p2 v2) ... (pn vn)添加到上下文里,然后对e求值。于是:
0 ]9 a* w1 b' ^; W* A2 c/ _
! H% W( Z3 B5 H, ?# ?; e N- h(eval '((lambda (x y) (cons x (cdr y))) 'a '(b c d)) '())
. D1 q) F% t* q, b/ W: Y' m/ Q( z变为:
9 \) W# H8 c; a3 I& T$ a! Q; g# p" y3 M) |$ @( G
(eval '(cons x (cdr y)) '((x a) (y (b c d)))). [6 ^$ B, n* H1 k" O6 R2 R
最终返回(a c d)。
# {( k0 t H; O" V ~2 _% s9 n! q* s2 d: m
讲了这么一大篇,如果你看懂了,说明你已经理解Lisp甚至FP的基本编程方式和思路,那么我们写了一个如此之长的程序究竟能干什么呢?
8 P2 y R( p0 U: j5 l" [1 ~* O; j+ A! B4 J
我们在这里得到了一个非常优美的计算模型,eval函数实际上实现了整个语言,用它我们可以定义所需的任何其它函数。换句话说,我们现在有了一个自己的Lisp。* K) K& `6 g9 V2 z
# k+ g1 n6 B- D. P1 n! r(注:由此可见,递归下降的语法分析是多么美好啊,因为它意味着你可以用几十、最多不过一两百行程序搞定一个复杂的分析器,对比LALR你将更有体会)
! [; a3 B& t! v; ^& U2 p
+ t2 i; f m8 {- q" j& c: |( B* d下面我们该去哪儿?这个问题,请读者自己去寻找答案。 |