tester

自作 Lisp インタプリタ。関数の内部でグローバル変数を setq できないバグがあった。それを直すと再帰関数がおかしな値を返すようになった。変数への値の束縛のバグで、現在の環境で新しく束縛するか、上位の環境をたどって束縛するかの場合分けが必要だった。試行錯誤して今は正しく動作する、はず。


ようやくだが unit test フレームワークの tester を導入(http://opensource.franz.com/test/index.html)。こんな感じでテスト。

(defmacro with-toy ((&key debug) &body body)
  (let ((env (gensym))
	(form (gensym))
	(result (gensym)))
    `(let ((,env (make-env)))
       (loop for ,form in ',body for ,result = (eval-form ,env ,form)
	   do (when ,debug
		(format t ";; ~a~%" ,result))
	   finally (return ,result)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; test
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(with-tests (:name "math")
  (test 3 (with-toy () (+ 1 2)))
  (test 2 (with-toy () (- 3 1)))
  (test 8 (with-toy () (* 2 4)))
  (test 0 (with-toy () (- 3 (+ 1 2))))
  )

(with-tests (:name "simple-func")
  (test 4 (with-toy () (defun f(x) (+ x x)) (f 2)))
  (test 6 (with-toy () (setq x 3) (defun f(x) (+ x x)) (f x)))
  (test 7 (with-toy () (setq x 2) (defun f(y) (setq x (+ y x))) (f 5) x))
  (test 55 (with-toy ()
	     (defun fib (n)
	       (if (< n 2)
		   n
		 (+ (fib (- n 1)) (fib (- n 2)))))
	     (fib 10)))
  (test 120 (with-toy ()
	      (defun fact (n)
		(if (= n 1)
		    1
		  (* n (fact (- n 1)))))
	      (fact 5)))
  (test 8 (with-toy ()
	    (defun f (x) (* x 2))
	    (setq x 1)
	    (defun g (x) (+ x 3))
	    (f (g x))))
  (test 5 (with-toy ()
	    (defun h (x y) (- (* 2 x) (* y y)))
	    (h 7 3)))
  )

せいぜい200行程度のインタプリタだけど、関数もマクロもある。動きを手で追うことはできるけど、組み合わせとして動作するととても複雑で、自分の手から離れて勝手に動いているような不思議な感覚だ。言語処理系ってのは面白いもんだ。10年前にやっておくべきだった。


もう少しマクロ周りが整理されたら、回り道をしたがスタックマシンへのコンパイラマシン語へのコンパイラに進もう。
インタプリタでも楽しすぎるほどだが、コンパイラになるともう一段違う楽しさになるに違いない。

おっと、let とクロージャーのことを忘れていた。それから lambda リストのことも。

  • let は新しく環境を作って値を束縛。多分 ok.
  • クロージャー。
    • let 内で関数を定義した場合、トップレベルの環境に関数本体が束縛される。自前インタプリタはそうなっておらず間違っている。
    • 単なる関数とクロージャーの違い。関数が環境を一緒に持つ必要がある。今の理解だと、トップレベルで定義されているかどうかをチェックし、そうでない場合は環境を関数と一緒に持つ。
    • よって、今は関数は唯のリストだけど、環境を一緒に持てるようにする。