hitode909の日記

以前はプログラミング日記でしたが、今は子育て日記です

Perlのメソッド呼び出しを置き換えるやつ,evalして死んだところを置き換える版

メソッド呼び出しを自動で指定した形式に置き換えるのを作りたくて,前からやってた.読んでない人はまず以下のエントリを読んでください.


前にやった方法では,以下のような問題があって,実用的でなかった.

  • 1トークンずつ見ていく方法では,Hashの順序や表記揺れに弱い
  • 実行時に書き換える方法では,引数が変数のとき,実行された瞬間の値に書き換えられてしまう
  • 自分で構文解析するの大変そうなのでやりたくない


今日新しいのを作った.これはめっちゃすごいと思う.

実行例

addをreverse_addにして引数を逆にしたい.普通に2つの引数で受け取る例.
実行前

add(1 , 2);

my ($x, $y) = (3, 4);
add($x, $y);


実行後

reverse_add(2, 1);

my ($x, $y) = (3, 4);
reverse_add($y, $x);
  • ちゃんと動いてる
  • 変数に対応している


引数のHashのaとbを入れ替えたい.
実行前

add(a => 1, b => 2);

my ($x, $y) = (3, 4);
add(a => $x, b => $y);
add('a', 'ignore this', 'b', $y, 'a', $x,);


実行後

reverse_add(a => 2, b => 1);

my ($x, $y) = (3, 4);
reverse_add(a => $y, b => $x);
reverse_add(a => $y, b => $x);

ポイント

  • ,と=>とか,最後にカンマがあるとか,表記揺れに対応できている
  • 重複したキーがあるとき,後が優先されている('ignore this'ではなく,$xが採用されている)

インターフェイス

ReplaceMethodCallのインスタンスを作って,このメソッド名が呼ばれたらこれをしてください,っていうのを登録して,このファイルを変換してくださいって呼ぶ.
Hashで引数渡すときはHashになってやってくるので,$hash->{a}とかでアクセスできる.文字列連結して新しい形式の文を返すと,呼び出し箇所が差し替えられてファイルに保存される.引数を展開するときは,quoteというメソッドを呼んで展開する.

my $r = ReplaceMethodCall->new;

$r->register(
    method_name => 'add',
    apply => sub {
        my ($args) = @_;
        my $hash = {@$args};
        my $arg1 = $hash->{a};
        my $arg2 = $hash->{b};
        "reverse_add(a => @{[ $r->quote($arg2) ]}, b => @{[ $r->quote($arg1) ]})";
    },
);

$r->file(@ARGV);

実装方法

引数の中身だけevalして実行して,Perlのオブジェクトにしている.add(1, 2)なら,[1, 2]というオブジェクトを作る.[1, 2]になってれば,配列の0番目と1番目を入れ替えれば,引数の順番を逆にするのも簡単.
このとき,add($x, $y)みたいに,変数があると,$xが3とかになってしまいそうだけど,そうならないように工夫している.
引数の中身だけevalするので,$xとか書いてあったらエラーが発生する.その発生したエラーメッセージを正規表現で調べて,$x'$x'というふうに文字列化してやる.'$x'は変数ではなく,文字列なので,evalできる.evalが通るまでエラーが出たところを書き換えていく.$user->nameなら,'$user->name' というふうに,値単位でクオートする.
また,本当に文字列にしてしまうと,$x'$x'になったのか,もとから'$x'だったのか,区別がつかなくなる.クオートしたことがあとから分かるように,Quoted->new('$x')みたいに,オブジェクト化して,あとから見て分かるようにしている.
詳しくはテストを見てください.

課題

  • add(add(1, 2), 3) みたいにネストするとうまくいかない.関数呼べませんっていうエラーが出る.
  • evalするので,引数の中でファイルを消すとか書いてあったら実際にファイルが消える
    • 安全なソースコードのみ実行するか,なんかサンドボックスみたいにしたい
    • それか, 全ての値をQuotedオブジェクトにする
  • 現在動作確認できているのはaddの引数を入れ替えるくらいであり,実際のソフトウェアで動くかは分からない
    • 仕事で使うために作ってたので,来週仕事のプロダクトに対して使ってみるつもりである
  • インデントずれそう
    • 置き換え前のインデントレベル見ておけば揃えられる

感想

うまくいくか分からなかったけどやってみたら動いたのでよかった.
ソースコード書き換えノウハウが貯まってきてておもしろい.以前書いたコードをコピペして使い回せたり,PPIの使い方とか慣れてきたりしてる.
途中までめちゃくちゃになってたけどテスト書いたおかげで楽に進められた.こういう複雑なことするときはテスト書いたほうがいい.テストなかったら完成しなかったと思う.
こういうことJavaではEclipseで右クリックとかしたらできるようなことなので,早くこれくらいできるようになりたい.実用的になれば会社のレガシーコード多少はこれでましにできると思う.