はじめに
最近、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
foo(); ?>継承出来てますね。
実行してみましょう。
$ php ../out/ext.php
foo
動きましたァァァァ!!!!!
## まとめ
MamMouthはPHPを出力するトランスレータ言語で、JavaScriptで作ってあります。
最初にインストールして使ってみました。
次にソースを拾って来てビルドしてみました。
継承するテストコードを作ってエラーが出るようにしてパーサを作ってエラーが出ないようにしました。
最後にコンパイラが出来ていないので継承はされていないので、コンパイラを修正して継承出来るようにしました。
MamMouthは簡単に言語を作って遊べる楽しい言語です。
ぜひとも弄って遊んでみてください。