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

追記するのは微妙みたいなので新たにエントリ起こす。make-test の次は make-branch からです。

make-branch

手続きの定義は以下。

(define (make-branch inst machine labels flag pc)
  (let ((dest (branch-dest inst)))
    (if (label-exp? dest)
        (let ((insts
               (lookup-label labels (label-exp-label dest))))
          (lambda ()
            (if (get-contents flag)
                (set-contents! pc insts)
                (advance-pc pc))))
        (error "Bad BRANCH instruction -- ASSEMBLE" inst))))

flag が真なら pc レジスタを書き換える、という操作で jmp を実装してます。何と申し上げれば良いか分かりませんが凄いな。でも試験書くの面倒だなぁ。extract-labels に渡す手続きの中に assert を書いてしまうか。
ええと、branch な構文は

(branch (label ))

との事。でっち上がったのが以下の試験。微妙スギ。

  ("make-branch"
   ("error"
    (assert-error (lambda () (make-branch '(branch (reg a)) '() '() '())))
    )

   ("flag is #t"
    (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)))
      (extract-labels 
       l
       (lambda (insts labels)
	 (set-contents! ((m 'get-register) 'flag) #t)
	 (let ((thunk (make-branch '(branch (label test-b))
				   m
				   labels
				   ((m 'get-register) 'flag)
				   ((m 'get-register) 'pc))))
	   (thunk)
	   (assert-equal '(test (op =) (reg b) (const 0))
			 (car (car (get-contents ((m 'get-register) 'pc)))))
	   )
	 ))
      )
    )


   ("flag is #f"
    (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)))
      (extract-labels 
       l
       (lambda (insts labels)
	 (set-contents! ((m 'get-register) 'flag) #f)
	 (set-contents! ((m 'get-register) 'pc) '(() (gcd-done ())))
	 (let ((thunk (make-branch '(branch (label test-b))
				   m
				   labels
				   ((m 'get-register) 'flag)
				   ((m 'get-register) 'pc))))
	   (thunk)
	   (assert-equal 'gcd-done
			 (car (car (get-contents ((m 'get-register) 'pc)))))
	   )
	 ))
      )
    )
   )

OK ってコトで次に進む。

make-goto

手続きの定義は以下。

(define (make-goto inst machine labels pc)
  (let ((dest (goto-dest inst)))
    (cond ((label-exp? dest)
           (let ((insts
                  (lookup-label labels
                                (label-exp-label dest))))
             (lambda () (set-contents! pc insts))))
          ((register-exp? dest)
           (let ((reg
                  (get-register machine
                                (register-exp-reg dest))))
             (lambda ()
               (set-contents! pc (get-contents reg)))))
          (else (error "Bad GOTO instruction -- ASSEMBLE"
                       inst)))))

なんか長いな。そうか、goto はラベルとレジスタが指定可能なのか。

(goto (label ))

(goto (reg ))

これも branch 式な試験が必要かなぁ。あそこまでせんでも良いように思うんですが。と言いつつ以下の試験をでっち上げたら

Error occurred in label
*** ERROR: unbound variable: false

って叱られた。

   ("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)))
      (extract-labels 
       l
       (lambda (insts labels)
	 (let ((thunk (make-goto '(goto test-b) m labels ((m 'get-register) 'pc))))
	   (thunk)
	   (assert-equal '(test (op =) (reg b) (const 0))
			 (car (car (get-contents ((m 'get-register) 'pc)))))
	   )
	 )
       )
      )
    )

goto の構文が違うのは良いんですが、false って何だよ、と。で、ch5-regsim.scm を false で grep したらあるでないの。探してみると tagged-list? でした。

; from 4.1
(define (tagged-list? exp tag)
  (if (pair? exp)
      (eq? (car exp) tag)
      false))

パクッたナニを使ってると駄目ですねぇ。(を

あるいは register な試験にて正常に lookup できぬ、という不具合が発現。

Error occurred in register
*** ERROR: Unknown register: (reg a)

こんなコトしてるし (とほほ

	       (ope (make-assign '(assign (reg a) (label test-b))
				 m
				 labels
				 (m 'operations)
				 ((m 'get-register) 'pc))))

紆余曲折を経て、一応以下の試験にパスしている模様。

  ("make-goto"
   ("error"
    (assert-error (lambda () (make-goto '(goto (op =))
					'()
					'()
					'())))
    (assert-error (lambda () (make-goto '(goto (const 2))
					'()
					'()
					'())))
    )

   ("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)))
      (extract-labels 
       l
       (lambda (insts labels)
	 (let ((thunk (make-goto '(goto (label test-b)) m labels ((m 'get-register) 'pc))))
	   (thunk)
	   (assert-equal '(test (op =) (reg b) (const 0))
			 (car (car (get-contents ((m 'get-register) 'pc)))))
	   )
	 )
       )
      )
    )

   ("register"
    (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))
      (extract-labels 
       l
       (lambda (insts labels)
	 (let ((thunk (make-goto '(goto (reg a)) m labels ((m 'get-register) 'pc)))
	       (ope (make-assign '(assign a (label test-b))
				 m
				 labels
				 (m 'operations)
				 ((m 'get-register) 'pc))))
	   (set-contents! ((m 'get-register) 'pc) '(() (gcd-done ())))
	   (ope)
	   (thunk)
	   (assert-equal '(test (op =) (reg b) (const 0))
			 (car (car (get-contents ((m 'get-register) 'pc)))))
	   )
	 )
       )
      )
    )
   )

段々試験が微妙になってる気がしてきた。無理矢理通してる感満点な感じ。

make-save

スタック操作は試験が楽そげ。手続きの定義は以下。

(define (make-save inst machine stack pc)
  (let ((reg (get-register machine
                           (stack-inst-reg-name inst))))
    (lambda ()
      (push stack (get-contents reg))
      (advance-pc pc))))

save の様式は以下ですか。

(save <register-name>)

これもレジスタの名前を直接記述か。stack の中身が直接見れないんだよなぁ。面倒臭いなぁ。このあたりまで来たら手続きオブジェクトの中身をなんたら、みたいなコトができるようになってたかたのですが、さすがに SICP にはそんなコトは書いていないのか。(何

試験としては push して次、という動作が確認できてれば OK かなぁ。

  ("make-save"
   ("make-save"
    (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 ((thunk (make-save '(save a)
			      m
			      (m 'stack)
			      ((m 'get-register) 'pc))))
	(assert-error (lambda () ((m 'stack) 'pop)))
	(set-contents! ((m 'get-register) 'pc) '(() (gcd-done ())))
	(thunk)
	(assert-equal '() (cadr (car (get-contents ((m 'get-register) 'pc)))))
	(assert-equal 1 ((m 'stack) 'pop))
	(assert-error (lambda () ((m 'stack) 'pop)))
	)
      )
    )
   )

なんと言えば良いか分かりませんが、pc の確認あたりがとても微妙。

make-restore

微妙と言いつつどんどん行く。make-restore の定義は以下。

(define (make-restore inst machine stack pc)
  (let ((reg (get-register machine
                           (stack-inst-reg-name inst))))
    (lambda ()
      (set-contents! reg (pop stack))    
      (advance-pc pc))))

あるいは restore の様式は以下。

(restore )

で、make-save な試験をパクッて作成した試験が以下。

  ("make-restore"
   ("make-restore"
    (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 ((push-thunk (make-save '(save a)
				   m
				   (m 'stack)
				   ((m 'get-register) 'pc)))
	    (pop-thunk (make-restore '(restore b)
				     m
				     (m 'stack)
				     ((m 'get-register) 'pc))))
	(assert-error (lambda () ((m 'stack) 'pop)))
	(set-contents! ((m 'get-register) 'pc) '(() (gcd-done ())))
	(push-thunk)
	(assert-equal '() (cadr (car (get-contents ((m 'get-register) 'pc)))))
	(set-contents! ((m 'get-register) 'pc) '(() (gcd-done ())))
	(pop-thunk)
	(assert-equal 1 (get-contents ((m 'get-register) 'b)))
	(assert-error (lambda () ((m 'stack) 'pop)))
	)
      )
    )
   )

一応パスはしております。残るは make-perform です。

make-perform

手続きの定義は以下。

(define (make-perform inst machine labels operations pc)
  (let ((action (perform-action inst)))
    (if (operation-exp? action)
        (let ((action-proc
               (make-operation-exp
                action machine labels operations)))
          (lambda ()
            (action-proc)
            (advance-pc pc)))
        (error "Bad PERFORM instruction -- ASSEMBLE" inst))))

様式は以下との事。

(perform (op ) ... )

むむ。どうやって試験したものやら、悩ましひ。こんなの書いて途中で止めてたり。

   ("make-perform"
    (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 '+ +)))
      (let ((thunk (make-perform '(perform (op +) (const 1) (const 2) (const 3))
				 m
				 '()
				 (m 'operations)
				 ((m 'get-register) 'pc))))
	(set-contents! ((m 'get-register) 'pc) '(() (gcd-done ())))
	(thunk)
	;; cannot assert
	)
      )
    )

確認できねぇじゃんかよ、みたいな。一応 make-operation-exp で確認してますのでスルーで勘弁して下さい (誰