SICP 読み (246) 5.2 レジスタ計算機での計算

assemble は試験で動作が確認したい。てコトは machine か。make-new-machine が戻すオブジェクトの属性でアクセス可能なのは

  • pc
  • flag
  • stack
  • the-ops

あたりのナニ。でも machine があれば assemble できる。ただ assemble が戻すのはとても微妙なリストのはずなんですが。
gdgd 言ってないで試験書くか。
ってコトでとりあえず書いてみたのが以下。微妙。

  ("make-new-machine"
   ("machine"
    (let ((m (make-new-machine)))
      (assert-equal '*unassigned* (((m 'get-register) 'pc) 'get))
      (assert-equal '*unassigned* (get-contents ((m 'get-register) 'pc)))
      (assert-equal '*unassigned* (((m 'get-register) 'flag) 'get))
      (assert-equal '*unassigned* (get-contents ((m 'get-register) 'flag)))
      )
    )
   )

とりあえず、make-machine なソレを手動でヤッてみて中身確認してみるか。

(define gcd-machine
  (make-machine
   '(a b t)
   (list (list 'rem remainder) (list '= =))
   '(test-b
       (test (op =) (reg b) (const 0))
       (branch (label gcd-done))
       (assign t (op rem) (reg a) (reg b))
       (assign a (reg b))
       (assign b (reg t))
       (goto (label test-b))
     gcd-done)))

make-machine は以下の手続きなんで

(define (make-machine register-names ops controller-text)
  (let ((machine (make-new-machine)))
    (for-each (lambda (register-name)
                ((machine 'allocate-register) register-name))
              register-names)
    ((machine 'install-operations) ops)    
    ((machine 'install-instruction-sequence)
     (assemble controller-text machine))
    machine))

こんなカンジでしょうか。

   ("hand make-machine"
    (let ((m (make-new-machine)))
      (for-each (lambda (register-name)
		  ((m 'allocate-register) register-name))
		'(a b t))
      (assert-equal '*unassigned* (get-contents ((m 'get-register) 'a)))
      (assert-equal '*unassigned* (get-contents ((m 'get-register) 'b)))
      (assert-equal '*unassigned* (get-contents ((m 'get-register) 't)))

      (assert-error (lambda () ((m 'stack) 'pop)))
      (assert-error (lambda () (pop (m 'stack))))

      (assert-equal 'initialize-stack (car (car (m 'operations))))
      (assert-equal 'print-stack-statistics (car (cadr (m 'operations))))
      ((m 'install-operations) (list (list 'rem remainder) (list '= =)))
      (assert-equal 'rem (car (caddr (m 'operations))))
      (assert-equal '= (car (cadddr (m 'operations))))
      )
    )

assemble のあたりをどう確認していくか、が問題。あと make-* なソレ達をどう試験したものやら。どっちかというと make-* の方が先な気もしています。

make-* から見ていく事に。make-* は基本的に手続きを戻してて、update-insts! において insts なリスト要素の cdr にセットされているはず。てー事は戻った手続きは基本的には thunk のようなので呼び出して結果を見てみりゃ良いのか。
とりあえず make-assign から着手。5.1.5 節によれば assign の様式は以下な模様。

(assign (reg ))

(assign (const ))

(assign (op ) ... )

(assign (label ))

うーん。label がどういった形なのか、ですが

(label-name (以降の命令列))

になっているという理解なんだけど、これは先に試験で確認しておいた方が良さげ。ってか labels って machine の中にリストがあるって思ってたらそうではなかった。assemble 中にそのラベルを参照する命令がある時に、という事になるのか。
assemble の中の処理を微妙に変えて試験してみる、というのは手かも。

   ("label"
    (let ((m (make-new-machine))
	  (l '(test-b
	       (test (op =) (reg b) (const 0))
	       (branch (label gcd-done))
	       (assign t (op rem) (reg a) (reg b))
	       (assign a (reg b))
	       (assign b (reg t))
	       (goto (label test-b))
	       gcd-done)))
      (for-each (lambda (register-name)
		  ((m 'allocate-register) register-name))
		'(a b t))
      ((m 'install-operations) (list (list 'rem remainder) (list '= =)))
      (extract-labels l
		      (lambda (insts labels)
			(assert-equal 'test-b (car (car labels)))
			(assert-equal '(((test (op =) (reg b) (const 0)))
					((branch (label gcd-done)))
					((assign t (op rem) (reg a) (reg b)))
					((assign a (reg b)))
					((assign b (reg t)))
					((goto (label test-b))))
				      (cdr (car labels)))
			(assert-equal 'gcd-done (car (cadr labels)))
			(assert-equal '() (cdr (cadr labels)))
			))
      )
    )

これはこれは。最初 test-b なラベルの cdr に格納されているリストが意味不明でしたが、extract-labels でいっこづつ cons してる上に assemble した手続きを格納するための領域があるんだったよ、と。

((test (op =) (reg b) (const 0)) ())

みたいな感じ。で、これがリストになってるんで (以下略) ってコトですか。で、何やってるんだったか、というと make-assign から動作の確認、なんですが例えばこんな試験はどうか。

  ("make-assign"
   ("(assign <register-name> (reg <register-name>))"
    (let ((m (make-new-machine)))
      (for-each (lambda (register-name)
		  ((m 'allocate-register) register-name))
		'(a b t))
      ((m 'install-operations) (list (list 'rem remainder) (list '= =)))
      (set-contents! ((m 'get-register) 'a) 1)
      (assert-equal 1 (get-contents ((m 'get-register) 'a)))
      (let ((ope (make-assign '(assign b (reg a)) m '() (m 'operations)
			      ((m 'get-register) 'pc))))
	(ope)
	(assert-equal 1 (get-contents ((m 'get-register) 'b)))
	)
      )
    )
   )

駄目。

Error occurred in (assign <register-name> (reg <register-name>))
*** ERROR: pair required, but got *unassigned*

って叱られる。make-assign が吐き出す手続きは

      (lambda ()                ; execution procedure for assign
        (set-contents! target (value-proc))
        (advance-pc pc)))))

な形で最後に pc を先に進める形になっているんですが、上記試験の machine は空の pc しか持っていない事が原因であると思われます。無理矢理セットしてやるか。

  ("make-assign"
   ("(assign <register-name> (reg <register-name>))"
    (let ((m (make-new-machine)))
      (for-each (lambda (register-name)
		  ((m 'allocate-register) register-name))
		'(a b t))
      ((m 'install-operations) (list (list 'rem remainder) (list '= =)))
      (set-contents! ((m 'get-register) 'a) 1)
      (assert-equal 1 (get-contents ((m 'get-register) 'a)))
      (set-contents! ((m 'get-register) 'pc) '(() (gcd-done ())))
      (let ((ope (make-assign '(assign b (reg a)) m '() (m 'operations)
			      ((m 'get-register) 'pc))))
	(ope)
	(assert-equal 1 (get-contents ((m 'get-register) 'b)))
	)
      )
    )
   )

手続き引用が重複しとりますが勘弁して下さい。無理矢理 pc に

(() (gcd-done ()))

なリストを設定して試験パス。無理矢理すぎませんか、というツッコミはスルーで以降を検討してみる事に。てーか先に make-primitive-exp とか make-operation-exp とかの動作の確認が先かなぁ、と言いつつエントリ投入。

続々

という事でまず、make-primitive-exp から試験確認実施。作業がフォークし杉とゆー話もありますが、ログとってるから戻り先は大丈夫ッス。
で、試験ですがこの手続きも thunk を戻します。

  ("make-primitive"
   ("constant"
    (let ((m (make-new-machine)))
      (let ((ope (make-primitive-exp '(const 2) m '())))
	(assert-equal 2 (ope))
	)
      )
    )

   ("label"
    (let ((m (make-new-machine)))
      (let ((ope (make-primitive-exp '(label gcd-done) m '((gcd-done . ())))))
	(assert-equal '() (ope))
	(assert-error (lambda () (make-primitive-exp '(label test)
						     m
						     '())))
	)
      )
    )

   ("register"
    (let ((m (make-new-machine)))
      (for-each (lambda (register-name)
		  ((m 'allocate-register) register-name))
		'(a b t))
      (set-contents! ((m 'get-register) 'a) 1)
      (let ((ope (make-primitive-exp '(reg a) m '())))
	(assert-equal 1 (ope))
	(assert-error (lambda () (make-primitive-exp '(reg c)
						     m
						     '())))
	)
      )
    )
   )

で、make-primitive-exp な試験検討中に make-assign な試験のラベルな部分に不備を発見。ラベルのリスト要素は

      (set-contents! ((m 'get-register) 'pc) '(() (gcd-done . ())))

みたいな形になってないと駄目な模様。make-assign はどうして試験パスしたのかはよく分かりませんが、中身まで見てる訳ではなかったはず。あるいは以下が make-operation-exp な試験。渡す式を普通の scheme の (+ 1 2 3) みたいな式を渡して叱られた。

  ("make-operation-exp"
   ("+"
    (let ((m (make-new-machine)))
      (for-each (lambda (register-name)
		  ((m 'allocate-register) register-name))
		'(a b t))
      ((m 'install-operations) (list (list 'rem remainder) (list '+ +)))
      (set-contents! ((m 'get-register) 'a) 3)
      (set-contents! ((m 'get-register) 'b) 2)
      (let ((ope1 (make-operation-exp '((ope +) (const 1) (const 2))
				      m
				      '()
				      (m 'operations)))
	    (ope2 (make-operation-exp '((ope +) (const 1) 
					(const 2)
					(const 3) 
					(const 4)
					(const 5))
				      m
				      '()
				      (m 'operations)))
	    (ope3 (make-operation-exp '((ope +) (reg a) (reg b))
				      m
				      '()
				      (m 'operations))))
	(assert-equal 3 (ope1))
	(assert-equal 15 (ope2))
	(assert-equal 5 (ope3))
	(assert-error (lambda () (make-operation-exp '((ope *)
						       (const 2)
						       (const 3))
						     m
						     '())))
	(assert-error (lambda () (make-operation-exp '((ope +)
						       (reg c)
						       (reg d))
						     m
						     '())))
	)
      )
    )
   )

一応試験できているみたいですが lookup-prim も以下に。

  ("lookup-prim"
   ("lookup-prim"
    (let ((m (make-new-machine)))
      (for-each (lambda (register-name)
		  ((m 'allocate-register) register-name))
		'(a b t))
      ((m 'install-operations) (list (list 'rem remainder) (list '+ +)))
      (assert-error (lambda () (lookup-prim '* (m 'operations))))
      (assert-equal 2 (apply (lookup-prim '+ (m 'operations)) '(1 1)))
      )
    )
   )

なんかてきとーにヤッツケちゃってる感満点だなぁ。ただ、これで make-assign 方面に戻れるはず。