XJC: plug-in で拡張できる Java コンパイラ

XJC (eXtensible Java Compiler) という Java コンパイラを作りました。
XJC は plug-in を使うことで機能を拡張できます。これにより、あたかも Java の言語仕様を拡張したかのようなコードを書くことができます。

現在、次のような plug-in を提供しています。

  • @accessor を使うことで、インスタンス変数の getter と setter を自動生成します。また @getter なら getter だけ、@setter なら setter だけを生成します。
class Person {
  @accessor // getName() と setName() が作られる
  private String name;
  @accessor("age of person")  // ドキュメント付き
  private int age;
  @accessor  // '_' は取り除かれ、getUserName() と setUserName() が作られる
  private String _user_name;
}
  • var を使うことで、変数宣言での型を省略できます。
// String s = "foo"; と同じ。
var s = "foo";

// HashMap<String, Object> m = new HashMap<String, Object>(); と同じ。
var m = new HashMap<String, Object>();

// これはエラー
var x;
  • 新しい予約語 lambda を導入し、インターフェースと無名クラスが簡単に書けるようになります。
public lambda void Action(Event ev);
onclick(new Action(Event ev) { System.out.println(ev.toString()); });

// これは次と同じ
public interface Action extends xlc.Lambda {
  public void execute(Event ev);
  // execute の名前を変えたいときは lambda(othername) のように指定する
}
onclick(new Action() {
  public void execute(Event ev) {
    System.out.println(ev.toString());
  }
});
  • @"..." または @'...' で文字列リテラルを表します (複数行は指定できません)。@"..." では #{...} を使って式を埋め込めます。
var html = @"<a html=\"#{url}\">#{label}</a>";
// これは次と同じ
String html = "<a html=\"" + url + "\">" + label + "</a>";
Matcher m = @/<\/?(\w+).*?>/.matcher(text);
if (m.find()) {
  System.out.println("m[1]=" + m[1]);
}
// これは次と同じ
private static _$regex001 = Pattern.compile("</?(\\w+).*?>");
Matcher m = _$regex001.matcher(text);
if (m.find()) {
  System.out.println("m[1]=" + m.group(1));
}
  • ヒアドキュメントが使えるようになります。終了記号をインデントすると、そのインデント分はヒアドキュメントからも取り除かれます。#{...} を使った埋め込み式は将来的にサポートされる予定です。
// これは String text1 = "aaa\n bbb\n"; と同じ
//  (インデントが消えていることに注意)
String text1 = <<END;
   aaa
    bbb
   END
// 最後の改行を取り除くこともできる。
// たとえば次は String text2 = "aaa\n bbb"; と同じ
String text2 = <<END-;
   aaa
    bbb
   END
public class Value {
  @accessor int value;
  public Value(int value) { this.value = value; }
  public Value __add__(Value v) {
    return new Value(this.value + v.getValue());
  }
  public Value __sub__(Value v) {
    return new Value(this.value - v.getValue());
  }
}
var v1 = new Value(10);
var v2 = new Value(20);
var v3 = v1 + v2;   // v1.__add__(v2) と同じ
var v4 = v1 - v2;   // v1.__sub__(v2) と同じ
System.out.println(v3.getValue());
System.out.println(v4.getValue());
  • java.util.List や java.util.Map に [] でアクセスできるようになります。
var list = new ArrayList<String>();
list[0] = "AAA";   // list.add(0, "AAA") と同じ
list["0"] = "BBB"; // list.add("0", "BBB") と同じなのでエラー
var map = new HashMap<String, String>();
map["a"] = "AAA";  // map.put("a", "AAA") と同じ
map[0] = "BBB";    // map.put(0, "BBB") と同じなのでエラー
  • 拡張 for 文で Map が指定できるようになります。
var map = new HashMap<String, String>();
map["a"] = "AAA";  map["b"] = "BBB";
for (String key, String val: map) {
  System.out.println("key="+key+", val="+val);
}
  • 引数のデフォルト値が指定できるようになります。ただし、今のところリテラルしか指定できません。
void f1(int x, int y=0, String name=null) { ... }
// これは次と同じ
void f1(int x, int y, String name) { ... }
void f1(int x, int y) { f1(x, y, null); }
void f1(int x) { f1(x, 0, null); }
  • getter や setter が定義されている場合は、obj.attr や obj.attr = value でアクセスできるようになります。ただし、同名のインスタンス変数にアクセスできる場合はそちらが優先されます。
class Foo {
  @accessor
  private int val1 = 0;
  @accessor
  public int val2 = 0;
}
var obj = new Foo();
obj.val1 = 10;    // obj.setVal1(10) と同じ
obj.val2 = 20;    // val2 が public なので、obj.setVal2(20) は呼ばれない
  • 新しい論理演算子「|||」を導入します。これは「||」に似ていますが、真偽値ではなく null かどうかを調べます。また左右の型は一致している必要があります。
String tmpdir = getEnviron("TEMP") ||| getEnviron("TMP") ||| "/tmp";
// これは次と同じ
String _tmp = getEnviron("TEMP");
if (_tmp == null) _tmp = getEnviron("TMP");
if (_tmp == null) _tmp = "/tmp";
String tmpdir = _tmp;
  • typedef を使って、型に別名をつけることができます。
typedef int money;
money amount = 1200;


これらの plug-in はデフォルトで有効になってますが、設定ファイルまたはコマンドラインオプションで個別に無効にすることができます。

また現在検討中の機能がいくつかあります。将来的には導入したいのですが、仕様がまだ練れていないので未導入です。そのような機能の中からいくつかを紹介します。

#ifdef MOBILE_PHONE
  ...
#else
  ...
#endif
@/<\/?\w+.*?>/ =~ str;
if ($m != null) {
  System.out.println("$m[1]=" + $m[1]);
}
// これは次と同じ
private static _$regex001 = Pattern.compile("</?(\\w+).*?>");
Matcher $m = _$regex001.matcher(text);
if (! $m.find()) $m = null;
if ($m != null) {
  System.out.println("$m[1]=" + $m.group(1));
}
  • Object#equals(object) のかわりに使える演算子 === の導入
void test(String s1, s2) {
  if (s1 === s2) System.out.println("equal.");
}
// これは次と同じ
void test(String s1, s2) {
  if (s1.equals(s2)) System.out.println("equal.");
}
// またはこっちにするか検討中
void test(String s1, s2) {
  if (s1 == null ? s2 == null : s1.equals(s2)) System.out.println("equal.");
}
  • 左辺値が null のときにだけ右辺値を代入する演算子「|||=」の導入
void createTempDir(String tmpdir) {
  tmpdir |||= getEnviron("TEMP") ||| getEnviron("TMP") ||| "/tmp";
  ...
}
  • if 文や while 文の条件式に boolean 以外の型が指定されたときは、null かどうかを調べるようにする。Java は静的なので、このように変更しても boolean と間違えることはないはず。
if (obj) 
  System.out.println("not null");
// これは次と同じ
if (obj != null)
  System.out.println("not null");
  • if 文の反対である unless 文
unless (obj) 
  System.out.println("is null");
// これは次と同じ
if (obj == null)
  System.out.println("is null");
  • List や Map を簡単に生成できるような文法のサポート
var list = new ArrayList<String>{"foo", "bar", "baz"};
var map = new HashMap<String, String>{"a":"AAA", "b":"BBB"};
  • DI コンテナの存在を前提として、interface名で new する機能
public interface Foo { ... }
Foo foo = new Foo();
// これを次のように変換する
static String $di_config = "seasar.dicon";
S2Container $di_container = S2ContainerFactory.create($di_config);
Foo foo = (Foo)$di_container.getComponent(Foo.class);
  • 疑似 Mix-in
public module TemplatePattern {
  // module は interface と似ているが、メソッドの宣言だけでなく
  // メソッドの定義もできる点が違う
  public void setUp() { /* empty */ }
  public void doAction(); // abstract
  public void tearDown() { /* empty */ }
  public void perform() {
    setUp();
    doAction();
    tearDown();
  }
}

public class Foo extends Bar includes TemplatePattern {
  public void doAction() {
    System.out.println("do action.");
  }
}

// これは次と同じ
public interface TemplatePattern {
  public void setUp();
  public void doAction();
  public void tearDown();
  public void perform();
}

public class $TemplatePattern {
  public void setUp(TemlatePattern _this) { /* empty */ }
  public void tearDown(TemplatePattern _this) { /* empty */ }
  public void perform(TemplatePattern _this) {
    _this.setUp();
    _this.doAction();
    _this.tearDown();
  }
}

public clas Foo extends Bar implements TemplatePattern {
  private $TemplatePattern _$TemplatePattern = new $TemplatePattern();
  public void setUp() { _$TemplatePattern.setUp(this); }
  public void tearDown() { _$TemplatePattern.tearDown(this); }
  public void perform() { _$TemplatePattern.perform(this); }
  public void doAction() {
    System.out.println("do action.");
  }
}


XJC のダウンロードは以下から行ってください。
http://xjc.sourceforge.net/
興味のある人や今の Java に不満のある人はぜひ使ってみてください。