学習計画 3-1 でリファクタリング
学習計画 3-1 でリファクタリングしてみた。
注意していないと自分が vm を書いているつもりなのか、ただの Lisp の関数を書いているのか、マクロを書いているのか分からなくなりそうだ。
ちょっと考えかたを変えて、コンパイラはS式を vm の命令のみで作られた関数(ラムダ式)に変換することにしてみた。こうすると実行時は funcall で関数を呼びだすだけになる。PC(プログラムカウンタ)の増加はどこでやるかまだちょっとあやしい。
今の作りを整理すると、
- スタックマシンに対する命令は defmethod で定義し、そこでは最小限の演算だけ使う。PCの操作含む。
- コンパイラはS式を vm の命令だけを使う関数の列(コード)に変換する。
- 実行時は無限ループで淡々と関数を実行する。
- todo: PC がコードの範囲外を指したら終了。(格好悪い)
結局コンパイラがやることというのは、複雑な命令を単純な命令の組み合わせへ変換する、単なる式の変形にすぎないのだろうか。
だとするとそれは Lisp のマクロがやっていることだよなぁ。
勉強になっているようななっていないような不可思議な気分だ。
対話的に vm を動かせると理解しやすいかと期待していたが、そもそも CLOS を使う必要など無かったかも…。
(defun compile-sexp (sexp) (cond ((null sexp) nil) ((numberp sexp) (list (lambda (vm) (vm-push vm sexp)))) ((and (consp sexp) (= 3 (length sexp))) (let ((fn (ecase (car sexp) (+ #'vm-add) (- #'vm-sub) (* #'vm-mul) (> #'vm-gt) (< #'vm-lt) ;; todo. ))) (append (append (compile-sexp (second sexp)) (compile-sexp (third sexp))) (list fn)))) (t (error "unexpected sexp:~a" sexp)))) (defun exec (sexp) (format t ";; expect: ~a" (eval sexp)) (let ((vm (make-instance 'vm))) (let ((codes (concatenate 'vector (compile-sexp sexp)))) (setf (codes vm) codes) (with-slots (pc fp codes) vm (loop while (< pc (length codes)) ;; 無限ループ do (funcall (aref codes pc) vm)) ;; 実行 vm))))