1. 程式人生 > >5.4.1 顯式控制的直譯器的核心

5.4.1 顯式控制的直譯器的核心

 5.4.1 顯式控制的直譯器的核心
在直譯器中核心元素是指令集的序列,指令集開始於eval-dispatch.這對應於
4.1.1部分中描述的元迴圈直譯器的程式eval.當控制器開始於eval-dispatch,它
解釋環境中的表示式。當解釋完成時,控制器將來到儲存在暫存器中的入口點。
暫存器中儲存著表示式的值。正如元迴圈的eval,eval-dispatch的結構也是
一個案例分析,根據要被解釋的表示式的語法型別。

eval-dispatch
   (test (op self-evaluating?)  (reg exp))
   (branch  (label ev-self-eval))
   (test (op variable?) (reg exp))
   (branch (label ev-variable))
   (test (op quoted?) (reg exp))
   (branch (label ev-quoted))
   (test (op assignment?)  (reg exp))
   (branch (label ev-assignment))
   (test (op definition?) (reg exp))
   (branch (label ev-definition))
   (test (op if?)  (reg exp))
   (branch (label ev-if))
   (test (op lambda?) (reg exp))
   (branch (label ev-lambda))
   (test (op begin?)  (reg exp))
   (branch (label ev-begin))
   (test (op application?) (reg exp))
   (branch (label ev-application))
   (goto (label unknown-expression-type))

*  解釋簡單的表示式
數字和字串(它們是自解釋的),變數,引用和Lambda表示式沒有子表示式要被解釋。
對於它們,直譯器簡單地設定val暫存器的正確的值,然後繼續執行由continue暫存器指定的入口點。簡單表示式的解釋用如下的控制器程式碼執行:

ev-self-eval
  (assign val (reg exp))
  (goto (reg continue))
ev-variable
  (assign val (op lookup-variable-value) (reg exp) (reg env))
  (goto (reg continue))
ev-quoted
  (assign val (op text-of-quotation) (reg exp))
  (goto (reg continue))
ev-lambda
  (assign unev (op lambda-parameters) (reg exp))
  (assign exp (op lambda-body) (reg exp))
  (assign val (op make-procedure)
              (reg unev) (reg exp) (reg env))
  (goto (reg continue))

注意的是,ev-lambda如何使用uevn和 exp暫存器來儲存lambda表示式的引數與程式體,
為了讓它們能夠被傳遞給make-procedure 操作,結合著環境變數env.

*    解釋程式應用
一個程式應用是被一個包括了一個操作符和多個運算元的組合體指定的。操作符
是一個子表示式它的值是一個程式,並且運算元也是子表示式它的值是被應用的
程式的實際引數。元直譯器eval處理應用,通過遞迴呼叫它自身來解釋組合體
的每個元素,並且然後把結果傳遞給apply,由apply來執行實際的程式應用。顯式
控制的直譯器做相同的事情:這些遞迴呼叫通過goto指令來實現,結合著棧的使用
來儲存暫存器,在遞迴呼叫返回之後進行恢復暫存器。在每個呼叫之前,我們將
小心地標識著哪個暫存器必須被儲存(因為它們的值稍後將被需要用)

通過解釋操作符來生成一個程式,我們開始了一個應用的解釋過程,這個生成的程式將在
稍後的時候被應用到解釋過的運算元上。為了解釋操作符,我們把它移動到exp暫存器中,
並且來到eval-dispatch.為了解釋操作符,在env暫存器中的環境已經準備好相應的內容。
然而,我們儲存env,因為我們將在稍後的時候需要它解釋運算元。我們也抽取了運算元
進入unev並且儲存它到棧上。我們設定了continue,為了讓eval-dispatch在操作符被
解釋之後,將重返回到ev-app-did-operator.首先,然而,我們儲存continue的舊值,
它告訴控制器在應用之後,繼續執行到哪裡。

ev-application
  (save continue)
  (save env)
  (assign unev (op operands) (reg exp))
  (save unev)
  (assign exp (op operator) (reg exp))
  (assign continue (label ev-appl-did-operator))
  (goto (label eval-dispatch))

從解釋操作符的子表示式返回後,我們繼續解釋組合體的運算元,並且在一個列表中
累加結果性的實際引數,儲存在暫存器arg1中。首先,我們恢復了未解釋的運算元和
環境。我們初始化了arg1為一個空的列表。然後,我們給proc暫存器賦值為解釋
操作符而生成的程式。如果沒有運算元,我們直接返回到apply-dispatch.否則我們儲存
proc到棧上,並且開始了實際引數的解釋迴圈中:

ev-appl-did-operator
  (restore unev)                  ; the operands
  (restore env)
  (assign argl (op empty-arglist))
  (assign proc (reg val))         ; the operator
  (test (op no-operands?) (reg unev))
  (branch (label apply-dispatch))
  (save proc)

實際引數的解釋的迴圈中每次迴圈體從列表unev中解釋一個運算元,並且累加結果到arg1中。
為了解釋運算元,我們把它放在exp暫存器中,在設定了contiue之後(這是為了執行能夠返回到實際引數的累加階段),並且來到了eval-dispatch。但是首先我們儲存累加的實際引數(在arg1中),環境(在env中),要被解釋的其它的運算元(在unev中)為了解釋最後一個運算元
做了一個特例,它是用ev-appl-last-arg來處理的。

ev-appl-operand-loop
  (save argl)
  (assign exp (op first-operand) (reg unev))
  (test (op last-operand?) (reg unev))
  (branch (label ev-appl-last-arg))
  (save env)
  (save unev)
  (assign continue (label ev-appl-accumulate-arg))
  (goto (label eval-dispatch))

當一個運算元被解釋了,它的值就被累加到了列表arg1中。運算元從
未解釋的運算元的列表unev中刪除了,並且實際引數的解釋繼續著。

ev-appl-accumulate-arg
  (restore unev)
  (restore env)
  (restore argl)
  (assign argl (op adjoin-arg) (reg val) (reg argl))
  (assign unev (op rest-operands) (reg unev))
  (goto (label ev-appl-operand-loop))

最後一個實際引數的解釋被處理得有點不同。在去eval-dispatch之前,這不需要
儲存環境和未解釋的運算元的列表。因為在最後一個運算元被解釋後,這些將不再
需要了。因此,我們從解釋中返回到一個特殊的入口點ev-appl-accum-last-arg,它恢復
實際引數的列表,累加新的實際引數,恢復儲存的程式,並且返回來執行應用。

ev-appl-last-arg
  (assign continue (label ev-appl-accum-last-arg))
  (goto (label eval-dispatch))
ev-appl-accum-last-arg
  (restore argl)
  (assign argl (op adjoin-arg) (reg val) (reg argl))
  (restore proc)
  (goto (label apply-dispatch))

實際引數的解釋迴圈的細節確定瞭解釋器在解釋一個組合體中的運算元的順序
(例如,從左到右或者是從右到左 見練習3.8)這個順序不是被元直譯器確定的,
而是從scheme繼承了控制結構。因為第一個運算元的選擇子
(在ev-appl-operand-loop為了從unev中抽取連續的運算元而使用)被實現為取頭部,
其它的運算元們的選擇子被實現為取尾部,顯式控制的直譯器將解釋一個組合體中
運算元以從左到右的順序進行。

*    程式應用
入口點apply-dispatch 對應於元直譯器中的apply程式。在我們到達apply-dispatch的時候,
proc暫存器包含著要被應用的程式, arg1包含著程式必須被應用到的解釋過的實際引數的列表。
continue的儲存的值是在棧上的。(原是被傳遞給eval-dispatch,在ev-application中儲存的)它告訴apply-dispatch返回到哪裡,並且要帶著程式應用的結果。當應用完成時,控制器轉回到
由continue指定的入口點上,程式的結果放在val暫存器中。與元迴圈的apply一樣,這有兩個情形要考慮。被應用的程式或者是一個原生的程式或者是一個複合的程式。

apply-dispatch
  (test (op primitive-procedure?) (reg proc))
  (branch (label primitive-apply))
  (test (op compound-procedure?) (reg proc)) 
  (branch (label compound-apply))
  (goto (label unknown-procedure-type))

我們假定每個原生的程式被實現為從arg1中得到它的實際引數,把它的結果放在val中。
為了指定機器如何處理原生的程式,我們將不得不提供一個控制器指令的序列來實現
每個原生的程式,並且為primitive-apply安排分發的指令,根據proc的內容
來標識原生的程式。因為我們感興趣的是解釋的過程的結構而不是原生程式的細節,我們將
代替僅使用一個apply-primitive-procedure操作,來應用proc中的程式到arg1中的實際引數。
為了模擬直譯器的目的,使用5.2部分中的模擬器,我們使用程式apply-primitive-procedure,
它呼叫底層的scheme系統來執行程式,正如在4.1.4部分中的元迴圈的直譯器所做的那樣。在
計算了原生的程式的值後,我們恢復了continue,並且去到目標的入口點。

primitive-apply
  (assign val (op apply-primitive-procedure)
              (reg proc)
              (reg argl))
  (restore continue)
  (goto (reg continue))

為了應用一個複合的程式,我們繼續學習元迴圈的直譯器。我們組裝一個幀,
它把程式的引數與實際引數繫結,使用這個幀,來擴充套件程式帶著的環境變數,
並且在這個擴充套件的環境變數中解釋表示式的序列,這些表示式形成了程式的程式體。
Ev-sequence在如下的5.4.2部分中描述,處理序列的解釋。

compound-apply
  (assign unev (op procedure-parameters) (reg proc))
  (assign env (op procedure-environment) (reg proc))
  (assign env (op extend-environment)
              (reg unev) (reg argl) (reg env))
  (assign unev (op procedure-body) (reg proc))
  (goto (label ev-sequence))

compound-apply是直譯器中僅有的一個地方,它的env暫存器被賦一個新的值。正如
在元迴圈的直譯器中,新的環境從程式帶有的環境中組裝起來,帶著實際引數的列表,和被繫結的變數的相應的列表。