Brainf*ck(bf) Interpreter
追記;
以下のインタプリターはあまりに素朴すぎることに気づいた。静的に決まる情報をぜんぜん利用できていないのだ。
さらに悪いことに、インタプリターがすべき仕事とそれ以外ですべき仕事が明確に分かれていない。
いろいろな人のコードを見たつもりだったが何も見えていなかった。Brainf*uck Compiler-Interpreter に続く。
どう書く?org の問題より。
お題は Brainf*ck (bf) コンパイラーを作るというもの。
bf は名前は何だが、シンプルなプログラミング言語で、言語処理系の入門には良い(かもしれない)。
とっくに美しい Common Lisp の回答が出ているが、
(http://ja.doukaku.org/comment/3956/)
勉強としてまずインタプリターを書いてみる。インタプリターは面白くて、
- ANSI Common Lisp などの Lisp で Lisp を書く例
- Practical Common Lisp の HTML インタプリター/コンパイラーの例
- 他の bf 処理系
などが(あとで勉強しなおす例として)ある。将来的には
- mini-python の実装(http://www.logos.ic.i.u-tokyo.ac.jp/lectures/enshu2007/index.php?%B9%D6%B5%C1%BB%F1%CE%C1)
- CASL II を Common Lisp で書く
とかできると、コンピューターの知識が深まると期待している。
Brainf*ck Interpreter としては、適当な長さのメモリー配列、メモリー配列の位置を示すポインタ(ptr)、
bf のプログラム、bf のプログラムの位置を示すポインタ(pc)を持たせた CLOS のクラスを定義する。
用語が正確か自信が無いがこのクラスを vm とよんでおく。
クラスを定義したため、実行途中のメモリーの状態を表示したり(bf のデバッグなぞどう考えてもしたくないが、、)しやすいと思う。bf はプログラム中でジャンプ[]があるため、pc が必要?
- ><+-. は処理をした後 pc を1増やす。
- [] は pc を増減させるか、または、1増やす。
- pc がプログラムの長さを超えたら終了。
後は定義通りに実装し、一応インタプリタとしては動くようになった。
(interpret ">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-]<.>+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]>++++++++[<++++>-]<+.[-]++++++++++." nil) => Hello World! eof
Hello World! を出力する上記 bf プログラムは、ちょっと追いかける気にはなれないが、pc がループしながら動作していることが分かる。
(defclass vm () ((ptr :accessor ptr :initform 0) (mem :accessor mem :initform (make-array 2000 :initial-element 0)) (prg :accessor prg :initform "") (pc :accessor pc :initform 0))) (defmethod print-object ((vm vm) stream) (print-unreadable-object (vm stream) (with-accessors ((ptr ptr) (mem mem) (prg prg) (pc pc)) vm (format stream "pc: ~a ptr: ~a *ptr: ~a" pc ptr (aref mem ptr))))) (defmethod init-vm ((vm vm) program) (setf (ptr vm) 0) (setf (mem vm) (make-array 2000 :initial-element 0)) (setf (prg vm) program) (setf (pc vm) 0)) (defun interpret (program) "interpret bf program" (let ((vm (make-instance 'vm))) (init-vm vm program) (interpret-iter vm t))) (defmethod interpret-iter ((vm vm) &optional debug) (when debug (format t "vm: ~a" vm)) (with-accessors ((ptr ptr) (mem mem) (prg prg) (pc pc)) vm (cond ((>= pc (length prg)) 'eof) (t (let ((op (aref prg pc))) (setf pc (ecase op ((#\> #\< #\+ #\- #\.) (ecase op (#\> (incf ptr)) (#\< (decf ptr)) (#\+ (incf (aref mem ptr))) (#\- (decf (aref mem ptr))) (#\. (format t "~a" (code-char (aref mem ptr))))) (1+ pc)) (#\[ (if (= 0 (aref mem ptr)) (position #\] prg :start pc) (1+ pc))) ;; ignore nested [] (#\] (if (not (= 0 (aref mem ptr))) (position #\[ prg :from-end t :end pc) (1+ pc))))) (interpret-iter vm debug))))))
次は、マクロと組み合わせてコンパイラを作ればいいのか。
いろいろな人の実装をさんざん眺めた後だが、手を動かすことで少し賢くなった気がする。
(続く)