私は、BrainfuckにコンパイルするLISPっぽい言語を書くことを目指しています。まあ、それは実際には中間コンパイラのスタックです。現在、私はこのコードを変換するものを書き込もうとしています:
a++b+a
に:
++>+<
これは標準のBrainfuckですが、変数が追加されています。
コンパイラーは2つのことを担当します。
a
とb
にメモリ内のアドレス、たとえば0
と1
を割り当てるa
とb
の各オカレンスをいくつかの<
または>
に変換して、メモリポインタが正しい場所に配置されるようにしますさて、これが重要です。コンパイラが<
をいくつ置くかを知るためには、メモリポインタが現在どこを指しているかを知る必要があります。最初は0
を指しますが、コードが実行されると、ポインターが移動し、a
に到達するには、現在の場所から比較的移動する必要があります。 。
したがって、メモリポインタの値を知る唯一の方法は、コードを実行し、変数名を置き換えることです。 「ああ、mempointは4になりました。a
が表示され、場所は1なので、左に3か所移動する必要があります:<<<
」
目を閉じると、「停止性問題を解こうとしている」という声が聞こえます。
だから私の質問は:私は本当にですか?回避策はありますか?
replace<
および>
を変数で置き換えるか、実際にadd変数を言語に追加するかによって異なります。最初のケースでは、チューリング完全性が失われる可能性があります(メモリセルの数が有限であるため)。各セルが任意の大きな値を保持できる場合、チューリング完全のままになる可能性がありますが、正直なところわかりません。
とにかく、<
および>
操作がない場合、変換は簡単です。コードが最後にアクセスした変数がわかっているため、前後の変数のアドレスを比較するだけで済みます。 a+[b+]
のようなコードでは、現在のセルがループ本体とループの終了後の両方でa
またはb
になる可能性があるため、ループだけが少し注意が必要です。簡単な回避策の1つは、すべてのパスを同じセルで終了するように強制することです。つまり、コードをa+[b+a]
またはa+b[b+]
(a+b[+]
に簡略化できます)に書き換えます。
裸の>
と<
がソース言語で引き続き利用できる場合は、さらに注意が必要です。コメントが指摘しているように、Brainfuckはチューリング完全であるため、十分に複雑な同等のプログラムを構築できますが、シンプルで効率的な翻訳が常に可能であるとは限りません。特定のポイントで現在のセルを静的に決定するには、一般に、そのポイントまでのすべてのループのトリップカウントを知る必要があります。停止問題による潜在的な減少を無視しても(実際にここに当てはまるとは思いませんが)、トリップカウント、したがってメモリ位置は入力に依存することがよくあります。つまり、Oracleマシンでも静的に予測することはできません。
さて、私自身の質問に答えるために、そこには回避策があります。
<
の数を静的に計算することは、ほとんどの人が示唆していないように、実際には不可能です。コードが実行されるかどうかは、入力に依存する場合があり、それを回避する方法はありません。例:,[>]
したがって、それを行う唯一の方法は、実行時にセルを検索することです。メモリ空間の先頭(アドレス0)を見つけることができた場合は、右に2回ジャンプして、アドレス2に到達できます。
Brainfuckではゼロを見つけるのは簡単です:[<] # do something here
ただし、値が0
である任意のセルで停止したくない場合は、それが実際のメモリの先頭ゼロであることを確認する必要があります。 !
これは本当に不便です。開始アドレスを表すためにどの値を選択しても、誤ってメモリに表示される可能性があります。
だから私はメモリを私が呼ぶものメモリ空間と値空間に分割しました。奇数のセルは値であり、偶数のセルは「アドレス」です。まあ、セルを識別する正確に一意の番号はありませんが、移動するために使用する「はしご」のようなものがあります。
マップされた後のメモリのレイアウトは次のとおりです。
memory = [0, 0, 1, 0, 1, 0 ...] start ^ ^ addr ^ value
これで、場所を検索するたびに、左<
に1回移動してアドレス空間に入り、ゼロが見つかるまでループしてから、右にn
回移動します。
このスキーマでは、プログラムを実行する前に、メモリを交互に1
と0
で埋める必要があります。固定メモリサイズを指定すると、コンパイラはこれを行うコードを生成できます。
このソリューションは完璧ではありません。まず、メモリの半分が失われます。 1
で埋めるのも面倒で、各プログラムの開始時に時間がかかります。
スキーマを反転して、アドレス0を1
でマークし、他のすべてのアドレスを0
のままにしてみませんか?そうすれば、メモリを1
でいっぱいにする必要はありません。
やってみよう。
必要なコードを単語に翻訳すると、次のようになります。
if / then / else
構文を作成する必要があります。 Brainfuckでは、これはいくつかの値を一時的な場所にコピーし(それらはたくさんあります!)、正しいブランチを実行してからクリーンアップすることを意味します。
しかし、私たちの場合、「コードを実行する」とは「他の場所に行って戻ってこない」ことを意味するため、一時変数をクリーンアップすることは不可能です。そして、それらが0
でない場合、あなたは再びスタートを見つけることができません。
私はこれを回避する方法を見つけようとしましたが、できませんでした。
コンパイラのコードは次のとおりです。 https://Gist.github.com/whoeverest/3b15f2a06fc32333f342