Humanity

Edit the world by your favorite way

最近Vimでよくやるパースの方法

function! s:parse_args(q_args) "{{{
    let modes = ''
    let options = ''
    let lhs = ''
    let rhs = ''

    try
        let rest = a:q_args
        let rest = s:skip_white(rest)

        let [modes    , rest] = s:parse_modes(rest)
        let rest = s:skip_white(rest)

        let [options  , rest] = s:parse_options(rest)
        let rest = s:skip_white(rest)

        let [lhs, rest] = s:parse_lhs(rest)
        let rest = s:skip_white(rest)

        let [rhs, rest] = s:parse_rhs(rest)

        "if rest != ''
        "    throw 'error'
        "endif
    catch
        echoerr 'error'
    endtry

    return [modes, options, lhs, rhs, rest]
endfunction "}}}

だいたい

[パースした結果とか, 残りの文字列]

のような感じで残りの文字列を処理していく感じ。
それぞれの関数は正規表現で処理するなり手続き的に処理するなりいろいろと。


厳格にパースするならthinca/vim-vparsec · GitHubみたいなParsec風ライブラリ作って処理するなりすればいいんじゃないですかね。


おまけ

ユーティリティ関数晒してみる。

function! s:skip_white(q_args) "{{{
    return substitute(a:q_args, '^\s*', '', '')
endfunction "}}}

function! s:parse_one_arg_from_q_args(q_args) "{{{
    let arg = s:skip_white(a:q_args)
    let head = matchstr(arg, '^.\{-}[^\\]\ze\([ \t]\|$\)')
    let rest = strpart(arg, strlen(head))
    return [head, rest]
endfunction "}}}

function! s:eat_n_args_from_q_args(q_args, n) "{{{
    let rest = a:q_args
    for _ in range(1, a:n)
        let rest = s:parse_one_arg_from_q_args(rest)[1]
    endfor
    let rest = s:skip_white(rest)    " for next arguments.
    return rest
endfunction "}}}

s:skip_white()は単純に先頭の空白を削除する関数。
s:parse_one_arg_from_q_args()は.vimrcの中でもよく使う関数で、
から最初の文字列を取り出したい場合などに使う。

単純な引数のフォーマットの場合なら単にを使えばいいんだけど、
必要に応じて'や"で囲んだ文字列を自作Exコマンドに渡したい場合に

:MyExCommand foo 'my string' bar

のように起動すると

["foo", "'my", "string'", "bar"]

な感じで分断されて残念な感じになるためにパースが必要になる。*1



ユーティリティ関数はこれからもいろいろ.vimrcに追加していくと思う。

*1:シェルみたいに引用符みたいな特定のフォーマットをサポートしてくれればいいんだけど...Vimスクリプトはいちいちプリミティブすぎる