ふと思い立ち、SalesforceのApex言語のローカル実行環境を作っています。
まだまだリリースにはほど遠いのですが、単純なコードであればある程度は動く状態になってきているので、モチベーション維持のためにここらで紹介記事を書きたいと思います。
何ができるのか
こういうApexのコードがローカルで動くようになります。public class Hoge {
public Integer a;
public Integer b = 1;
public static Integer b = 1;
public Integer c { get; private set; }
public static Integer action() {
List<Account> accounts = [SELECT id, Name FROM Account];
System.debug(accounts);
List<Integer> integers = new List<Integer>();
integers.add(100);
integers.add(1000);
System.debug(integers[0]);
System.debug(integers.get(1));
Hoge h = new Hoge();
h.a = 1235787;
System.debug(h.a);
Integer i = 1;
switch on i {
when 2, 3 {
System.debug(i);
}
when 1 {
System.debug(111);
}
when else {
System.debug(123);
}
}
return 1;
}
}
コンパイルできるかどうかをAPI経由で確かめるのではなくローカルでシンタックスチェック、型チェックなどを実行しつつ、実際にコードの実行もできるというヤーツ。
ローカルで実行できて何が嬉しいのか
- 個人単位の開発効率が上がる
- 動作確認をWeb経由で行う必要がない
- シンタックスチェック、型チェックをWeb経由で行う必要がない
- テストコードをローカルで高速に実行できる
- デバッグがしやすい
- ローカルなのでステップ実行可能なデバッガが作れる
- デバッグログの表示内容をコントロールできる
- 例えばオブジェクトをSystem.debugしたときにprettyprintするなど
- (デバッグログはログでしかないので、実際のSalesforceでの動作と差異が有ってもそんなに問題じゃないはず)
- チーム単位の開発効率が上がる
- ローカルでApexを実行できるのでgitなどのVCSを使った開発ができるかも…?
なんで作ろうとしたのか
Salesforceのアンチパターンをまとめた技術書籍を書こうとメモ書きしていたところ、Apexはローカルで実行できないので○○
みたいなフレーズを結構書いていて、「あれ、これそもそもローカルで実行できたら解決すること多いのでは?」と思ってチャレンジしてみました。言語実装にも興味があったんですよねー。
利用技術
ANTLRというLL(*)なパーサジェネレータを使ってNode.jsで実装しています。Rubyが好きなのでRaccというLALR(1)なパーサジェネレータを使ってRubyで実装しようと思ったんですが、LALR(1)だとジェネリクスのシンタックスのパースがかなり厳しかったので断念。
ANTLRはJava, C#, C++, Go, JavaScript, Pythonに対応していて、Salesforce界隈でよく使われているCLI・ライブラリであるsfdxやjsforceがJavaScriptを採用していることもあり、JavaScriptを採用しました。Salesforce開発者はnodeを入れればOK!みたいな世界観ができるといいなぁ、みたいな。
WebのREPLを開発するときも静的ファイルだけで構成できそうですし。
今のところできることなど
- 簡単なコードであれば何となく動くような感じ
- デバッガ作った(後述)
- 標準のクラスに関しては実装中(数が多い…)
- modifier系(abstract/virtual/private/protected)はこれから
- SOQLと各DMLの実行基盤もこれから
- PoC的な感じで実装しているのでコードはかなり雑
今すぐ試してみたい方へ
こんな感じでcloneして$ git clone git@github.com:tzmfreedom/apex_parser2.git
bin/landのコマンドを叩けばOK
$ cd /path/to/apex_parser2
$ npm install
$ bin/land -f /path/to/apex.cls --action "Hoge#action"
Javaと同じようにエントリポイントとなる関数を定義する必要があって、上の例だとHogeクラスのactionメソッド(クラスメソッド)を実行するようになります。ここらへんは仕様策定中。
まだまだ動かないパターンが多いしコードもグッチャグチャなのでnpm化まではもう少しかかりそうです。
ローカル環境だからできる機能拡張
ステップ実行が手軽にできる環境があると便利ですよね。Rubyだとbyebugというステップ実行可能なデバッガがあるんですが、これと同じことを実現したいなーと思ったので作ってみました。こんな感じでデバッガを仕込めますpublic class Hoge {
public static void action() {
Integer i = 0;
System.debug(i);
i = 1;
// debugger
System.debug(i);
i = 2;
System.debug(i);
}
}
// debugger
というコメントがデバッガになります。コメントにデバッガの機能を仕込むことで、そのままデプロイしても通るようになっています。
実際にこのコードを走らせると対話式のデバッガが起動して、next
とかstep
とか打つとステップ実行できたり、show
コマンドで変数を確認できます。
$ bin/land -f /Users/mtajitsu/tmp/tmp/apex_parser2/hoge.cls --action "Hoge#action"
0
3: System.debug(i);
4: i = 1;
5: // debugger
=> 6: System.debug(i);
7: i = 2;
8: System.debug(i);
> next
これ以外にも // xxx
のコメント構文を使ってマジカルなことをしたいと思ってます。
その他色々
思い立ったのが6/18https://twitter.com/tzm_freedom/status/1008703525147181056
ANTLRで実装し始めたのは7/2
https://twitter.com/tzm_freedom/status/1013449445646561280
なので3〜5週くらいで実装していた感じなのですが意外と形になっていてびっくりです。
これからは
- SOQL/DML周りの実装
- 標準ライブラリの実装
- 不具合潰し・リファクタリング
ASTという意味合いだとローカル実行基盤以外にも、シンタックスハイライトしたりlintツールや補完ツールを作れたりもします。ここらへんは言語実装が落ち着いたら着手したいなーと思っていますが興味がある方はANTLRで実装してみるのも良いかもしれません。今のANTLRの文法ファイルはこれを使ってます↓
あと、今のアプローチだとざっくりこんな感じで
- AST作る
- ASTから型チェックや変数の二重定義などのチェックをする
- ASTを実行
本当は2でチェックしたものをバイナリか何かで保存できれば1, 2をスキップできるので高速に動きそうな感じはするのですが良い方法がないか模索中です。今のところメモリに常駐させて、ファイル変更があったら都度1, 2をそのファイルに対してのみ行う(差分に対してのみ適用する)ことで時間短縮しようかな、と思っていたり。Railsのspringがそういう考えで実装されているのでそれを踏襲できると良いかなと。さすがにバイトコードに変換するとかまではやりません(というかできないw)
あと、言語実装が専門な人ではないので私のクソパーサを見て「俺だったらもっと良いの作れる!」と後発で良いのが出るかもしれませんし、もしかしたら今年のDreamforceあたりでローカル実行環境とかそろそろ出てくるのでは?とか思っていたりします。リモートデバッガもできたようですし。まぁそれでも今の取り組みは全然無駄にはならないと思っているので、そこらへんは考えずに気軽にやってます。