状態遷移
eskkに大量にコミットしたのでバグ潰しがてら普通の日記でも書く。
感覚で適当なこと書くので突っ込まれるだろうけど気にしない。
というか突っ込まれるのを気にしないために適当なこと書く。
モードというか状態遷移を実装する時はなるべくフラットにする、というのが最近自分が心がけていることの一つだったりする。
つまり関数を呼び出してまたそこから新しいモードに入るので関数を呼び出し、モードが終わったら戻ってくる、というのはダメだと思う。
関数を呼び出したらreturnしてまた別のモードの関数を呼び出す形にするのがいいと思う。
呼び出しのネストが深いというのは、よくないことの予兆の一つだと思っている。
Cではスタックを消費するから〜とかいう理由でもなくCに限ったことじゃないと思っている。
上位と下位のコードで値を受け渡ししにくくなる = 修正が難しいプログラム
だと思う。
Vimのソースコードは悪い例だと思っている。
とかdisれるレベルじゃないのですが(ry
例えばノーマルモードでiが押された時、
normal_cmd() (in normal.c)から(直接じゃないけど) edit() (in edit.c)を呼び出し、
インサートモードが終わるとedit()からreturnする。
そういう形じゃなくこういう形だと良いと思った。*1
MODE_NORMAL = 'n' MODE_INSERT = 'i' def do_normal(c, context) case c when 'i' ... context.mode = MODE_INSERT when 'a' ... context.mode = MODE_INSERT end end def do_insert(c, context) ... end mode_func_table = { MODE_NORMAL => do_normal, MODE_INSERT => do_insert, } def main_loop context = { col => 1, lnum => 1, mode => MODE_NORMAL, ..., } mode_func_table[context.mode](getchar(), context) end main_loop
VMみたいな形とも言える。
main_loopを
mode = mode_func_table[mode](c)
とかするとさらにそれっぽい。
それか継続で言うトランポリンとかいうアレかも。
スタックって言うから複数状態遷移のフローをpushできるみたいだけど。
グローバル変数経由で渡すしかないとかそんな理由な気がする。気がするだけ。
あとGNUのソースコード*2とかもグローバル変数使いまくりで
「プロセス分かれてるからええやろガハハ」な印象を受ける。
そこらへんはGNUとか関係なくて昔に書き始めたソースコードだからってだけだろう。多分。
まぁVimのfコマンドとかrコマンドとか入力を受け付けるのもあるし、
(別バッファにユーザからの入力を入れればいいという策もあるにしろ)
VimにしろVMにしろ、結局は「どこまでの操作を最小とするか」なんだと思う。
Vimのoperatorとtextobjのようにかけ算的に働かせるというアプローチはあるけど
それぞれどのように折り合いをつけるかが悩ましい。
operatorとtextobjの場合バッファ(文字列)の操作に特化してるから
そんなにややこしくはない気がする。
じゃあどんな操作が絡むとややこしいのか。
同期的な操作だろうか。I/Oみたいに、順序が変わってはいけない操作とか。
時間って厄介で何もしてなくてもパラメータが変わってくんですよ。やだー。
時間って何?