各命令に実行手続きを作り出す技法は, 4.1.7節で解析を実行時の処理から分離して速度をあげるのに使ったのと同じである. 4章で見たように, Scheme式の有用な解析の多くは, 変数の実際の値の知識なしに行える. ここでも同様で, レジスタ計算機言語の式の有用な解析の多くは, 計算機レジスタの実際の内容の知識なしに行える. 例えばレジスタの参照を, レジスタオブジェクトへのポインタで取り替え, またラベルの参照を, ラベルの指示する命令列の中の場所へのポインタで取り替えることが出来る.
命令実行手続きを生成する前に, アセンブラはすべてのラベルが何を参照するか知らなければならない. そこでラベルを命令から分離するため, 制御器の文書の走査から始める. 文書を走査しながら, 命令のリストと, 各ラベルをリストの中へのポインタと対応づける表を構成する. 次にアセンブラは, 各命令の実行手続きを挿入して, 命令リストを拡張する.
assemble手続きはアセンブラへの主要な入り口である. 引数として制御器の文書と計算機のモデルをとり, モデルに格納すべき命令列を返す. assembleはextract-labelsを呼び出し, 渡された制御器文書から, 最初の命令リストとラベル表を構築する. extract-labelsの第二引数は, これらの結果を処理するのに呼び出す手続きである: この手続きはupdate-insts!を使い, 命令実行手続きを生成し, それらを命令リストに挿入して, 修正したリストを返す.
(define (assemble controller-text machine) (extract-labels controller-text (lambda (insts labels) (update-insts! insts labels machine) insts)))
extract-labelsは引数としてリストtext(制御器の命令の式の列)と, receive手続きをとる. receiveは二つの値: (1)それぞれがtextの命令を含んでいる命令のデータ構造のリストinstsと(2)textの各ラベルを, リストinsts内のラベルが指示している位置と対応づけるlabelsという表で呼び出される.
(define (extract-labels text receive) (if (null? text) (receive '() '()) (extract-labels (cdr text) (lambda (insts labels) (let ((next-inst (car text))) (if (symbol? next-inst) (receive insts (cons (make-label-entry next-inst insts) labels)) (receive (cons (make-instruction next-inst) insts) labels)))))))extract-labelsはtextの要素を順に走査し, instsとlabelsを蓄積する. 要素が記号(つまりラベル)の時は, 適切な入り口を labels表に追加する. それ以外の要素はinstsリストに蓄積する.4
update-insts!は, 最初命令の文書を持っていただけの命令リストを, 対応する実行手続きを含むように修正する:
(define (update-insts! insts labels machine) (let ((pc (get-register machine 'pc)) (flag (get-register machine 'flag)) (stack (machine 'stack)) (ops (machine 'operations))) (for-each (lambda (inst) (set-instruction-execution-proc! inst (make-execution-procedure (instruction-text inst) labels machine pc flag stack ops))) insts)))
機械命令データ構造は, 命令文書を対応する実行手続きと対にするだけである. 実行手続きは, extract-labelsが命令を構成した時にはまだ使用可能ではなく, 後にupdate-insts!によって挿入される.
(define (make-instruction text) (cons text '())) (define (instruction-text inst) (car inst)) (define (instruction-execution-proc inst) (cdr inst)) (define (set-instruction-execution-proc! inst proc) (set-cdr! inst proc))われわれのシミュレータは命令文書を使わない. しかし虫とりのためには持ち回るのが便利である(問題5.16参照).
ラベル表の要素は対である:
(define (make-label-entry label-name insts) (cons label-name insts))項目は
(define (lookup-label labels label-name) (let ((val (assoc label-name labels))) (if val (cdr val) (error "Undefined label -- ASSEMBLE" label-name))))を使って表の中を探すことになる.
start (goto (label here)) here (assign a (const 3)) (goto (label there)) here (assign a (const 4)) (goto (label there)) thereこれまでに書いたシミュレータでは, 制御がthereに達した時レジスタa の内容はどうなるか. extract-labels手続きを修正し, 同じラベル名が二つの異る場所を指すように使われたら, エラーとするようにせよ.
4
ここのreceive手続きの使用は, extract-labelsに二つの値---
labelsとinsts---を, それらを保持する合成データ構造を陽には作らずに効率的に戻させる方法である. 値の対を陽に戻すもう一つの実装は,
(define (extract-labels text) (if (null? text) (cons '() '()) (let ((result (extract-labels (cdr text)))) (let ((insts (car result)) (labels (cdr result))) (let ((next-inst (car text))) (if (symbol? next-inst) (cons insts (cons (make-label-entry next-inst insts) labels)) (cons (cons (make-instruction next-inst) insts) labels)))))))これはassembleから次のように呼び出す:
(define (assemble controller-text machine) (let ((result (extract-labels controller-text))) (let ((insts (car result)) (labels (cdr result))) (update-insts! insts labels machine) insts)))receiveの使用を, 複数の値を返す優美な方法の例示, あるいは単にプログラムトリックを示す言い訳と考えてよい. 呼び出すべき次の手続きであるreceiveのような引数を「継続」という. 4.3.3節のamb評価器でのバックトラック制御構造の実装でも, 継続を使ったことを思い出そう.