Vim script でコルーチンな処理を考えてみた

元々 Vim script でスレッド処理が行いたかったのですが、残念ながらどうあがいてもそれは無理なので
『それならばコルーチンを使って細かく処理を分ければいいのでは?』
といったことが事がきっかけ。
まぁコルーチン自体は使ったことがないんですが。
で、いろいろと構文を考えてみたんですけど、現状はこんな感じ。

[使い方]

function! s:func()
    Coroutine

    Echo "hello"
    Yield

    Echo ","
    Yield

    Echo "world"
    Yield

    Echo "end"
endfunction

function! s:test_func()
    call s:func()
    call s:func()
    call s:func()
    call s:func()
    call s:func()
endfunction
call s:test_func()


function! s:factorial()
    Coroutine

    Let n = 1
    Let val = 1

    while 1
        Let val = val * n
        Let n += 1
        Yield val
    endwhile
endfunction


function! s:test_fact()
    for n in range(10)
        echo s:factorial()
    endfor
endfunction
call s:test_fact()

[出力]

hello
,
world
end
end
1
2
6
24
120
720
5040
40320
362880
3628800


Vim script だとコンテキストの保存や復元は簡単なんですけど、スキップ命令がないので全部 if 文で囲っています。
Let や Echo を使用しているのはその為。
ここら辺もどうにか簡単に出来ないか考えてみたんですけど、これが一番マシかなーと。


ちなみにこんな案もありました。

function! s:func()
    Coroutine
    
    echo "hello"
    en|Yield"

    echo ","
    en|Yield

    echo ","
    en|Yield
    en
endfunction


function! s:factorial()
    Coroutine

    let n = 1
    let val = 1

    en | Yield

    while 1
        let val = val * n
        let n += 1
        YieldLoop val
    endwhile
    en
endfunction


こっちは広い範囲を if 文で囲っているんですが、en とか余計なものを書く必要が…。
今見るとやっぱり酷い。
とりあえず、せっかく考えてみたのでどこかで使ってみたい。
あと for 文は未対応(ってか、対応している構文の方が少ない気が。

[ソース]

let s:context = {}

function! g:context_reset()
	let s:context = {}
endfunction

function! s:context_save(l, id)
	let s:context[a:id] = deepcopy(a:l)
endfunction

function! s:context_load(l, id)
	if has_key(s:context, a:id)
		call extend(a:l, s:context[a:id])
	endif
endfunction

function! s:context_reset(id)
	if has_key(s:context, a:id)
		unlet s:context[a:id]
	endif
endfunction


command! -bar ContextSave  :call s:context_save(l:, expand("<sfile>"))
command! -bar ContextLoad  :call s:context_load(l:, expand("<sfile>"))
command! -bar ContextReset :call s:context_reset(expand("<sfile>"))


command! -bar Coroutine
\   let suspend_line = 0
\|  ContextLoad

command! -nargs=* Suspend
\   let suspend_line = expand("<slnum>")
\|  ContextSave
\|  return <args>

command! -nargs=* Skipper
\   if expand("<slnum>") > (suspend_line+1)
\|  	execute <q-args>
\|  endif

command! -nargs=* Yield
\   execute "Skipper Suspend ".<q-args>
\|  if expand("<slnum>") == (suspend_line)
\|  	let suspend_line = 0
\|  endif


command! -nargs=* Echo
\   Skipper echo <args>

command! -nargs=* Let
\   Skipper let <args>


最近、関数定義するよりもコマンド定義するほうが楽に感じる今日この頃