TL; DR
Vim scriptをパースしてASTを作り、高速化を図ります。リポジトリはこちら→wholekeik/vim
AST化
Vim script は実行のたびにコマンドをパースしているので非常に遅い言語です。コマンドをパースしておいてASTとし、それを実行すれば高速化が見込めます。しかし、Vim scriptでは引数の解釈が各コマンドによって全く異なるため、共通のパーサーを書くのは不可能です。したがって事前にパースするのではなく実際に実行しながら並行してASTを作っていきます。なおVim scriptの実行はユーザーの入力(コマンドモード)やオートコマンド、関数などがありますが、ASTとなるのは関数内のみです。また、AST化は行単位で行われます。
大まかな流れ
- 関数を定義する
- 通常通り呼ばれる
- 呼ばれたExコマンドのうち、ASTにできるものがあれば実行しつつASTを作る
- 2回目に同じ関数が呼ばれたとき、ASTが作られていればそちらを実行する。なお、一度AST化に失敗した行は二度とAST化されない
AST化できるコマンド・式
- if
- elseif
- while
- throw
- echo
- echon
- return
- 及びこれらの引数に現れる式(の一部)
AST化されているか確認するには、:function 関数の名前
を入力してください。
" AST化されていないfunction! F()1return1endfunction" 1行目がAST化されているfunction! F()1return1endfunction
AST化された行が他の行より深くインデントされます。元々はoptimized: return 1
のように表示していたのですが、Vimのテスト(make test
)でこの:function
コマンドの出力をパースするため、見づらい意味を変えない表記にしました。
安定性
Vimのソースコードについているテスト(make test
)はすべて通ります。しかし、同じ関数を二度以上実行してみないとAST化がうまくいっているかはわかりませんので、あまり信用はできません。
ベンチマーク
TODO // あとで調べて書く
破壊的変更
一点のみ、互換性を破壊する箇所があります。具体的にいうと次のスクリプトが動きません(ソース元はこちら→本当にキモい Vim script - . 演算子編)。
function! s:func(time)let time =a:time
let suffix ='min'return1000-time.suffix
endfunction
echo s:func(50)" => 950min
echo s:func({'suffix': 'sec'})" => 1000 (これまでの動作)" => エラー (新しい動作)
このように、「関数内でドット演算子を使っているとき、ドット演算子の意味が途中で変化する」とエラーになるようになります。
課題
以下はAST化できません。
- call
- execute
- for
- let
- ラムダ式
- 複雑な関数呼び出し
|
を含む行- echomsg
- echoerr
- unlet
- lockvar
- unlockvar
- ブレース展開(
var{'iab'}le
) - 関数内関数
- ある条件を満たすドット演算子
AST化できない式を含むコマンドも連鎖的にAST化できなくなります。
関数についてですが、len('abc')
といったビルトイン関数、s:func(123)
といったユーザー定義関数はASTにできます。しかし、dict.func(0)
や、function('f', [42])(1)
というような複雑な関数はASTにできません。詳細はソースコードを参照してください。
もう一つ、ドット演算子の条件について説明します。Vimのドット演算子は左辺の型が決定しないと優先順位すら定まりません。ここで、x ? a.b : c.d
のような式を考えてみます。x
が真の時はa
が評価されるので一つ目のドット演算子が文字列結合か辞書アクセスかが決定します。しかしその時、c
は評価されませんので、二つ目のドット演算子はどちらの意味なのかがわかりません。そのためc.d
をASTにできず、連鎖的にx ? a.b : c.d
全体がASTにできなくなります。x
が偽の時も同様のことが起こるため、結局この式は絶対にASTにはできません。「右側に空白が一つ以上あればそのドット演算子を文字列結合とみなす」というルールがあればこの問題を(部分的に)解決できるのではないかと思っています(左側だと行継続の関係で辞書のアクセスでもスペースがある可能性があります)。ただ互換性が壊れる危険があるのでこれについては既存のスクリプトを調査する必要があります。
記事投稿時点での目下の課題は:let
と:call
のAST化です。この二つを実装すればかなりの行がAST化できるようになるのではないでしょうか。
試したい方へ
$ git clone https://github.com/wholekeik/vim
$cd vim
$ git checkout feature/vimscript-optimization
$ ./configure && make && make test# 注: 私は引数なしのconfigure,makeしか試していません
大規模な変更でありどのようなバグがあるかわかりませんので、-u NONE -i NONE
のフラグ付きで起動するか、.vimrc等のバックアップをとった状態で起動してください。ファイル消失など、何が起こっても責任は負えません。
ブランチは
feature/vimscript-optimization
に切り替えてください。FEAT_OPTIMIZATION
を有効にする必要があります。今はsrc/feature.h
に#define FEAT_OPTIMIZATION
が直書きされているので何もする必要はありません(あとでこれはちゃんとfeatureを定義します)。逆に、その行をコメントアウトすればFEAT_OPTIMIZATION
が有効になっていない状態でのテストが可能になります。開発中の利便性のため
src/Makefile
が直接変更されており、デバッグ用のフラグがいくつかオンになっています。ベンチマークを取っていただける場合は、それらを切ってコンパイラの最適化オプションを有効にした状態でお願いします。
この量の変更を一人でテストするのは難しいので、バグ報告だけでも非常に助かります。