この記事は、「Vim Advent Calendar 2013」の第77日目、通算2回目の担当記事である。
なお、昨日はosyo-manga(@manga-osyo)さんの「Vim Advent Calendar 2013: KaoriYa 版 Vim の +migemo を試してみた」だった。
更に、私のVAC2013担当記事1つめは「closesomewindow.vim – 条件に合うウィンドウを閉じる Vim plugin」だった。
最初に言っておく。
ごめんなさい。
ヒアドキュメントとは何か?
シェルスクリプトやその他大体のスクリプト言語に実装されている、以下のような機能のことである。
var = <<EOS
↑こういう風に、
"<<" + 識別子
という形式で書いたら、
次に識別子と同じ文字列が現れるまでの、
全ての行の内容を、
一つの文字列として取得出来る
↓この"EOS"までがヒアドキュメント
EOS
実装の前提となる知識
- Vim script にはヒアドキュメント機能がない。
- 一部に「それっぽい」書き方が出来るコマンドはある。他言語インタフェイス関係がそれ(
:perl
、:ruby
等)。 - 関数は呼び出されない限り中の文法チェックは行われない。
- 「
:function FUNCTION_NAME
」で、「行番号付きで」関数定義を見る事が出来る。 :function
は:execute
や:command
の中に押し込むことが出来る(「:execute "function Foo()"
」とか「:command Bar function BarFunc()
」)。:endfunction
は:execute
や:command
のRHSの中に押し込むことが出来ない。
結果
出来上がったのが以下のコード。
function! s:SID()
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
endfunction
function! s:setOption(name, newVal)
let varName = '&' . a:name
let bak = eval(varName)
execute 'set' a:name . '=' . escape(a:newVal, ' \')
return bak
endfunction
function! s:parseCommandArg(args)
let pVarName = '%(\w:)?\w{-}'
let pRegName = '\@.'
let pTargetName = pVarName . '|' . pRegName
let pOp = '\W+'
let pMarker = '\w+'
let pattern = '\v^(' . pTargetName . ')' . pOp . '(' . pMarker . ')$'
return matchlist(a:args, pattern)[1:2]
endfunction
command! -nargs=1 HereDocFunction execute s:heredocfunction(<q-args>)
function! s:heredocfunction(args)
let sid = s:SID()
let [varName, marker] = s:parseCommandArg(a:args)
let tmpfuncname = '<SNR>' . sid . '_heredocfunction_tmp'
execute 'command! HereDocAssign let' varName '= <SNR>' . sid . '_get("' tmpfuncname "\") | delfunction" tmpfuncname
return 'function!' . tmpfuncname . "()\nperl <<" . marker
endfunction
function! s:get(funcname)
" backup 'listchars' because effect to ':function''s output.
let listchars_bak = s:setOption('listchars', 'tab: ')
redir => str
execute 'silent function' a:funcname
redir END
let &listchars = listchars_bak
let lineList = split(str, "\n")
let lineNumberWidth = strlen(matchstr(lineList[0], '\v^\s+'))
let res = ''
for i in range(2, len(lineList)-3) " 'perl << MARKER' to 'MARKER'
" Remove line number columns
let res .= strpart(lineList[i], lineNumberWidth, strlen(lineList[i])) . "\n"
endfor
" Remove last line-break char.
let res = strpart(res, 0, strlen(res) - 1)
return res
endfunction
そして、以下が使い方。
" g:vernameにヒアドキュメントの内容を代入
HereDocFunction g:varname << EOS
なんとか
かんとか
うんとか
すんとか
"<<"の部分は、正規表現「\v\W+」にマッチすれば何でも良いです。
おしょーさんの作った物( http://d.hatena.ne.jp/osyo-manga/20140120/1390220933 )と異なり、「:end~」が入っても問題ない。
endfunction
↑でも大丈夫
EOS
endfunction
" 以下を実行して初めて代入される
HereDocAssign
うん、キモい。
キモ過ぎる。
ヒアドキュメントの終わりに、3行費やさないといけないとか酷すぎる。
しかも、:HereDocFunction
って。ヒアドキュメントは関数じゃねーよというツッコミが今にも聞こえてきそうだ。:endfunction
と合わせる為の措置とは言え。:endfunction
を:command
や:execute
に含めることさえ出来れば……
ああ、やっぱり、正式な言語機能として欲しいなぁ……
参考文献
- Vim script でヒアドキュメント…を書きたかった - C++でゲームプログラミング
- Big Sky :: vim script でヒアドキュメント
- Vim script で end 系のコマンドを :execute できない理由 - C++でゲームプログラミング