ex コマンドの実行処理を再考したい。
実行処理の核となるループは単純だ。
- ソース文字列は ex コマンド列である
- ソース文字列から ex コマンドをひとつ取り出す。このとき、ex コマンドの区切りは改行だったり、[cci]|[/cci] だったりコマンドごとに異なる。この違いを吸収するのはパーサの仕事だ
- 取り出した ex コマンドから引数を解析する
- コマンド毎のハンドラを呼び出す
- ハンドラがエラーを返すか、ソース文字列の最後まで実行するまで繰り返す
しかし単純ではないものもある。まず、動作が非同期に行われるコマンドがある。
- クリップボードからの読み込み
- dropbox などからのファイルの読み込み
これらのコマンドを実行した場合、いったん実行ループをそこで終了し、待機状態にする必要がある。ex コマンド実行中ではあるが、待機中ということだ。そして、それぞれの処理が完了した時点で実行ループを再開する。なお、待機状態のタイムアウトや、[cci]^C[/cci] を特別なシグナルとして扱う処理は必要だ。
もうひとつ面倒なのは、ex コマンドが入れ子に実行される場合がある。global、v、および edit コマンドなども該当する。
現在の ex コマンド実行クラスである ExCommandExecutor は、非同期処理の機構は基本的には実装済みであるものの、先の記事の通り global の入れ子としてのコマンド群の実行が非同期処理に対応していない。これをどうにかするのがこのミッションのステートメントだ。
ExCommandExecutor はまずソース文字列を解析し、ひとつの ex コマンドにひとつのオブジェクト(引数とハンドラを保持する)が対応するその配列を生成する。ソースそのものではなくいわば中間形式にして保持するのは、非同期の完了時に呼ばれる仮想的・擬似的な ex コマンドを動的に ex コマンド列中に挿入する必要があるためだ。ソース文字列のままだと挿入するポイントをつかみにくい。なお擬似というのは中間形式の状態では存在するが、あくまで内部的に使用するものであって、ユーザーが直接使用することはできないということだ。
それを踏まえて入れ子コマンドを考えるに、中間形式も入れ子の構造にする必要はない。たとえば配列の [n] 番目が edit でその引数に読み込み完了後に実行される ex コマンドを指定した場合、時系列的にはその入れ子のコマンド群は [n+1] 番目のコマンドの前に実行されるわけなので、単に一次元の配列のまま [n] の直後、[n+1] の直前に新しい中間形式を挿入すればよい。あくまで一次元の配列のまま考えばよい。
ただしそうは言っても、global の場合はもっと複雑になる。[n] 番目が global だったとき、[n] の直後に入れ子コマンドの中間形式を挿入するのは同じだが、先頭には擬似 ex コマンドとして global の処理の後段、つまり与えられた正規表現にマッチする全ての行に対して入れ子コマンドを実行するループを制御する処理を司る中間形式 latter-global-head が入る。また末尾にもまた擬似 ex コマンドとして、入れ子コマンドの実行後の分岐を行う処理を司る中間形式 latter-global-bottom が入る。latter-global-head は引数とハンドラの他に
- 正規表現にマッチした全行への参照
- 入れ子の ex コマンドの個数
も保持する必要がある。latter-global-bottom は
を持つ。
そして、実際にこの中間形式を実行する際は、
- global: 次に実行されるべき latter-global-head を挿入する
- バッファから正規表現にマッチする行を抜き出し、latter-global-head に保持させる
- 入れ子のコマンドの中間形式を latter-global-head の直後に挿入する
- 続けて latter-global-bottom を挿入する
- latter-global-head、latter-global-bottom に挿入した中間形式の個数を保持させる
- latter-global-head: マッチしている行が残っているなら、何もしない。すなわち直後の中間形式を実行させる
- マッチしている行が残っていないなら、中間形式のプログラムカウンタ的なものに入れ子の ex コマンドの個数を加算し、実行をスキップさせる
- latter-global-bottom: 入れ子のコマンドが実行されるとその最後にこの中間形式が実行される。プログラムカウンタを latter-global-head へ戻す
というような、逐次処理・分岐処理・繰り返し処理を備えたちょっとした構造化言語として振る舞わせる必要がある(これは一見、ex コマンドの実行処理部がそのままスクリプティングの実行エンジンに転用できそうな感じがするが、その予定はない)
それから、入れ子のコマンドでは特定のコマンドが使用を制限されることに気をつけなければならない。global の入れ子コマンドは global と v を使うことができない、と POSIX は定義している。これはわかる。しかし不思議なことに、edit コマンドに付加できるコマンドにはそういう制限はないように思える。つまり edit の入れ子に edit したりすることが許される。[cci]:edit +edit\ B A[/cci] としたとき、最終的に読み込まれるのは B だ。これにどういう意味があるのかはわからないが、まあそういうふうに動作するように組んでみよう。