読者です 読者をやめる 読者になる 読者になる

VimScriptでbrainfuck処理系

VimScriptのお勉強も兼ねて.

.vim/pluginディレクトリに入れて,.vimrcに

nnoremap <C-i><C-f> :<C-u>call<Space>Bfrun_current_buffer()<CR>

と書けば,Ctrl-i Ctrl-fと打つだけで,今開いているバッファがbrainfuck
インタプリタで実行され,出力が新しいバッファとして開かれるようになります.(quickrunみたいな感じ)

結構うんこーどなので,変なところあったら言ってください.

brainfuck.vim

" 対応するかぎかっこの位置を
" 辞書型として返す
function! s:Bfjumplist(program)
  let jumplist = {}
  for i in range(strlen(a:program))
    if a:program[i] ==# '['
      let j = 0
      let depth = 0
      while 1
        if a:program[i+j] ==# '['
          let depth += 1
        elseif a:program[i+j] ==# ']'
          let depth -= 1
          if depth ==# 0
            let jumplist[i] = i+j
            break
          endif
        endif
        let j+=1
      endwhile
    elseif a:program[i] ==# ']'
      let j = 0
      let depth = 0
      while 1
        if a:program[i+j] ==# '['
          let depth += 1
          if depth ==# 0
            let jumplist[i] = i+j
            break
          endif
        elseif a:program[i+j] ==# ']'
          let depth -= 1
        endif
        let j-=1
      endwhile
    else
    endif
  endfor

  return jumplist
endfunction

" BF実行関数
" 戻り値は出力
function! g:Bfrun(program, input)
  " プログラムカウンタ
  let pc = 0
  let next_pc = 0
  " メモリ
  let mem = map(range(30000), 0)
  " メモリのポインタ
  let ptr = 0
  " ジャンプリスト
  let jumplist = s:Bfjumplist(a:program)
  " 出力
  let output = ''

  while pc !=# len(a:program)
    if a:program[pc] ==# '>'
      let ptr += 1
      let next_pc = pc + 1
    elseif a:program[pc] ==# '<'
      let ptr -= 1
      let next_pc = pc + 1
    elseif a:program[pc] ==# '+'
      let mem[ptr] += 1
      let next_pc = pc + 1
    elseif a:program[pc] ==# '-'
      let mem[ptr] -= 1
      let next_pc = pc + 1
    elseif a:program[pc] ==# '.'
      let output = output.nr2char(mem[ptr])
      let next_pc = pc + 1
    elseif a:program[pc] ==# ','
      let mem[ptr] = char2nr(a:input[0])
      let a:input = a:input[1:]
      let next_pc = pc + 1
    elseif a:program[pc] ==# '['
      if mem[ptr]
        let next_pc = pc + 1
      else
        let next_pc = jumplist[pc]+1
      endif
    elseif a:program[pc] ==# ']'
      let next_pc = jumplist[pc]
    else
      let next_pc = pc + 1
    endif
    let pc = next_pc
  endwhile

  return output
endfunction

" カレントバッファをソースコードとしてBfrunを呼び出す関数
" 結果は画面を立て分割して表示される
function! Bfrun_current_buffer()
  let output = g:Bfrun(join(getline("1", "$"), ""), "")
  vsplit newfile
  call setline(1, split(output, "\n"))
endfunction

おまけ

brainfuckからcのトランスレータも書いた

nnoremap <C-i><C-f> :<C-u>call<Space>Bf2c_current_buffer()<CR>
function! g:Bf2c(program, input)
  " インデントの深さ
  let depth = 0
  " 出力
  let output = ''
  " ヘッダ
  let output .= repeat("\t", depth)."#include <stdio.h>\n"
  let output .= repeat("\t", depth)."int main(void)\n"
  let output .= repeat("\t", depth)."{\n"
  let depth += 1
  " メモリ
  let output .= repeat("\t", depth)."int mem[30000] = {0};\n"
  " メモリのポインタ
  let output .= repeat("\t", depth)."int ptr = 0;\n"
  " 入力文字バッファ
  let output .= repeat("\t", depth)."unsigned char c;\n"

  let i = 0
  while i < strlen(a:program)
    if a:program[i] ==# '>'
      let cnt = 0
      while a:program[i] ==# '>'
        let cnt += 1
        let i += 1
      endwhile
      let output .= repeat("\t", depth)."ptr += ".cnt.";\n"
    elseif a:program[i] ==# '<'
      let cnt = 0
      while a:program[i] ==# '<'
        let cnt += 1
        let i += 1
      endwhile
      let output .= repeat("\t", depth)."ptr -= ".cnt.";\n"
    elseif a:program[i] ==# '+'
      let cnt = 0
      while a:program[i] ==# '+'
        let cnt += 1
        let i += 1
      endwhile
      let output .= repeat("\t", depth)."mem[ptr] += ".cnt.";\n"
    elseif a:program[i] ==# '-'
      let cnt = 0
      while a:program[i] ==# '-'
        let cnt += 1
        let i += 1
      endwhile
      let output .= repeat("\t", depth)."mem[ptr] -= ".cnt.";\n"
    elseif a:program[i] ==# '.'
      let output .= repeat("\t", depth)."printf(\"%c\", mem[ptr]);\n"
      let i += 1
    elseif a:program[i] ==# ','
      let output .= repeat("\t", depth)."scanf(\"%c\", &mem[ptr]);\n"
      let i += 1
    elseif a:program[i] ==# '['
      let output .= repeat("\t", depth)."while(mem[ptr]){\n"
      let depth += 1
      let i += 1
    elseif a:program[i] ==# ']'
      let depth -= 1
      let output .= repeat("\t", depth)."}\n"
      let i += 1
    else
      let i += 1
    endif
  endwhile

  " フッタ
  let depth -= 1
  let output .= repeat("\t", depth)."}\n"

  return output
endfunction

" カレントバッファをソースコードとしてBf2cを呼び出す関数
" 結果は画面を立て分割して表示される
function! Bf2c_current_buffer()
  let output = g:Bf2c(join(getline("1", "$"), ""), "")
  vsplit newfile
  call setline(1, split(output, "\n"))
endfunction