この記事はVim Advent Calendar 2014の20日目の記事です。
19日目はthleapさんによるマークアップや文章の編集をちょっと楽にするでした。
導入
みなさん、正規表現、使ってますか? 私は毎日使っています。 特にVimの正規表現、かなり変態で良さがありますよね。
ところでPerlには、Regexp::Assembleという非常に便利なモジュールがあります。 この子は何をする子かというと、自動で最適化された正規表現を生成してくれます。 正規表現のマッチングでは、うまくトライ木を作ることができれば、かなりマッチング速度が向上しますので、自動でそれをやってくれるモジュールと言えばわかりやすいでしょうか。 (いわゆるtrie optimization)
例えば、”public”、”protected”、”private”という3つの単語についてマッチする正規表現は、
純粋にpublic|protected|private
と書けば良いのですが、これだと遅いので(?:p(?:ublic|r(?:otected|ivate)))
と書いたほうが速いので、こう書きましょうよってことです。
ここから本題。 Regexp::Assembleのように、自動で正規表現を最適化するためには、まず正規表現を切り刻まなければなりません。 この記事では、正規表現を切り刻んで遊んでみることにします。
下準備
正規表現を切り刻む、Vim scriptコードを用意します。
以下を手元で実行し、RegexpTokenize
コマンドを使えるようにしてください。
(簡単のため、一部の正規表現にしか対応させてません。)
function! s:tokenize(expr)
let [chars, i]= [split(a:expr, '\zs'), 0]
let tokens= []
while i < len(chars)
let ch= chars[i]
if ch ==# '\'
if !(i < len(chars))
break
endif
let i+= 1
let ch= chars[i]
if ch ==# '+'
let tokens+= ['\+']
elseif ch ==# '=' || ch ==# '?'
let tokens+= ['\' . ch]
elseif ch ==# '('
let tokens+= ['\(']
elseif ch ==# ')'
let tokens+= ['\)']
elseif ch ==? 'm' || ch ==? 'v'
let tokens+= ['\' . ch]
elseif ch ==? 'c'
let tokens+= ['\' . ch]
elseif ch ==? 'i'
let tokens+= ['\' . ch]
elseif ch ==? 'k'
let tokens+= ['\' . ch]
elseif ch ==? 'f'
let tokens+= ['\' . ch]
elseif ch ==? 'p'
let tokens+= ['\' . ch]
elseif ch ==? 's'
let tokens+= ['\' . ch]
elseif ch ==? 'd'
let tokens+= ['\' . ch]
elseif ch ==? 'x'
let tokens+= ['\' . ch]
elseif ch ==? 'o'
let tokens+= ['\' . ch]
elseif ch ==? 'w'
let tokens+= ['\' . ch]
elseif ch ==? 'h'
let tokens+= ['\' . ch]
elseif ch ==? 'a'
let tokens+= ['\' . ch]
elseif ch ==? 'l'
let tokens+= ['\' . ch]
elseif ch ==? 'u'
let tokens+= ['\' . ch]
else
throw printf("Unsupported regex: `%s'", a:expr)
endif
else
let tokens+= [ch]
endif
let i+= 1
endwhile
return tokens
endfunction
command!
\ -nargs=1
\ RegexpTokenize
\ echo s:tokenize(<q-args>)
切り刻め!
以下のコマンドを実行してみましょう。 切り刻まれ、正規表現単位に分割された文字列が表示されます。
" => ['h', 'o', 'g', 'e', '\(', '\w', '\+', '\)', '\?']
RegexpTokenize hoge\(\w\+\)\?
" => ['p', 'u', 'b', 'l', 'i', 'c']
RegexpTokenize public
" => ['\d', '\+']
RegexpTokenize \d\+
" => ['\d', '\+', '\(', ',', '\d', '\+', '\)', '\=']
RegexpTokenize \d\+\(,\d\+\)\=
最後に
この記事で書いたようなことをするライブラリとして、vim-regexp-assembleというものを作成しております。 切り刻みたいだけなら、Vital.Regexp.Lexerを使えばそれで終わってしまいますし、この記事に記載した一部の正規表現だけでなく、Vimの正規表現すべてに対して対応しています。
(help整備したらvital本家に入れてもらうかもしれませんが、ちょっと先の話になりそう。)
みなさんもっと正規表現で遊びましょう。 ということで、20日目の記事を終わりたいと思います。 明日はmitsuseさんです。