2018-07-24

[Salesforce] Apexのローカル実行環境を作ろうとしている話

ふと思い立ち、SalesforceのApex言語のローカル実行環境を作っています。

tzmfreedom/apex_parser2

まだまだリリースにはほど遠いのですが、単純なコードであればある程度は動く状態になってきているので、モチベーション維持のためにここらで紹介記事を書きたいと思います。

何ができるのか

こういう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経由で確かめるのではなくローカルでシンタックスチェック、型チェックなどを実行しつつ、実際にコードの実行もできるというヤーツ。

ローカルで実行できて何が嬉しいのか

なんで作ろうとしたのか

Salesforceのアンチパターンをまとめた技術書籍を書こうとメモ書きしていたところ、 Apexはローカルで実行できないので○○ みたいなフレーズを結構書いていて、「あれ、これそもそもローカルで実行できたら解決すること多いのでは?」と思ってチャレンジしてみました。言語実装にも興味があったんですよねー。

利用技術

ANTLRというLL(*)なパーサジェネレータを使ってNode.jsで実装しています。

Rubyが好きなのでRaccというLALR(1)なパーサジェネレータを使ってRubyで実装しようと思ったんですが、LALR(1)だとジェネリクスのシンタックスのパースがかなり厳しかったので断念。

ANTLRはJava, C#, C++, Go, JavaScript, Pythonに対応していて、Salesforce界隈でよく使われているCLI・ライブラリであるsfdxjsforceがJavaScriptを採用していることもあり、JavaScriptを採用しました。Salesforce開発者はnodeを入れればOK!みたいな世界観ができるといいなぁ、みたいな。

WebのREPLを開発するときも静的ファイルだけで構成できそうですし。

今のところできることなど

今すぐ試してみたい方へ

こんな感じで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/18

https://twitter.com/tzm_freedom/status/1008703525147181056

ANTLRで実装し始めたのは7/2

https://twitter.com/tzm_freedom/status/1013449445646561280

なので3〜5週くらいで実装していた感じなのですが意外と形になっていてびっくりです。

これからは

を軸に実装を進めていき、9〜10月くらいには公開できると良いかなーと思っとります。

ASTという意味合いだとローカル実行基盤以外にも、シンタックスハイライトしたりlintツールや補完ツールを作れたりもします。ここらへんは言語実装が落ち着いたら着手したいなーと思っていますが興味がある方はANTLRで実装してみるのも良いかもしれません。今のANTLRの文法ファイルはこれを使ってます↓

apex_parser2/apex.g4

あと、今のアプローチだとざっくりこんな感じで

  1. AST作る
  2. ASTから型チェックや変数の二重定義などのチェックをする
  3. ASTを実行
毎回1の工程から処理するので標準ライブラリのクラスが増えたりApexのファイルが増えると実行時間が長くなります。

本当は2でチェックしたものをバイナリか何かで保存できれば1, 2をスキップできるので高速に動きそうな感じはするのですが良い方法がないか模索中です。今のところメモリに常駐させて、ファイル変更があったら都度1, 2をそのファイルに対してのみ行う(差分に対してのみ適用する)ことで時間短縮しようかな、と思っていたり。Railsのspringがそういう考えで実装されているのでそれを踏襲できると良いかなと。さすがにバイトコードに変換するとかまではやりません(というかできないw)

あと、言語実装が専門な人ではないので私のクソパーサを見て「俺だったらもっと良いの作れる!」と後発で良いのが出るかもしれませんし、もしかしたら今年のDreamforceあたりでローカル実行環境とかそろそろ出てくるのでは?とか思っていたりします。リモートデバッガもできたようですし。まぁそれでも今の取り組みは全然無駄にはならないと思っているので、そこらへんは考えずに気軽にやってます。

このエントリーをはてなブックマークに追加