yo_waka's blog

418 I'm a teapot

オレオレCoffeeScriptを作ろう

若干遅いですが、あけましておめでとうございます。
今年もこっそり!


昨年からcoffee-scriptが流行り出しましたね!
coffee-scriptってもう現場でも使われ始めてるのかな?
Railsな人たちは使ってるんだろうけど、JavaScriptでアプリ書いちゃうようなゴリゴリ書いてるところでも採用してたりするのかしら。

個人的には素のJSの方が小回りが利くので好きなのですが、誰が書いても出力されるJSの文法を揃えられるところはいいですね。
いや待てよ、よくよく考えてみれば、JavaScriptトランスレータな形でフレームワークの機能を提供するのはありかも。
人によってコールバックだったりイベントドリブンだったり、書き方が異なるとコードレビューも大変ですしそういうのを言語側で吸収するのはよさそう。ってか同じこと考えてる人結構いるんじゃね?
これはもう、2012年は間違いなくオレオレJavaScriptトランスレータが流行る!(嘘


ということでJavaScriptからJavaScriptを出力するトランスレータを作ってみました。
前のブログで、Closure Libraryのgoog.inheritsとgoog.baseはいいよねみたいなこと書いたので、言語としてその機能を提供しましょう。
素のJavaScriptに加えて、"class Foo extends Bar"と"super()"、"super.someMethod()"を使えるようにして、JSファイルとして出力する仕様にします。


JavaScriptの文法+上の文法を構文解析して作ったASTをJavaScriptに変換すればいいわけですが、JavaScriptのLexer/Parserを1から書くのは大変そう。。。

ググったところ、id:ConstellationさんがEcmaScriptのASTを作ってくれるLexer/Parserを作られていたので使わせてもらって、パーサーに"ClassDeclaration"と"ExtendsStatement"を追加しました。
素晴らしいもの、ありがとうございます!

これでASTからJavaScriptへの変換器を書くだけで上手くいきそうです。

Rewriterを書くにあたっては、id:teramakoさんがまとめられている、ECMAScript 6th Syntax Grammerがとても参考になりました。

ソースコードはGitHubに上げてあります。
名前はJSに少し機能追加したものなので、なんとなくJC Scriptでw
waka / jc-script


git cloneしてnpmインストールすると(しなくてもいいです)、こんな感じでCoffeeScriptライクに使える。
jcファイルをコンパイルすると、デフォルトでjcファイルと同じディレクトリに変換したjsファイルを書き出します。

$ jc -c -p test/extends.jc // 出力されるJavaScriptコードを標準出力に表示する
$ jc -c test/extends.jc // test/extends.jsを書き出す

extends.jcの中身はこう。

/**
 * @constructor
 */
class Parent() {}

/**
 * @type {boolean}
 * @private
 */
Parent.prototype.inDocument_ = false;

/**
 */
Parent.prototype.enterDocument = function() {
    this.inDocument_ = true;
};

/**
 * @constructor
 * @extends {Parent}
 */
class Child() extends Parent {
  super();
}

/**
 * @type {string}
 * @private
 */
Child.prototype.status_ = 'deactive';

Child.prototype.enterDocument = function() {
    super.enterDocument();
    this.status_ = 'active';
};

書き出されるextends.jsはこうなる。

function Parent() {
}

Parent.prototype.inDocument_ = false;

Parent.prototype.enterDocument = function () {
    this.inDocument_ = true;
};

function Child() {
    this.constructor.superClass_.constructor.apply(this, []);
}
(function(childCtor, parentCtor) {
    function tempCtor() {};
    tempCtor.prototype = parentCtor.prototype;
    childCtor.superClass_ = parentCtor.prototype;
    childCtor.prototype = new tempCtor();
    childCtor.prototype.constructor = childCtor;
})(Child, Parent);

Child.prototype.status_ = "deactive";

Child.prototype.enterDocument = function () {
    this.constructor.superClass_.constructor.prototype.enterDocument().call(this);
    this.status_ = "active";
};


これ自体は年末年始に「言語実装パターン」を読んだアウトプットだったりします。
といいつつ変換器しか書いてないけど。。
構文解析とかこれまでまともに勉強したことなかったので、よい機会になった!いい本でした。
ANTLRが何やってるのかとか、ようやくこの本読んで理解出来た気がする。

上で作ったのはおもちゃだけど、入力はECMAScript 6thで書けて、出力は5thとの差分を現状の代替コードとして吐き出すトランスレータとかは結構使えるかもなーと思ったりしました。
変換器は作ったので、時間できたらやってみようかな。