
BrainF**kコードを作成する独自言語とコンパイラを自作してみた② | ポインタの扱い
はじめに
このお話は前回↓からの続きです。今回はBrainF**k(以下BF)のポインタの扱いについてお話しします。
変数を使うにはメモリの存在とポインタの移動処理が欠かせませんが、BFという難解な言語では、そのポインタの移動さえも困難を極めます。
なので、BF上で簡単に変数を使ったりできるように、まずは簡単にポインタを移動できる仕組みを考えてみました。
もくじ
1. BFプログラムでの変数の表し方
2. 頭が混乱しないポインタの扱いの原則
3. Javaで実装 GOSUB
1.
BFプログラムでの変数の表し方
BFにて、以下のようなプログラムを考えましょう。
メモリ0番地に3を設定し、メモリ1番地に2を設定する。
そして、メモリ0番地にある数値とメモリ1番地にある数値を足し合わせたものをメモリ3番地に入れる。
こう書けるはずです。
{0}+++
>{1}++
<{0}
[{0}- >>>{3}+ <<<{0}]
>{1}
[{1}- >>{3}+ <<{1}]
実行が終わるとちゃんとメモリ3番地に5が設定されているはずです。こちら↓のインタプリタで実行すると分かりやすいです。
2.
頭が混乱しないポインタの扱いの原則
さて、1.では軽いプログラムを作ってみた訳ですが、ポインタの移動数を考えるのがとても大変ですね。コード中に{N}などというように今のポインタの位置をメモしておかないと、
+++
>++
<
[- >>>+ <<<]
>
[- >>+ <<]
メモリ3番地に行くためにはポインタを3回インクリメントする必要があるけど、今のポインタがメモリ1番地にいるからインクリメントは2回だけでいいのか。
そこからまたメモリ1番地に戻るには、2回デクリメントして、次にそこからメモリ2番地に行くには・・・?
みたいに混乱して今どこにいるのかがまるで分らない。
ということで、私はこういうような原則を設けてポインタの位置を整理することとしました。
原則
メモリN番地の操作をしたら一度必ずメモリ0番地に戻ってくる
BFは初期状態ではポインタがメモリ0番地にいます。このメモリ0番地をいわば拠点として置き、何かしらの処理をする際は
>>>>ポインタを指定の場所へ動かす
処理
<<<<拠点に戻ってくる
という作業をいちいち挟んでやることにしましょう。
そうして書き直したBFコードが以下の通りです。
{0}+++
>{1}++<
[{0}- >>>{3}+<<< {0}]
>{1}[< >{1}-< >>>{3}+<<< >]<
こうすることで、ポインタの移動数を考えるのが楽になりました。この原則を守りながらであれば、変数が多くなってもポインタの移動のことをあまり気にせずに複雑な処理も実装できるようになると思います。
3.
Javaで実装 GOSUB
このシリーズの目的は、BFコードを作成するような独自言語を作ることですが、とりあえず、上のような原則に則ったBFコードを生成するメソッドをJavaで作ってみました。メソッド名はGOSUBとしました。
まずはポインタを移動するBFコードを生成するメソッドを作ります。これはGOSUBで使う子メソッドになります。Pだけポインタを移動させるには
private String go(int P) {
if (P == 0) return "";
int absP = Math.abs(P);
char mover = P > 0 ? '>' : '<';
StringBuilder moverPTimesBuilder = new StringBuilder();
for (; absP > 0; absP--) moverPTimesBuilder.append(mover);
return moverPTimesBuilder.toString();
}
とします。例えば5だけポインタを移動させたいのなら
go(5)
とやれば
>>>>>
と出てきます。
次はGOSUBを実装しましょう。メモリP番地で処理processを行うという内容のBFコードを生成するメソッドになります。
public String GOSUB(int P, String process) {
return go(P) + process + go(-P);
}
例えばメモリ1番地で2加算する処理をしたければ
System.out.println(GOSUB(1, "++"))
と命令すれば
>++<
と出てきます。また、1. の例をこのメソッドを使って作ると
System.out.println(
GOSUB(0,"+++")+
GOSUB(1,"++")+
GOSUB(0,"[-")+
GOSUB(3,"+")+
GOSUB(0,"]")+
GOSUB(1,"[-")+
GOSUB(3,"+")+
GOSUB(1,"]")
);
という感じで作れて、
+++>++<[->>>+<<<]>[-<>>>+<<<>]<
という風に出てきます。これでポインタの移動のことを気にしなくても色々な変数を扱うことができるようになりました。
さいごに
次回(はあるのか?)はこのGOSUBを利用した色々な命令のラッパーメソッドやBFコードの最適化について解説しようと思います。