vimスクリプトで BrainF*ck - 再実装編

BFの8命令すべてに対応したよ

昨日のエントリーで不完全だった「ジャンプ命令」というか
[ と ] に対応してなかった件、対応してみた。
実装方針も、チューリングな感じにした。かな?


vimスクリプトはこんな感じ。
bf.vim という名前で。

$ 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:LB        = 7     " [
let s:RB        = 8     " ]
let s:UNKNOWN   = -1    " X

" variables
let s:lineno = 0
let s:lastno = 0

let s:instruction = ""  " instruction memory
let s:ip = 0            " instruction pointer
let s:stack = []        " stack memory
let s:stack_size = 256
let s:sp = 0            " stack pointer
let s:ax = 0            " register ax

function! s:BFInit()
    let s:instruction = ""
    let s:stack = []
    let s:ip = 0
    let s:sp = 0
    let s:ax = 0

    " load instruction
    while s:lineno <= s:lastno
        let s:instruction = s:instruction . getline(s:lineno)
        let s:lineno = s:lineno + 1
    endwhile

    " clear stack
    let l:i = 0
    while l:i < s:stack_size
        call add(s:stack, 0)
        let l:i = l:i + 1
    endwhile
endfunction


function! s:BFForward() " >
    let s:ip = s:ip + 1
    if s:sp >= s:stack_size - 1
        echo "Oops! out of range"       
        return
    endif
    let s:sp = s:sp + 1
endfunction


function! s:BFBack()    " <
    let s:ip = s:ip + 1
    if !s:sp
        echo "Oops! out of range"
        return
    endif
    let s:sp = s:sp - 1
endfunction


function! s:BFIncr()        " +
    let s:ip = s:ip + 1
    let s:stack[s:sp] = s:stack[s:sp] + 1
endfunction


function! s:BFDecr()        " -
    let s:ip = s:ip + 1
    let s:stack[s:sp] = s:stack[s:sp] - 1
endfunction


function! s:BFPutChar() " .
    let s:ip = s:ip + 1
    echo nr2char(s:stack[s:sp])
endfunction


function! s:BFGetChar() " ,
    let s:ip = s:ip + 1
    let l:char = input('input char : ')
    let s:stack[s:sp] = char2nr(l:char)
endfunction


function! s:BFWhile()   " [
    if s:stack[s:sp] == 0
        let l:dst = stridx(s:instruction, "]", s:ip)
        if l:dst == -1
            echo "syntax error : ] not found"
            return
        endif
        let s:ax = 0
        let s:ip = l:dst + 1
    else
        let s:ax = s:ip
        let s:ip = s:ip + 1
    endif
endfunction

function! s:BFEndWhile()    " ]
    let s:ip = s:ax
endfunction

" -----------------------------------

function! s:BFDebug()
    echo "ip  : " . s:ip
    echo "*ip : " . s:instruction[s:ip]
    echo "instruction length : " . strlen(s:instruction)
    echo "sp  : " . s:sp
    echo "*sp : " . s:stack[s:sp]
    echo "stack depth : " . len(s:stack)
    echo "ax  : " . s:ax
endfunction


function! s:BFLex()
    let l:char = strpart(s:instruction, s:ip, 1)

    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:LB
    elseif l:char == "]"
        return s:RB
    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:LB
            execute s:BFWhile()
        elseif  l:ch == s:RB
            execute s:BFEndWhile()
        else    
            break
        endif
    endwhile
endfunction

hello.bf を用意する

Hello, World! を出力するソースを用意します。
昨日のエントリーにも書いたけど、再掲。

$ cat hello.bf
+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.
------------.<++++++++.--------.+++.------.--------.>+.

BrainF*ckを実行!

じゃ、vimで BrainF*ckを実行しまっす。


vimでソースファイルを開いて

$ vim hello.bf

次のように実行すると...

:%call BFMain()


Hello, World!
でましたー。

H
e                                                                                                  
l
l
o
,
 
w
o
r
l
d
!
Press ENTER or type command to continue

今回の実装


今回は、わりとチューリングっぽく
s:instruction をテープに、s:ipを磁気ヘッドにみたてた動きにしました。
s:stack でデータをごにょごにょして
スタックポインタを s:sp ってことで。
空行があっても、止まらなくしてます。

ツッコミどころ

で、やっぱりツッコミどころは満載な感じです><


[ と ] は復帰アドレスというかジャンプ先を格納するために
汎用レジスタっぽく、s:ax を用意したのだけど
axにしてるあたりが中途半端なんですが><
それから、[ と ]の処理は
本来、レジスタじゃなくて、スタックにしないといけないけど
ま、[ と ] はネストしないでね、ということで、とりあえず。
あー、このへんの実装がやっぱ中途半端><


あと、トークンを用意してみたものの
ステートマシンとか用意してないし
先読みとかする必要もないから、あんま意味ねーし、、


ていうか、メソッド、みたいなところでトークンを使わずに
じかに "]" を使っててかえって汚い&バグのもと。
嗚呼、見れば見るほど中途半端。
関数名もあんまよくない。
あと、リエントラントな設計じゃない?とかもあるか。


あと、出力文字列用にプールを用意しといて
終了時に文字列プールを flushするようにすれば
きれいに Hello, World! が出るのだけど。


vimスクリプトを習得する、というのが目的ではあるのだけど
んー、どうかなぁ。
いいのかなぁ、こんなので。


つづく、かもしれない...。

追記

しかも、World じゃなくて world が出るプログラムだったよ><