実は薄毛を通り越して既に無毛なのですが、個人的に作っていた Objective-C to Swift コンバータ Objc2Swift.js が、そこそこ動くようになってきたので、GitHub で公開しました。
実装言語は JavaScript で、パーサージェネレータは PEG.js を使っています。
どんなものが出来たのか
こちらに デモ サイト を用意したのでご覧ください。D&Dでお手持ちの .m ファイルを突っ込んで試すこともできます。変換はブラウザ内でローカル実行され、サーバーにソースが送信されることはありませんのでご安心ください。
↓はデモサイトのスクショです。クリックするとデモに飛びます。念のため。
ブラウザ版はヘッダファイルを読み込めないので、ブラウザ上でヘッダ込みの変換をしてみたい場合は、ヘッダファイルと実装ファイルを cat したものをペーストしてください。
何が新しいのか
なんで今更 ObjC → Swift コンバータ?という感じですが、今回作ったものには、以下の特徴があります。
- Objective-Cのフル構文解析(PEG.js を使用)
- インデントやコメントなど、重要な情報をなるべく残して変換
- 大きめのソースを渡してもOK。数千行規模のソースコードでも落ちません
- コマンドライン版はヘッダファイルも読み込めます。UIKitなどSDKのヘッダもOK
- 型情報が揃っていなくても、構文エラーがなければそれなりに変換できる
また、意味解析にも少しですが踏み込んだので、
- メソッド呼び出し時に、自動でForced Unwrap (
!
を補完) - nullable, nonnull 修飾子 や NS_ASSUME_NONNULL_{BEGIN,END} を認識
- 代入されていない変数は var でなく let で宣言
- オーバーライドメソッドには override 修飾子を付加
などなど、この他にも細かく落ち穂拾いをやっています。詳細はドキュメントをご覧ください。
生成されたSwiftコードのコンパイルエラーは軽減されていますが、完全にエラーを0にすることは目的としていません。無理にエラーがないソースを出すより、微妙な部分は分かりやすくエラーのまま残して、プログラマに目視してもらう方が安全である、という考え方です。
実装の弱点としては、
- 構文解析の速度がちと遅い(速いPCで1000行あたり1秒ぐらい)
- 勢いで作ったのでコードが非常に汚い
- Appleから公式コンバータが出たら価値無し
あたりでしょうか。
なお、まだ未実装箇所がいくつかあり、変換できないケースもあります。配列の配列、ポインタのポインタなどは実装されていません。また typedef により定義された型はうまく型チェックできないことがあります。
以下は未実装ではなく、仕様です。
- 構文エラーがある場合は変換できない
- 構文自体を再定義するようなマクロ(メソッドや構文ブロックを生成するようなもの)は変換できない(NS_ENUMは例外的に対応)
なぜ作ったのか
Objective-C のコードをまともに Swift に変換してくれるコンバータが見当たらなかったからです。出力が少々荒くても実用性のあるものは誰か作っているだろうと勝手に思っていたのですが、昨年末(2015.12)にちょっと調べてみたところ、
- 構文解析をやっておらず、処理できないパターンが多すぎる
- 構文解析をやっているが、文法のカバー範囲が狭すぎる
の2パターンのどちらかで、大きめのソースを与えて動くものは皆無でした。
また、ソースコード変換で一番重要なポイントは、コメントやインデント(C系の場合はマクロとかも)をなるべく残すことだと思っているのですが、そこに対応しているコンバータもありませんでした。
どのように作ったのか
作ろうかな、と思い立ったのは去年のクリスマスごろです。他の活動と並行していたのと、基本的に日曜日ぐらいしか作業時間がなかったため、けっこう時間がかかってしまいました。
最初は構文解析に ANTLR + ObjC.g4 を使おうと思ったのですが、ObjC.g4が古くて最終更新が2006年ごろだったのと、ざっと見て違和感のある定義(パースが通らないとか、パースは通るけどすごく変な場所に文法が書いてあるなど)があったので、後々手詰まりになると感じて使わないことにしました。
正月休みに3日ほどがっつり集中できる時間があったので、ObjC.g4 や C.g4 を参考にしつつ、PEG.js 用の文法定義ファイル objc.pegjs を書きました。
このとき、文法のデバッグが想像以上に大変だったので、 副産物としてデバッグ用のpegjs-backtrace を作って公開しました。
そこからしばらく放置してしまい、Swift コードの生成器の実装に着手したのは1月末です。しかし意外とコード生成はあっさり出来て、1日程度で大半の構文が処理できるようになりました。これは抽象構文木のクラスをまったく定義せず、とにかく動けばいいの精神で、可読性保守性を捨てて冗長に書き殴ったためと思います。
この時点で既存のコンバータよりは変換できる感じになっていたので、このまま公開しようかな?とも思ったのですが、変換後のソースを Xcode に掛けるとあまりにも型周りのコンパイルエラー、特に Optional Type がらみのエラーが多すぎたのと、例外で変換時に落ちるパターンが散見されたため、さすがに恥ずかしいなと思ってやめました。
そこから暇を見つけて型チェック(型推論ではない)の実装を進めました。こいつは結構難産で、元々型チェックしないでお気軽に変換器を作るつもりで進めてしまっていたため、大きな構造の変更が必要になってしまいました。また SDK ヘッダをサポートするために簡易プリプロセッサとキャッシュを作るなどしたこともあり、2月の日曜日をほとんど費やして、今日に至ります。
おわりに
PEG.js のおかげで、わりと小さな工数で、ある程度のコンバータが作れたと思います。JavaScript + PEG.js は気軽にパーサ処理系を作るときはなかなか良い組み合わせではないでしょうか。ただ、PEG.js が生成する構文解析器は比較的動作が遅い(バージョンアップで改善予定はあるようですが)ので、処理速度が強く求められる場合は別のアプローチが良さそうです。