vimスクリプトで BrainF*ck

言語習得には BrainF*ckだね

ということで、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! を出力してることになる。

+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.
------------.<++++++++.--------.+++.------.--------.>+.

これを vimスクリプトで解釈させたい。かな。

まずはジャンプ命令は未実装で

まずは [ と ] のジャンプ命令は未実装にしよう。

最初の実装はこんなかんじ

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 だな。
これで動くけど、意味的には間違ってるな><


あと、入力ファイル最後まで処理するか
空行がきたら処理を終了します。