パーサ千行コンパイラ千行のトランスレータ言語MamMouthを弄る


## はじめに

最近、CoffeeScript,Haxe,JSX等等、JavaScriptを出力するトランスレータ言語が熱いですよね。

CoffeeScriptみたいな言語を作って遊べたらいいのになぁ。
そんな夢を抱く人がいるかもしれません。
今回はそんな夢を叶えるべく、Mammouthを弄って遊ぶ方法を紹介したいと思います。

## Mammouthとは？

Mammouthはそんなトランスレータ言語の一つです。CoffeeScriptのようなオフサイドルールな記述からPHPを出力します。

詳しくは、Mammouthのサイトを参照してください。
 http://mammouth.wamalaka.com/


## なぜMammouth?

いろいろトランスレータ言語はありますが、結構ソースを見ると長くて手が出せそうにありません。でも、Mammouthなら安心です。
大ざっぱに言うと、パーサが1000行、コンパイラ1000行で構成されています。

wcの結果

```
      28      80     542 LineTerminator.pegjs
    1088    3811   28402 compiler.js
       3      28     374 helpers.js
    1215    3251   88244 parser.pegjs
      15      32     331 tokens.js
    2349    7202  117893 total
```

とても小さいですよね。これならちょちょっと弄って遊べそうです。
文法ファイルはPEGなので、コンフリクトに悩まされる事もなくパーサを書き換える事が出来ます。
JavaScriptで作ってあるので、難しい言語を覚える必要もありません。

## インストール

nodejsをあらかじめインストールされていれば、npmでインストールするだけです。

```
npm install -g mammouth
```
場合に寄ってはcommanderが足りないとか言われるので

```
npm install -g commander
```
とか追加モジュールをインストールしましょう。

## 使い方

まず、mammouthのプログラムを入れるディレクトリを作ります。

```
$ mkdir mam
```

次に、出力先ディレクトリを作ります。

```
$ mkdir out
```

でもって、mammouthファイルを作ります。

```
$ echo -e "{{\necho (\"a\\\\n\")\n}}" > mam/a.mammouth 
$ cat mam/a.mammouth 
```

mam/a.mammouthファイルはこんなファイルです。

```
{{
echo ("a\n")
}}
```

コンパイルします。

```
$ mammouth -c mam -o out
```

コンパイルすると結果こんな表示がされます。

```
[ { type: 'block', elements: [ [Object] ] },
  { type: 'embed', content: '\n' } ]
```

結果をみるとこんな感じのPHPが生成されてます。

```
$ cat out/a.php 
```

```
<?php 
echo('a'."\n".'');
?>
```

実行すると

```
$ php out/a.php 
```

aと表示されます。

```
a
```

うまく動きました！

## classを使ってみる

out/class.mammouth を以下のように書きます。

```
{{
class A ->
  foo ->
    echo("foo\n")

a = new A
a.foo()
}}
```
コンパイルします。

```
$ mammouth -c mam -o out
$ cat out/class.php
```

結果を見ると以下の通り

```
<?php 
class A {	
	function foo() {
		echo('foo'."\n".'');
	}
}
$a = new A();
$a->foo();
?>
```

実行してみると、

```
$ php out/class.php 
foo
```

fooと表示出来ました!

## ビルドする

ソースを拾ってきます。

```
$ git clone https://github.com/btwael/mammouth.git
```

mammouthディレクトリに移動して

```
cd mammouth
```

package.jsonを開きます。

```
$ vi package.json 
```

versionを0.2.1とかに変えましょう。

```
{
        "name": "mammouth",
        "version": "0.2.1",
        "description": "Unfancy PHP",
        "author": {
                "name": "Wael Amine Boutglay",
                "email": "btwael@gmail.com"
        },
        "bin": "./bin/mammouth",
        "main": "extras/mammouth-nodejs.js",
        "repository": {
                "type": "git",
                "url": "git://github.com/btwael/mammouth.git"
        }
}
```


ビルドしてみます。

```
$ node build.js
```

なんか一瞬で終わります。

```
$ npm install -g
/usr/local/bin/mammouth -> /usr/local/lib/node_modules/mammouth/bin/mammouth
mammouth@0.2.1 /usr/local/lib/node_modules/mammouth
```
これでインストールできます。mammouth@0.2.1ってなってるのでインストール出来たようです。
コンパイルして実行してみます。

```
$ mammouth -c ../mam -o ../out
[ { type: 'block', elements: [ [Object] ] },
  { type: 'embed', content: '\n' } ]
[ { type: 'block', elements: [ [Object], [Object], [Object] ] },
  { type: 'embed', content: '\n' } ]
$ php ../out/a.php 
a
```
ちゃんと動きますね。

## パーサを書き換えよう

こんなMammouthですが、まだまだ実用には機能不足だったりします。例えば、classはあるんだけど、extendsが出来ません。

なので、継承できるようにしてみましょう。

### テスト用ファイルを作る

まずは、こんなファイルを作ります。

```
$ vi ../mam/ext.mammouth
```

```
{{
class A ->
  foo ->
    echo("foo")
class B extends A ->
b = new A
b.foo()
}}
```

Aを継承したBクラスを作ってfooを呼び出すわけです。

コンパイルしようとすると

```
$ mammouth -c ../mam -o ../out
[ { type: 'block', elements: [ [Object] ] },
  { type: 'embed', content: '\n' } ]
[ { type: 'block', elements: [ [Object], [Object], [Object] ] },
  { type: 'embed', content: '\n' } ]
{ name: 'SyntaxError',
  expected: [ '"->"', 'comment', 'whitespace' ],
  found: 'e',
  message: 'Expected "->", comment or whitespace but "e" found.',
  offset: 47,
  line: 5,
  column: 9 }
```

エラーになりますね。

### パーサ書き換え

継承する機能がないので、継承する事が出来るようにしましょう。

パーサのファイルを開きます。

```
$ vi src/parser.pegjs
```

ClassDとかで検索します。

これがClassの宣言の文法です。

```
ClassDeclaration
        = ClassToken __ name:Identifier __ '->' __
        body:( b:blank* INDENT c:(n:ClassStatement)* DEDENT { return b.concat(c); })? {
                return {
                        type:"ClassDeclaration",
                        name: name,
                        body: body !== '' ? body: null
                }
        }
```

```ClassToken __ name:Identifier __ '->' __```という箇所が変えたい部分です。

```class A extends 継承元クラス名 ->```みたいにしたいので

```= ClassToken __ name:Identifier __ ext:('extends' __ Identifier __ )? '->' __```

と書き換えます。extをパーサが返すようにしたいので、```body: body !== '' ? body: null```をコピーして``` ext: ext !== "" ? ext[2]: null,```という1行を書き加えます。



```
ClassDeclaration
        = ClassToken __ name:Identifier __ ext:('extends' __ Identifier __ )? '->' __
        body:( b:blank* INDENT c:(n:ClassStatement)* DEDENT { return b.concat(c); })? {
                return {
                        type:"ClassDeclaration",
                        name: name,
                        ext: ext !== "" ? ext[2]: null,
                        body: body !== '' ? body: null
                }
        }
```

結果こんな感じになります。

さ、ビルドして実行してみます。

```
$ node build.js
$ npm install -g
/usr/local/bin/mammouth -> /usr/local/lib/node_modules/mammouth/bin/mammouth
mammouth@0.2.1 /usr/local/lib/node_modules/mammouth
$ mammouth -c ../mam -o ../out
[ { type: 'block', elements: [ [Object] ] },
  { type: 'embed', content: '\n' } ]
[ { type: 'block', elements: [ [Object], [Object], [Object] ] },
  { type: 'embed', content: '\n' } ]
[ { type: 'block',
    elements: [ [Object], [Object], [Object], [Object] ] },
  { type: 'embed', content: '\n\n' } ]
```
うまくいきました。

出力結果は、、、
```
$ cat ../out/ext.php 
<?php 
class A {	
	function foo() {
		echo('foo');}
}
class B {}
$b = new A();
$b->foo();
?>

まだ、extendsされてませんね。コンパイラを書き換えていないので当然といえば当然の結果です。
次はコンパイラを書き換えましょう。

## コンパイラ書き換え

パーサが出来たので、パースした結果を出力結果に反映させましょう。

src/compiler.jsを開きます。

```
vi src/compiler.js
```

ClassDとかで検索します。

```
                        case 'ClassDeclaration':
                                var r = Tokens.ClassToken + ' ' + evalStatement(seq.name);
                                r += ' {';
                                if(seq.body != null) {
```
この辺を修正すると良さそうです。
パーサで、extに継承クラスを入れるようにしていて、bodyに合わせて書いたので、```seq.body != null```ならbodyを出力するように書いてあるのに合わせて、

```
                                if(seq.ext != null) {
                                        r += ' extends ' + seq.ext;
                                }
```
を書き加えます。

```
                        case 'ClassDeclaration':
                                var r = Tokens.ClassToken + ' ' + evalStatement(seq.name);
                                if(seq.ext != null) {
                                        r += ' extends ' + seq.ext;
                                }
                                r += ' {';
                                if(seq.body != null) {
```

結果こんな感じになります。

さ、ビルドして、インストールして、コンパイル

```
$ node build.js
$ npm install -g
/usr/local/bin/mammouth -> /usr/local/lib/node_modules/mammouth/bin/mammouth
mammouth@0.2.1 /usr/local/lib/node_modules/mammouth
$ mammouth -c ../mam -o ../out
[ { type: 'block', elements: [ [Object] ] },
  { type: 'embed', content: '\n' } ]
[ { type: 'block', elements: [ [Object], [Object], [Object] ] },
  { type: 'embed', content: '\n' } ]
[ { type: 'block',
    elements: [ [Object], [Object], [Object], [Object] ] },
  { type: 'embed', content: '\n\n' } ]
```

結果を見ると

```
$ cat ../out/ext.php 
<?php 
class A {	
	function foo() {
		echo('foo');}
}
class B extends A {}
$b = new A();
$b->foo();
?>

```
継承出来てますね。

実行してみましょう。

```
$ php ../out/ext.php 
foo
```

動きましたァァァァ!!!!!

## まとめ

MamMouthはPHPを出力するトランスレータ言語で、JavaScriptで作ってあります。
最初にインストールして使ってみました。
次にソースを拾って来てビルドしてみました。
継承するテストコードを作ってエラーが出るようにしてパーサを作ってエラーが出ないようにしました。
最後にコンパイラが出来ていないので継承はされていないので、コンパイラを修正して継承出来るようにしました。

MamMouthは簡単に言語を作って遊べる楽しい言語です。
ぜひとも弄って遊んでみてください。