そうだ！TuringComplete(ゲーム)でBrainf**kを実行しよう！

## よみとばしポイント

限界派遣SESのみなさんお疲れ様です。私も限界派遣SESです。
ひとりアドカレ15日目です。

TuringCompleteをやってからずっとやりたかったBrainf\*\*kインタプリタを作成していこうと思います。

なお、この記事は前の記事を読んだり`TuringComplete`をプレイすると理解できる気がします。
筆者はこんなクソ記事読むより購入して遊ぶことをオススメします。

https://qiita.com/nikawamikan/items/0f7b6ab1589d83342da3

https://store.steampowered.com/app/1444480/Turing_Complete/

## Brainf\*\*kとは？

Brainf\*\*k(以下BF)は、2004年にUrban Müllerによって作成された、最小のチューリング完全言語です。
BFは、8つの命令のみで構成されています。

| 命令 | 意味                                                    |
| ---- | ------------------------------------------------------- |
| `+`  | ポインタが指す値をインクリメントする                    |
| `-`  | ポインタが指す値をデクリメントする                      |
| `>`  | ポインタをインクリメントする                            |
| `<`  | ポインタをデクリメントする                              |
| `[`  | ポインタが指す値が0なら、対応する`]`の直後              |
| `]`  | ポインタが指す値が0でないなら、対応する`[`の直後        |
| `,`  | 標準入力から1バイト読み込み、ポインタが指す先に書き込む |
| `.`  | ポインタが指す値を標準出力する                          |

これによってHello Worldを出力するBFプログラムは以下のようになります。

```bf
>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]
>++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++
.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++.
```

見ても読めませんね。

## TuringCompleteでBFインタプリタを作成する

まず、インタプリタを作る前にいくつかの課題があります。

### ベースとなるCPU

ベースとなるCPUはレベルFUNCTIONS終了時点で作成しているCPUをベースに使用しています。
そのため、スタックやメモリが搭載されたCPUを使用しています。

これだけだと、ファイル読み込みや出力表示などの機能がないため、それらを追加していきます。

![image-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1239781/5482192e-c202-b8bc-33f8-59c69d9c08a9.png)

### ファイル読み込み

TuringCompleteのサンドボックスモードでは`File Loader`を使ってファイルを読み込むことができます。

`File Loader`を配置すると、ファイルを選択するダイアログが表示されるので実行するファイルを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1239781/f542253c-d486-b66b-b7a4-3c55022d565f.png)

`Console`とLink Componentsで接続することで、BFファイルの中を確認することができます。  
今回は上記のHello Worldプログラムを設定しています。

![image-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1239781/906c866f-2d48-48ab-1c1a-34056c260f8c.png)


また、今回はファイルIOのことをあんまり考えていなかったので、レジスタ5番を廃止して、ファイルIOのカーソルの位置を管理するようにしました。  
データ取得の際もINPUTの部分から読み込むようになっているため、CPUがBF専用みたいになってしまっています。

しかし、BFを実行できるので実質TuringCompleteですね？

### 出力の表示

先ほど`Console`はファイルの中身を確認するために使用しましたが、出力を表示するためにも使用できます。

出力用の`RAM`を用意し、`Console`と接続します。

また出力されるたびに、`Counter`インクリメントして、メモリアドレスをインクリメントするようにしました。

![image-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1239781/c90fe215-894d-81e2-656b-85224a6481ae.png)


## プログラムの実装

このBFインタプリタを作る上でレジスタは固定の役割をもたせるようにしています。

| レジスタ | 機能                               |
| -------- | ---------------------------------- |
| REG0     | BFのメモリアドレスの管理           |
| REG1     | BFの命令の管理                     |
| REG2     | ループの開始アドレス               |
| REG3     | メモリアドレスの値                 |
| REG4     | メモリアドレスの値のインクリメント |

また、BFの命令に対応する値を定数として設定しています。

| 定数名   | 命令  |
| -------- | ----- |
| ptr_inc  | `>`   |
| ptr_dec  | `<`   |
| inc      | `+`   |
| dec      | `-`   |
| out      | `.`   |
| loop_s   | `[`   |
| loop_e   | `]`   |
| main_end | `EOF` |

上記を基にBFの命令を実装していきます。

### メインループ

まずはBFの命令を実行するメインループを作成します。  

```
const ptr_inc 62
const ptr_dec 60
const inc 43
const dec 45
const out 46
const loop_s 91
const loop_e 93
const main_end 0

label MAIN_LOOP
ORi INPUT 0 REG1
IF_EQi REG1 ptr_inc ptr_increment
IF_EQi REG1 ptr_dec ptr_decrement
IF_EQi REG1 inc increment
IF_EQi REG1 dec decrement
IF_EQi REG1 out output
IF_EQi REG1 loop_s loop_start
IF_EQi REG1 loop_e loop_end
IF_NOT_EQi REG1 main_end MAIN_LOOP

label STOP
IF_EQ REG0 REG0 STOP
```

`const`というのはこのゲームにおいての定数値を表します。
それぞれの命令に対応する値を設定しています。

`IF_EQi REG1 ptr_inc ptr_increment`という命令がありますが、これは`REG1`の値と`ptr_inc`の値が等しい場合、`ptr_increment`のラベルにジャンプするという命令です。

また、`IF_NOT_EQi REG1 main_end MAIN_LOOP`という命令がありますが、これは`REG1`の値と`main_end`の値が等しくない場合、`MAIN_LOOP`のラベルにジャンプするという命令です。

これにより、空文字が入力されるまでBFの命令を実行することができます。

TypeScriptで書くと以下のようなイメージです。
実際には関数ではなく、ジャンプ命令なので関数呼び出し後はメインループの先頭に戻るようになります。

```ts
const ptr_inc = 62;
const ptr_dec = 60;
const inc = 43;
const dec = 45;
const out = 46;
const loop_vs = 91;
const loop_e = 93;
const main_end = 0;

while (true) {
    const input_value = input();
    switch (input_value) {
        case ptr_inc:
            ptr_increment();
            break;
        case ptr_dec:
            ptr_decrement();
            break;
        case inc:
            increment();
            break;
        case dec:
            decrement();
            break;
        case out:
            output();
            break;
        case loop_vs:
            loop_start();
            break;
        case loop_e:
            loop_end();
            break;
        case main_end:
            break; // メインループを終了
        default:
            continue; // 未定義の命令はスキップ
    }
}
```

### ポインタのインクリメント

次に、`>`に対応する`ptr_increment`を実装します。

```
label ptr_increment
ADDi REG0 1 REG0
IF_EQ REG0 REG0 MAIN_LOOP
```

labelは先程のメインループでの呼び出し先になります。
`ADDi REG0 1 REG0`という命令は、`REG0`の値に1を加算し、`REG0`に代入するという命令です。
また、`IF_EQ REG0 REG0 MAIN_LOOP`という命令は、`REG0`の値と`REG0`の値が等しい場合、`MAIN_LOOP`のラベルにジャンプするという命令です。
これは常にTrueとなるため、純粋な`JUMP`命令と同じですが、`JUMP`命令はこのCPUには存在しないため、このような形で実装しています。

### ポインタのデクリメント

ほとんど`ptr_increment`と同じですが、`<`に対応する`ptr_decrement`を実装します。

```
label ptr_decrement
SUBi REG0 1 REG0
IF_EQ REG0 REG0 MAIN_LOOP
```

違うのは、`SUBi REG0 1 REG0`という命令で、`REG0`の値から1を減算し、`REG0`に代入するという命令です。

### メモリのインクリメント

`+`に対応する`increment`を実装します。

```
label increment
LOAD_MEM 0 REG0 REG3
ADDi REG3 1 REG4
SAVE_MEM REG0 REG4 0
IF_EQ REG0 REG0 MAIN_LOOP
```

まず、`LOAD_MEM 0 REG0 REG3`という命令は、`REG0`の値をメモリアドレス`REG3`から読み込み、`REG3`に代入するという命令です。  
0の位置は利用されていないため、ダミーの値を入れています。(私のCPUの実装では0以外だとバグが発生する)
次に、`ADDi REG3 1 REG4`という命令は、`REG3`の値に1を加算し、`REG4`に代入するという命令です。
最後に、`SAVE_MEM REG0 REG4 0`という命令は、`REG4`の値をメモリアドレス`REG0`に書き込むという命令です。
ここでも最後の0はダミーの値です。

### メモリのデクリメント

`-`に対応する`decrement`を実装します。

```
label decrement
LOAD_MEM 0 REG0 REG3
SUBi REG3 1 REG4
SAVE_MEM REG0 REG4 0
IF_EQ REG0 REG0 MAIN_LOOP
```

先ほどと実装はほとんど一緒で、`ADDi`の部分が`SUBi`に変わっているだけです。

### 出力

`.`に対応する`output`を実装します。

```
label output
LOAD_MEM 0 REG0 REG3
OR REG3 REG3 OUTPUT
IF_EQ REG0 REG0 MAIN_LOOP
```

`OR REG3 REG3 OUTPUT`という命令は、`REG3`の値を`OUTPUT`に書き込むという命令です。
`OUTPUT`は`Console`に接続されているため、`Console`に書き込むことができます。

### ループの開始

`[`に対応する`loop_start`を実装します。

```
label loop_start
OR REG5 REG5 REG2
IF_EQ REG0 REG0 MAIN_LOOP
```

`OR REG5 REG5 REG2`という命令は、`REG5`の値を`REG2`に書き込むという命令です。
`REG5`はレジスタではないですね。

`REG5`はファイルIOのカーソルの位置を管理するために使用しています。

### ループの終了

`]`に対応する`loop_end`を実装します。

```
label loop_end
LOAD_MEM 0 REG0 REG3
IF_EQi REG3 0 MAIN_LOOP
ADDi REG2 0 REG5
IF_EQ REG0 REG0 MAIN_LOOP
```

`LOAD_MEM 0 REG0 REG3`という命令は、`REG0`の値をメモリアドレス`REG3`から読み込み、`REG3`に代入するという命令です。
次に、`IF_EQi REG3 0 MAIN_LOOP`という命令は、`REG3`の値と0が等しい場合、`MAIN_LOOP`のラベルにジャンプするという命令です。
これにより、`[ ]`の中身が0の場合、`[ ]`の外にジャンプすることができます。

ただし、この実装では多重ループには対応していません。
もし、多重ループに対応する場合はスタックにループの開始アドレスを積むなどの実装が必要です。

## 実行してみる

実際にBFのHello Worldプログラムを実行します。
といってもスクショだけなので、イマイチ伝わらないですね。

![image-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1239781/36ecc7e1-624e-2082-2437-8a1bc9d40b23.png)


`Console`にHello Worldが表示されているのがわかります。

実行Tickはゲーム内で9.5Kとなっています。
やはり、BFはステップ数が多いですね。

ゆっくり実行するモードだと、BFのWeb実行ツールなどを眺めてる気分になれます。

https://kachikachi.net/brainfuck/

## コンパイラも作りたかった

今回はインタプリタを作成しましたが、コンパイラも作成したかったですね。

今回作成したインタプリタは、BFの命令を1つずつ実行しているため実行のステップが多くなってしまいます。
これは、コンパイラが直接機械語に変換されるため、単純な命令の組み合わせに変換されるためです。

簡単にいうと「`>`はどういう意味？」とコンピュータは毎回考えずに、「ポインタのインプリメント」というような単純な命令を与えるだけにするようにします。
これを人間に適用しようとすると、「すべこべ考えずに言われたことだけやっておけ！」という主張になります。
まるでコンパイラはパワハラ上司ですね。

また、コンパイラは最適化を行うことができるため、実行速度も向上します。
これもBFを例に出しますが、例えばBFコードでは`+`の文字列が非常に多く連続して登場することがあります。  
つまり、`+`の命令を連続で実行するのではなく、`+`の命令をまとめて実行することで、実行速度を向上させることができます。

いわゆるランレングス圧縮のようなものですね。
`+++++`は`+5`として扱うことで、`+`の命令を5回実行するよりも効率的になります。

これ以外にも様々な最適化が可能ですが、コンパイラはインタプリタを作るよりも難易度が高いため、今回は諦めました。

## まとめ

今回はBFインタプリタを作成しました。
実はツッコミどころとして標準入力が実装されていませんが、使ってる人みたこないので省略しています。

TuringCompleteでTuringCompleteな言語を実行することが出来て満足です。

それでは。

## 参考にしたもの

https://zenn.dev/praha/articles/4cd2beab7b09e8