LoginSignup
13
13

More than 5 years have passed since last update.

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

Posted at

はじめに

最近、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は簡単に言語を作って遊べる楽しい言語です。
ぜひとも弄って遊んでみてください。

13
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
13