見出し画像

某ゲームをモチーフにした "だなも言語" というプログラミング言語を外出自粛中の時間を使って動くところまで作ってみた

まえおき

みなさん、こんにちは!めもりー (@m3m0r7) です!
なんと「ちょうぜつエンジニアめもりーちゃん」が Software Design さんにて連載されるようで、本当に本当におめでとうございます、と同時にぜひ皆さんもご購入いただけたらと思います。

フォーク元の私でさえ、いったいどうなるんだ…とものすごくワクワクしています。

"だなも言語" とは?

"だなも言語" とは、とあるゲームのキャラクターの語尾をもじって開発された言語です。その「とあるゲーム」とはみなさんが今もなおプレイしているであろう「あつまれ どうぶつの森」に登場する「たぬきち」というキャラクターの語尾です。

もちろん皆さんどうぶつの森、プレイしてますよね?私は花の水やり、住人のマネージメント、カブ価の確認、などなどやることが多いですね。

画像2

サーバー室の様子

画像3

怪しい葉っぱを育てている

画像4

車椅子でカンファレンスに参加する私を模した概念(ノンフィクション)

そろそろ本当の本題へ

一般的にネタ言語の開発は割と簡単に実装できる BrainF*ck を使われがちですが "だなも言語" はそうではありません。なんと日本語の文章でプログラムが動くのです。

https://gist.github.com/m3m0r7/a8c37e8bebf2200cd1a8e59262ffe636

実装元の言語は PHP ですが、もちろんその他の言語でも可能です。

例を見てみてください。

1+2の結果を出力するだなも

上記のようなコードを書くことでだなも言語は動きます。
どちらかというとなでしこに近いですね。

だなも言語の特徴はなんと言っても末尾が "だなも" で終わらないとパースエラーになります。

画像1

本当は動詞の活用とかも考えて実装するべきなんですが、若干手を抜きました。 4 時間で作ったネタ言語なので許して!

どういう動きをしているかはコードを見ればわかると思います。
ということでこのノートではどういう風に実装しているのかをご紹介します。

だなも言語は主に抽象構文木、いわゆる AST (Abstract Syntax Tree) に分解し、処理をします。

だなも言語の末尾につくだなもは一般的なプログラミング言語における ; か改行と同等のものであり、トークンの識別をする必要のないいわばノイズです。

とはいえ、だなも言語末尾にだなもがあってこそ成り立つ言語ですから、だなもがついていなければエラーを出すようにします。

$chars = mb_substr($line, -3, 3);
if ($chars !== 'だなも') {
   throw new RuntimeException('だなもで終わってないだなも!この構文はエラーになるんだなも!');
}

まぁ無理やりですね。ここは正直雑でもいいでしょう(よくない)

肝心の文章を AST に落とし込むところですが、酒に酔っていたということもあり、下記の条件で行いました。

- 「の」と「を」、「する」がついている文章を動詞として扱う。つまり関数実行と認識する。
- 「の」の場合文字列定義、数字定義、式定義などの場合があることを踏まえる。

たった、これだけの条件を元に実装を進めました。つまり、これをノードにすると

1+2| 
 +----結果を
        |
        +----出力する
                |
                +---だなも

上記のようになります。「の」、「を」、「する」がついたタイミングで関数が走ります。また、 1+2 自体も計算処理の最適化のため、上記のようなノードに変換した後に再度 AST 化します。

1, +
|
+--- 2

上記のようになります。1, +, 2 をそれぞれ left operand, operator, right operand とすると

[ 'left' => '1', 'operator' => '+', 'right' => ['left' => 2, 'operator' => null, 'right' => null ] ]

のようになり、right operand だけノードが深くなっていくような仕組みです。こうすることにより 1 + 2 + 3 となったときでも ((1 + 2) + 3) という計算順序を保証できるのです。

そして、 「の」, 「を」, 「する」,「だなも」は正直ノイズなので除外します。

1+2
 | 
 +----結果
        |
        +----出力

そうすると式がシンプルになり、扱いやすい形になります。これら、結果関数、出力関数を処理させていくと

3
|
+----出力

となり、出力関数に引数として 3 が渡され見事に出力されます。

だなも言語では直前までのノードを引数に渡すような設計にしています。
つまり、出力関数に渡される前までに 1+2 を結果関数での計算を行ってからその値を出力関数に渡しています。

ちなみにだなも言語は簡単にいうとトークンを下記の 4 つに分けています

- EXPRESSION (計算式)
- FUNCTION_CALL (関数呼び出し)
- NUMBER (数字)
- UNKNOWN (不明)

開発途中でもありよくわからんパースされるところは UNKNOWN を出してました。

それぞれ説明すると EXPRESSION は 1+1 などが該当して、 FUNCTION_CALL は結果出力といったものに対してラベリングされます。
NUMBER は計算式を AST にするさいにそれぞれの数字に割り振るためのものです。

そうするとだなも言語がどういう分割をされているかわかると思います。

1+2 (EXPRESSION)
| 
+----結果 (FUNCTION_CALL A)
       |
       +----出力 (FUNCTION_CALL B)

となります。だなも言語では一般的な言語とは違い、すべて前の処理を参照として第一引数に置くという特徴を持っています。こうすることによるメリットは開発工数も対してかからないというのがある他、引数定義をさせなくて済むというのがあります。まぁこれも開発工数がかからないという点に包含されるといえば包含されます。

つまり、上記のコードは FUNCTION_CALL B FUNCTION_CALL A の実行結果の引き数を持ち FUNCTION_CALL AEXPRESSION である 1+2 を引数として持っていることになるのです。

これを PHP で出力すると下記のような AST になります。

array(1) {
 [0]=>
 array(3) {
   [0]=>
   &array(3) {
     ["type"]=>
     int(0)
     ["code"]=>
     array(3) {
       ["left"]=>
       array(2) {
         ["type"]=>
         int(1000)
         ["value"]=>
         string(1) "1"
       }
       ["operator"]=>
       string(1) "+"
       ["right"]=>
       array(3) {
         ["left"]=>
         array(2) {
           ["type"]=>
           int(1000)
           ["value"]=>
           string(1) "2"
         }
         ["operator"]=>
         NULL
         ["right"]=>
         NULL
       }
     }
     ["func"]=>
     NULL
   }
   [1]=>
   &array(3) {
     ["type"]=>
     int(1)
     ["code"]=>
     &array(3) {
       ["type"]=>
       int(0)
       ["code"]=>
       array(3) {
         ["left"]=>
         array(2) {
           ["type"]=>
           int(1000)
           ["value"]=>
           string(1) "1"
         }
         ["operator"]=>
         string(1) "+"
         ["right"]=>
         array(3) {
           ["left"]=>
           array(2) {
             ["type"]=>
             int(1000)
             ["value"]=>
             string(1) "2"
           }
           ["operator"]=>
           NULL
           ["right"]=>
           NULL
         }
       }
       ["func"]=>
       NULL
     }
     ["func"]=>
     string(6) "結果"
   }
   [2]=>
   array(3) {
     ["type"]=>
     int(1)
     ["code"]=>
     &array(3) {
       ["type"]=>
       int(1)
       ["code"]=>
       &array(3) {
         ["type"]=>
         int(0)
         ["code"]=>
         array(3) {
           ["left"]=>
           array(2) {
             ["type"]=>
             int(1000)
             ["value"]=>
             string(1) "1"
           }
           ["operator"]=>
           string(1) "+"
           ["right"]=>
           array(3) {
             ["left"]=>
             array(2) {
               ["type"]=>
               int(1000)
               ["value"]=>
               string(1) "2"
             }
             ["operator"]=>
             NULL
             ["right"]=>
             NULL
           }
         }
         ["func"]=>
         NULL
       }
       ["func"]=>
       string(6) "結果"
     }
     ["func"]=>
     string(6) "出力"
   }
 }
}

だなも言語では EXPRESSION を直接処理することはなく、処理時に FUNCTION_CALL のみを実行するような仕組みになっています。そして、注目すべきポイントがもう一つあり、引数はすべて参照として渡されます。これには理由があります。開発工数を減らすため、というのもありますがきれいな言葉で言えば余分なメモリを使わないで済む、という点です。だなも言語は渡された AST を単純に上から順に処理していき、マッチする FUNCTION_CALL だけを実行します。つまり、 FUNCTION_CALL で実行された値が変われば、次実行される FUNCTION_CALL の引数の値も変わっていてほしいのです。したがって、すべてを参照渡しとすることでそれらをクリアしているのです。これらが実行されると下記のようになります。

まずはじめに FUNCTION_CALL である結果が実行されると下記のようになります。

array(1) {
[0]=>
array(3) {
  [0]=> 2                 <--- 計算済みの値が入る
  [1]=> 2                 <--- 計算済みの値が入る
  [2]=>
  array(3) {
    ["type"]=>
    int(1)
    ["code"]=> 2          <--- 計算済みの値が入る
    ["func"]=>
    string(6) "出力"
  }
}
}

その後に、出力が引数 (code) を持って実行します。雑な構造と実装なので code って名前おかしくない? arguments とかのほうがよくない?とかのツッコミは受け付けてません。

そして、だなも言語にはビルトイン関数が用意されています。出力もその一つです。

public function 出力($string): void
{
    echo $string . "\n";
}

渡された値をそのまま PHP の echo で出力しているだけです。(詳しくはコードを見てください)

上記がだなも言語のだいたいの実装概要です。ちなみに、処理している FUNCTION_CALL は 2 つだけですが、もちろん増やすことも可能で、だなも言語には階乗を計算する関数がデフォルトで用意されているので使うと下記のようになります。ついでに式も増やしましょう

1+2+3+4+5の結果の階乗を出力するだなも

これをだなも言語として分解すると下記のようになります。

1+2+3+4+5| 
    +----結果の
           |
           +----階乗を
                   |
                   +----出力する
                           |
                           +---だなも

実際の AST にすると

array(1) {
 [0]=>
 array(4) {
   [0]=>
   &array(3) {
     ["type"]=>
     int(0)
     ["code"]=>
     array(3) {
       ["left"]=>
       array(2) {
         ["type"]=>
         int(1000)
         ["value"]=>
         string(1) "1"
       }
       ["operator"]=>
       string(1) "+"
       ["right"]=>
       array(3) {
         ["left"]=>
         array(2) {
           ["type"]=>
           int(1000)
           ["value"]=>
           string(1) "2"
         }
         ["operator"]=>
         string(1) "+"
         ["right"]=>
         array(3) {
           ["left"]=>
           array(2) {
             ["type"]=>
             int(1000)
             ["value"]=>
             string(1) "3"
           }
           ["operator"]=>
           string(1) "+"
           ["right"]=>
           array(3) {
             ["left"]=>
             array(2) {
               ["type"]=>
               int(1000)
               ["value"]=>
               string(1) "4"
             }
             ["operator"]=>
             string(1) "+"
             ["right"]=>
             array(3) {
               ["left"]=>
               array(2) {
                 ["type"]=>
                 int(1000)
                 ["value"]=>
                 string(1) "5"
               }
               ["operator"]=>
               NULL
               ["right"]=>
               NULL
             }
           }
         }
       }
     }
     ["func"]=>
     NULL
   }
   [1]=>
[ ... 省略 ... ]
         ["func"]=>
         string(6) "結果"
       }
       ["func"]=>
       string(6) "階乗"
     }
     ["func"]=>
     string(6) "出力"
   }
 }
}

上記のように出力に階乗関数、そして結果関数が引数として渡されているのがわかりますね。実際に動いている動画は下記です。

以上で、だなも言語がどう実装されていて、動いているかの解説でした。
最後までご覧頂きありがとうございましただなも。

画像5


この記事が参加している募集

おうち時間を工夫で楽しく

この記事が気に入ったらサポートをしてみませんか?