DOMの構築

今後ぶれないためにも微妙に書き記しておく。

コンパイラフレームワークを(コア)ユーザが使いたがらない理由の一つに、DOM(AST)を生成するのが面倒だから、というのがあるかなと思います。
たとえば、下記のコード片をEclipseのASTで生成する場合を考えます。

@Copy(source = "#hoge()")

上記を書くには、次のようなコードが必要になります。

// AST ast = AST.newAST(AST.JLS3);
NormalAnnotation copy = ast.newNormalAnnotation();
copy.setTypeName(ast.newSimpleName("Copy"));
MemberValuePair pair = ast.newMemberValuePair();
pair.setName(ast.newSimpleName("source"));
StringLiteral literal = ast.newStringLiteral();
literal.setLiteralValue("#hoge()");
pair.setValue(literal);
copy.values().add(pair);

元はたった一行の注釈なのに、かなりひどいことになりました。
Irenkaも初期構想ではこれに近いことを強制させようとしていたのですが、さるお方から「これは無理」と延々と言われた関係もあり、大幅に概念設計からやり直した過去があります。

ほかのアプローチとしては、Javassistのように(ソース言語Likeな)AST記述言語をパースして、それを扱うというような方法もあります。
これに近い設計も考えましたが、あくまでこれは方法のひとつでありメインに据えるものではないかなと。

現在のIrenkaは、次のような操作を仮定して作りこみ中です:

  1. どこかにあるソースコードから、特定のDOMノードを引っこ抜いてくる
  2. 引っこ抜いてきたノードのコピーを作って、好きに改変する
  3. 改変したノードを、好きな位置に配置

例として、「注釈を指定のメソッドに付与する」というコードの例を。

@Copy(source = "model") int a; // (1)

/**
 * @when
 *     sample in {@link #a}.annotations // (2)
 *     method = {@link Hoge#foo()} // (3)
 */
public void copier(LiteralFactory literals, CtAnnotationInstance<?> sample, CtMethod<?> method) {
  CtAnnotationInstance<?> copy = (CtAnnotationInstance<?>) sample.copy(); // (4)
  copy.getElement("source").getValue().substitute(literals.of("ほげほげ")); // (5)
  method.getModifiersAndAnnotations().add(0, copy); // (6)
}
  1. 注釈@Copyの「見本」を持つフィールド
  2. 「注釈@Copyの「見本」を持つフィールド」に含まれる注釈(=@Copy)をsampleに
  3. Hoge#foo() メソッドをmethodに
  4. sampleをそのまま改変すると見本が蹂躙されるので、一度コピー
  5. copy#source = "ほげほげ"
  6. Hoge#foo() メソッドの先頭に注釈を付与

このように、見本を作っておいてそれを参照するというモデルです。上記は注釈でしたが、メソッドをとってきてインライン展開を行ったり、クラスのMixinを行ったりといろいろと出来るようにしたいです。

この設計では、見本は通常のソースコードとして記述できますので

  1. (Javaという言語の設計を許せるなら)簡単に書ける
  2. コンパイラによるフェイルファスト
  3. 通常の単体テストを適用可能

などの利点があります。

さらに「見本の検出」と「DOMの操作」を完全に切り分けているところもポイントです。前者はクエリで記述し、後者はメソッド内のプログラムで記述しています。