上一集我们已经见到了一个Lisp程序的大致外貌,在文末,我提到这一集中我们将会用Lisp写一个Lisp解释器,事实上这个解释器并不太长,虽然它有可能是你至今为止见过的最长的一个。
9 g- C- ~6 r3 E& q) |( q& ~/ C. S: x+ [; B" H+ ]" G
我已经有点等不及了,让我们先来看一下整个程序,然后再来讲解:3 \8 ? k- ^; F
3 \5 L0 m* }8 R$ t(defun eval (e a)
& V6 K$ o$ }7 s) d1 l (cond 2 H# v" s5 @* h8 O' f7 L# n# x/ S
((atom e) (assoc e a))
/ [+ a6 L7 N' a/ `; v# @ ((atom (car e))- W" @$ D. e' J4 d* l& X4 S1 v/ ^ B
(cond
+ p p. J/ z7 r ((eq (car e) 'quote) (cadr e))9 M3 @" C( r+ H+ ]7 g, E& y
((eq (car e) 'atom) (atom (eval (cadr e) a))): G$ f! p9 Y% r% U: {" ]
((eq (car e) 'eq) (eq (eval (cadr e) a)
! P7 {; S/ U* q$ v (eval (caddr e) a)))8 X, t. m6 T& o
((eq (car e) 'car) (car (eval (cadr e) a)))% o3 I. N" {0 H( n2 h K
((eq (car e) 'cdr) (cdr (eval (cadr e) a)))) ]0 I& A+ K. h& Z
((eq (car e) 'cons) (cons (eval (cadr e) a)
o) w/ O( P4 u9 X, @( T, L, S (eval (caddr e) a)))2 Z$ g$ y0 B6 r: d3 n
((eq (car e) 'cond) (evcon (cdr e) a)): L5 M; M* W6 Q' o2 m
('t (eval (cons (assoc (car e) a)
; h$ ?) m* x+ j2 ^, }- h" {# a (cdr e))
, `, P' J2 S o. u- ^9 T a))))
, w' k, s1 ?0 O% T ((eq (caar e) 'label)' i1 j7 e4 k! X5 U" l& E& X
(eval (cons (caddar e) (cdr e)) B9 H' x8 L: u* r I. l
(cons (list (cadar e) (car e)) a)))/ H) i) o1 }. ]" w' v } }
((eq (caar e) 'lambda)
# Q4 r H9 j! \& s. X (eval (caddar e)# f. e" N* Q3 D2 Y2 n9 q% S, D
(append (pair (cadar e) (evlis (cdr e) a))- R, F2 ]8 |% j% _4 m* \
a)))))
$ ~3 C `, Q3 J8 [2 P, |
9 u A4 J, f/ Y; h(defun evcon (c a)
* e$ d0 \, n& K; @, X2 c; E! K/ ~ (cond ((eval (caar c) a)* U' a2 r/ A- G- A' \* a
(eval (cadar c) a))
/ v& S. _, D2 B- m$ }4 N" Z# a* a6 _$ K ('t (evcon (cdr c) a))))
5 }4 ?" u: g J, h1 ~3 G0 H' l6 }# M; T. |7 b
(defun evlis (m a)7 O6 G' t" i- V6 D& E |% `* R6 F
(cond ((null m) '()), s9 ^8 n3 ~8 H3 E! t
('t (cons (eval (car m) a)& K3 w O, e7 O* `% T9 w! d( `: j
(evlis (cdr m) a)))))) I1 V$ f3 ]" s) ^
0 k1 T/ r+ B J8 X# M(注:可能有的读者已经发现,Lisp并不要求你一定要在使用函数前先定义它)
2 {7 q9 y" [" ~% t% ^: @) ]+ Z9 r5 b/ J$ r: D' u7 P
整个程序包含三个函数,主函数我们遵从Lisp(和Python、Perl)的惯例,叫它eval,它是整个程序的骨架。eval的定义比我们以前看到的任何一个函数都要长,让我们考虑它的每一部分是如何工作的。- r) H1 x9 n6 H6 y! D# X* f
4 E. J8 b" m p+ c! P& u& S' T3 h
eval有两个自变量:e是要求值的表达式,a是由一些赋给原子的值构成的表,这些值有点象函数调用中的参数。这个形如pair返回值的表叫做上下文,正是为了构造和搜索这种表我们才在前一章写了pair和assoc。9 k5 e- _. e% G+ w% [: h
. g2 J: _. j! H2 {" [eval的骨架是一个有四个子句的cond表达式,如何对表达式求值取决于它的类型,第一个分支处理原子,如果e是原子, 我们在上下文中寻找它的值:
- z) _+ {+ s% X; d- c
3 O5 A5 }$ U6 W. i# [5 M> (eval 'x '((x a) (y b))) a
( Y5 X7 i- W5 `3 d第二个分支是另一个cond,它处理形如(a)的表达式,其中a是原子。这包括所有的基本操作符,每个对应一条分支。2 W* V1 Y! z8 l2 s. t
) [0 O. R; z9 G) p9 ~) i+ @> (eval '(eq 'a 'a) '()) t > (eval '(cons x '(b c)) '((x a) (y b))) (a b c)
0 Z/ R% @9 Z2 @/ b这几个分支(除了quote)都调用eval来寻找自变量的值。- _+ @3 T9 I4 I( g5 |. `
$ {$ j4 _3 \! c& n最后两个分支更复杂些。为了求cond表达式的值我们调用了一个叫evcon的辅助函数。它递归地对cond分支进行求值,寻找第一个元素返回t的子句,如果找到了这样的子句,它返回此分支的第二个元素。, X- [5 `9 m. Y- F1 v
# [) O$ z4 M8 A# U9 `' Q> (eval '(cond ((atom x) 'atom) ('t 'list)) '((x '(a b)))) list
& @; Y% r9 Y) t7 b0 G1 d, g第二个分支的最后部分处理函数调用。它把原子替换为它的值(应该是lambda或label表达式)。然后对所得结果表达式求值。于是:2 P" v- {" j* V5 l I: U3 Y
5 b# ]: |/ F# u$ b+ n/ A
(eval '(f '(b c)) '((f (lambda (x) (cons 'a x)))))8 b) Z, k# `0 U1 G$ g
变为:4 r/ N+ T* ?0 n' ?% y- R5 Z
% l, U* H/ l( O0 J4 s) Y- Y# B" }
(eval '((lambda (x) (cons 'a x)) '(b c)) '((f (lambda (x) (cons 'a x)))))6 Q H- Z0 E$ a
它返回(a b c)
4 G6 m1 |; g) q
9 ^; o: H# r, R" R; Feval的最后两个cond分支处理第一个元素是lambda或label的函数调。用为了对label表达式求值,先把函数名和函数本身压入上下文,然后调用eval对一个内部有lambda的表达式求值,即:
4 z# `, f# k0 _6 @+ Q
& R% U8 ?9 v8 ~(eval '((label firstatom (lambda (x) (cond ((atom x) x) ('t (firstatom (car x)))))) y) '((y ((a b) (c d)))))6 [: d4 ]3 O( M! \
变为
6 x8 B! \, }( h# L C- r8 ?' h3 _" W: I6 A5 I
(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 c9 a1 O$ s- ?# A6 E3 J最终返回a。. ^; }, q( _, S( y
' x s! l* z$ o" l; A) _1 B
最后,对形如((lambda (p1 p2 ... pn) e) a1 a2 ... an)的表达式求值,先调用evlis来求得自变量(a1 a2 ... an)对应的值(v1 v2 ... vn),把(p1 v1) (p2 v2) ... (pn vn)添加到上下文里,然后对e求值。于是:. }/ k4 C& r P+ r, r
; F3 d5 K2 v- {, a) v
(eval '((lambda (x y) (cons x (cdr y))) 'a '(b c d)) '())5 \4 V: e# I5 ~1 b, c7 B
变为:& c' q k& @9 P! N, Z( _
* i+ `. h& [0 G8 m/ w& u0 X9 g/ t
(eval '(cons x (cdr y)) '((x a) (y (b c d))))
. a, f5 u% ~% ]$ T+ M- F最终返回(a c d)。
* l$ v" }9 T/ }$ U) x9 i( ^; E* q6 S1 n
讲了这么一大篇,如果你看懂了,说明你已经理解Lisp甚至FP的基本编程方式和思路,那么我们写了一个如此之长的程序究竟能干什么呢?3 m. X) L+ l- J+ e. j/ c
# U! e' x# i( X( n. s+ k7 G6 ]- e
我们在这里得到了一个非常优美的计算模型,eval函数实际上实现了整个语言,用它我们可以定义所需的任何其它函数。换句话说,我们现在有了一个自己的Lisp。
, I$ h9 t' ^5 _5 _9 @/ T d' ~* x+ J7 ?. ~& F" G& j
(注:由此可见,递归下降的语法分析是多么美好啊,因为它意味着你可以用几十、最多不过一两百行程序搞定一个复杂的分析器,对比LALR你将更有体会)
2 Z1 A! C) l6 P8 ^9 `4 e6 C3 J* Z# E" Q+ H z& V2 z+ N
下面我们该去哪儿?这个问题,请读者自己去寻找答案。 |