2014年02月15日

やったー!Vimでヒアドキュメント実装出来たよー!

この記事は、「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:commandRHSの中に押し込むことが出来ない。

結果

出来上がったのが以下のコード。

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に含めることさえ出来れば……

ああ、やっぱり、正式な言語機能として欲しいなぁ……

参考文献

posted by 天井冴太 at 15:00| Comment(0) | TrackBack(0) | Tool | 更新情報をチェックする
この記事へのコメント
コメントを書く
コチラをクリックしてください

この記事へのトラックバック