vimスクリプトで BrainF*ck
BrainF*ck 言語仕様
まずは BrainF*ck の言語仕様
Brainfuck - Wikipedia
によると、次のようになる。
実行可能な命令は「8つ」のみである。
1. > ポインタをインクリメントする。ポインタをptrとすると、C言語の「ptr++;」に相当する。
2. < ポインタをデクリメントする。C言語の「ptr--;」に相当。
3. + ポインタが指す値をインクリメントする。C言語の「(*ptr)++;」に相当。
4. - ポインタが指す値をデクリメントする。C言語の「(*ptr)--;」に相当。
5. . ポインタが指す値を出力する。C言語の「putchar(*ptr);」に相当。
6. , 1バイトを入力してポインタが指す値に代入する。C言語の「*ptr=getchar();」に相当。
7. [ ポインタが指す値が0なら、対応する ] までジャンプする。C言語の「while(*ptr){」に相当。
8. ] ポインタが指す値が0でないなら、対応する [ にジャンプする。C言語の「}」に相当。
BrainF*ck で Hello, World! するには
http://ja.wikipedia.org/wiki/Hello_world%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E4%B8%80%E8%A6%A7#Brainfuck
によると、BrainF*ckで Hello, World! を出力してることになる。
+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-. ------------.<++++++++.--------.+++.------.--------.>+.
まずはジャンプ命令は未実装で
まずは [ と ] のジャンプ命令は未実装にしよう。
最初の実装はこんなかんじ
bf.vim って感じで作りました。
あとはジャンプ命令かな。
vim7.1.285 で試してるので vim6系だと実行できるか心配。
あんまりオシャレな組み込み関数は使ってないんだけど。
これを runtimepathが通ってるディレクトリ
たとえば pluginディレクトリに置いておきます。
$ cat bf.vim " define let s:GT = 1 " > let s:LT = 2 " < let s:PLUS = 3 " + let s:MINUS = 4 " - let s:DOT = 5 " . let s:CAMMA = 6 " , let s:LC = 7 " [ let s:RC = 8 " ] let s:UNKNOWN = -1 " X " variables let s:ptr = 0 let s:stack_size = 256 let s:array = [] let s:lineno = 0 let s:lastno = 0 let s:buf = "" function! s:BFInit() let s:buf = "" let s:array = [] let s:ptr = 0 let l:i = 0 while l:i < s:stack_size call add(s:array, 0) let l:i = l:i + 1 endwhile endfunction function! s:BFForward() " > if s:ptr >= s:stack_size - 1 echo "Oops! out of range" return endif let s:ptr = s:ptr + 1 endfunction function! s:BFBack() " < if !s:ptr echo "Oops! out of range" return endif let s:ptr = s:ptr - 1 endfunction function! s:BFIncr() " + let s:array[s:ptr] = s:array[s:ptr] + 1 endfunction function! s:BFDecr() " - let s:array[s:ptr] = s:array[s:ptr] - 1 endfunction function! s:BFPutChar() " . echo nr2char(s:array[s:ptr]) endfunction function! s:BFGetChar() " , let l:char = input('input char : ') let s:array[s:ptr] = char2nr(l:char) endfunction " ----------------------------------- function! s:BFDebug() echo "ptr = " . s:ptr echo "*ptr = " . s:array[s:ptr] echo "array length = " . len(s:array) endfunction function! s:BFLex() if s:buf == "" if s:lineno > s:lastno return s:UNKNOWN endif let s:buf = getline(s:lineno) let s:lineno = s:lineno + 1 endif let l:char = strpart(s:buf, 0, 1) if strlen(s:buf) == 1 let s:buf = "" else let s:buf = strpart(s:buf, 1, strlen(s:buf)-1) endif if l:char == ">" return s:GT elseif l:char == "<" return s:LT elseif l:char == "+" return s:PLUS elseif l:char == "-" return s:MINUS elseif l:char == "." return s:DOT elseif l:char == "," return s:CAMMA elseif l:char == "[" return s:LC elseif l:char == "]" return s:RC else return s:UNKNOWN endif endfunction " -------------------------------------- function! BFMain() range let s:lineno = a:firstline let s:lastno = a:lastline execute s:BFInit() while 1 let l:ch = s:BFLex() if l:ch == s:GT execute s:BFForward() elseif l:ch == s:LT execute s:BFBack() elseif l:ch == s:PLUS execute s:BFIncr() elseif l:ch == s:MINUS execute s:BFDecr() elseif l:ch == s:DOT execute s:BFPutChar() elseif l:ch == s:CAMMA execute s:BFGetChar() elseif l:ch == s:LC echo "LC : not impl." elseif l:ch == s:RC echo "RC : not impl." else " echo "X : terminate" break endif endwhile endfunction
ジャンプ命令がまだないので
hoge を出力するサンプルでいきまーす。
hoge.bf って感じでファイルを作ります。
てきとーに改行入れつつ。
$ cat hoge.bf ++++++++++++++++++++ ++++++++++++++++++++ ++++++++++++++++++++ ++++++++++++++++++++ ++++++++++++++++++++ ++++. +++++++. --------. --.
このファイルを vimで開いて、と
$ vim hoge.bf
次のように実行するとー
:%call BFMain()
こんな結果になりまーす。
h o g e Press ENTER or type command to continue
それなりには動いているらしい。
気になる点を、いくつか。
関数呼び出しに execute を使ってるけど
これでいいんだっけ?
stack と arrayの呼び方が、ごっちゃになってるけど
256バイトの配列の中で操作していく感じです。
(サイズにはあまり意味がない)
配列(リスト?)の初期化って、もっとうまくできる気がする。
あと、a:firstline と a:lastline をセットするのを
s:BFInit() に閉じ込めたかったけど
BFMain() からうまく引き継げなかったので、イマイチな感じです。
あと、switch文が欲しいなぁ、と思ったけど。
コールバックとかアレ系な実装ができたらいいな。
と思うけど、うまく実装する方法もあるはず。
vimスクリプトはわかってきたように思うけど
plugin化のセオリーとかは入れてないので、まだまだ。
なにはともあれ、ジャンプ命令を実装しないと
BrainF*ck にならないので、またやろう。
追記
s:LC と s:RC は s:LB と s:RB だな。
これで動くけど、意味的には間違ってるな><
あと、入力ファイル最後まで処理するか
空行がきたら処理を終了します。