Vim script でパターンマッチ

を書いてみた。
構文は Scala を参考に。

[ソース]

function! s:func(n)
    Match a:n
        Case 1 => echo "one"
        Case 2 => echo "two"
        Case 3 => echo "three"
    EndMatch
endfunction

function! s:typename(a)
    Match type(a:a)
        Case type(0)  => "integer"
        Case type("") => "string"
        Case type([])  => "list"
        Case _            => "other"
    EndMatch result
    return result
endfunction

function! s:fizzbuzz(n)
    Match [a:n%3 == 0, a:n%5 == 0]
        Case [1, 1] => "FizzBuzz"
        Case [1, _] => "Fizz"
        Case [_, 1] => "Buzz"
        Case _      => a:n
    EndMatch result
    return result
endfunction


function! s:main()
    call s:func(1)
    call s:func(2)
    call s:func(3)

    echo s:typename(1)
    echo s:typename("hoge")
    echo s:typename([1, 2, 3])
    echo s:typename({})
    echo s:typename(0.0)

    echo map(range(1, 20), "s:fizzbuzz(v:val)")
endfunction
call s:main()

[出力]

one
two
three
integer
string
list
other
other
[1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7, 8, 'Fizz', 'Buzz', 11, 'Fizz', 13, 14, 'FizzBuzz', 16, 17, 'Fizz', 19, 'Buzz']


見た目はだいぶすっきり。
コマンドで処理を展開しています。
コマンドなので s: は使えません。かなしい。


Scala みたいに直接値を返したいんですが、コマンドだと無理なので、EndMatch で結果を受け取るような構文になっています。
これも何とかしたいな。
関数の戻り値にする場合もいちいち return が必要だからなぁ…。

[実装]

let s:default_pattern = "all_match"

function! s:is_default(pattern)
    try
        return type(a:pattern) != type([]) && type(a:pattern) != type({}) && a:pattern =~ s:default_pattern
    catch
        return 0
    endtry
endfunction

function! s:is_match(case, pattern)
    try
        return (type(a:case) == type([]) ? count(map(copy(a:case), "s:is_default(a:pattern[v:key]) || v:val ==# a:pattern[v:key]"), 1) == len(a:case) : a:case =~ a:pattern)
    catch
        return 0
    endtry
endfunction

command! -nargs=1 Match
\   let match_src = <args>
\|  let is_matched = 0
\|  let _ = s:default_pattern

command! -nargs=? EndMatch
\   unlet match_src
\|  unlet is_matched
\|  unlet _
\|  if !empty(<q-args>) && has_key(l:, "match_result")
\|      let <args> = match_result
\|      unlet match_result
\|  endif


function! s:pattern(src)
    return matchstr(a:src, '\s*\zs.*\ze\s*=>.*')
endfunction

function! s:expr(src)
    return matchstr(a:src, '.*=>\s*\zs.*\ze\s*')
endfunction


command! -nargs=1 Case
\   if !is_matched && (s:is_default(eval(s:pattern(<q-args>))) || s:is_match(match_src, eval(s:pattern(<q-args>))))
\|      try
\|          execute "let match_result=" s:expr(<q-args>)
\|      catch
\|          execute s:expr(<q-args>)
\|      endtry
\|      let is_matched = 1
\|  endif


だいぶ適当なので Advent Calendar の記事が書き終わったら直すんだ…。
……と、いうかこれが Advent ネタでも(ゲフンゲフン