パラレルブレインファック

ペースを落とさないようにしましょう。 今週はまだ終わっていないので、Brainfuckに関する次のトピックの時間はまだあります。 アイデアは私を捕まえましたが、すでに非常に多くのインタープリターの実装があったので、私は熱心になりたいです。 したがって、実験の目標として、BrainfuckのマルチスレッドバージョンであるBrainforkを選択しました 。 そしてツールとして-並列プロセスの実装に最適なErlang。 まだこのトピックにうんざりしていない人のために、私は猫の下を見ることを提案します。



Brainfork言語の原則は、Brainfuckの原則とまったく同じですが、1つの例外があります。追加の命令Yが追加され、プロセスをフォークし、親の現在のセルをゼロにし、子プロセスの次のセルをインクリメントします。 この場合、子のセルポインターも1つ右にシフトされます。



まず、コードを見てみましょう。以下にコメントを示します。



-module(bf). -export([start/1, code_server_loop/1, execute_loop/2]). start(ProgramString) -> Program = array:from_list(ProgramString), register(code, spawn(?MODULE, code_server_loop, [Program])), spawn(?MODULE, execute_loop, [{[], 0, []}, 0]). code_server_loop(Program) -> receive {get_token, Pid, Pos} -> reply(Pid, token, array:get(Pos, Program)), code_server_loop(Program); {get_left_brace, Pid, Pos} -> reply(Pid, left_brace, find_left_brace(Pos, Program)), code_server_loop(Program); {get_right_brace, Pid, Pos} -> reply(Pid, right_brace, find_right_brace(Pos, Program)), code_server_loop(Program); stop -> ok after 5000 -> self() ! stop, code_server_loop(Program) end. reply(Pid, ReplyType, Value) -> case Value of undefined -> Pid ! end_of_program; Value -> Pid ! {ReplyType, Value} end. find_left_brace(Pos, Program) -> find_left_brace(Pos - 1, Program, 0). find_left_brace(Pos, Program, Count) -> case array:get(Pos, Program) of $[ -> if Count == 0 -> Pos; true -> find_left_brace(Pos-1, Program, Count-1) end; $] -> find_left_brace(Pos-1, Program, Count+1); undefined -> undefined; _ -> find_left_brace(Pos-1, Program, Count) end. find_right_brace(Pos, Program) -> find_right_brace(Pos + 1, Program, 0). find_right_brace(Pos, Program, Count) -> case array:get(Pos, Program) of $] -> if Count == 0 -> Pos; true -> find_right_brace(Pos+1, Program, Count-1) end; $[ -> find_right_brace(Pos+1, Program, Count+1); undefined -> undefined; _ -> find_right_brace(Pos+1, Program, Count) end. get_code_server(MessageType, Position) -> code ! {MessageType, self(), Position}, receive end_of_program -> exit(normal); {_ReplyType, Reply} -> Reply end. get_token(Position) -> get_code_server(get_token, Position). get_left_brace(Position) -> get_code_server(get_left_brace, Position). get_right_brace(Position) -> get_code_server(get_right_brace, Position). execute_loop(Tape, CodePosition) -> Token = get_token(CodePosition), case execute_token(Token, Tape, CodePosition) of {skip, SkipPosition, NewTape} -> execute_loop(NewTape, SkipPosition); NewTape -> execute_loop(NewTape, CodePosition + 1) end. execute_token($., {_, C, _} = Tape, _) -> io:format("~c", [C]), Tape; execute_token($,, {L, _, R}, _) -> [C] = io:get_chars("> ", 1), {L, C, R}; execute_token($+, {L, C, R}, _) -> {L, C+1, R}; execute_token($-, {L, C, R}, _) -> {L, C-1, R}; execute_token($>, {L, C, []}, _) -> {[C|L], 0, []}; execute_token($>, {L, C, [RH|RT]}, _) -> {[C|L], RH, RT}; execute_token($<, {[], C, R}, _) -> {[], 0, [C|R]}; execute_token($<, {[LH|LT], C, R}, _) -> {LT, LH, [C|R]}; execute_token($[, {_, C, _} = Tape, Position) -> case C of 0 -> {skip, get_right_brace(Position) + 1, Tape}; _ -> Tape end; execute_token($], Tape, Position) -> {skip, get_left_brace(Position), Tape}; execute_token($Y, {L, _, R} = Tape, Position) -> fork(Tape, Position + 1), {L, 0, R}. fork({L, C, []}, Position) -> fork({L, C, [0]}, Position); fork({L, C, [RH|RT]}, Position) -> spawn(?MODULE, execute_loop, [{[C|L], RH+1, RT}, Position]).
      
      







コードはシンボリックに実行されます。 Mercuryでのbf実装のように、 ASTを構築するのは素晴らしいことです(それほど難しくありません)。 ただし、これにより、フォークのコードが大幅に複雑になります。 そして、解釈の速度の背後に競争がないので、実装は安価で怒っています。



子プロセスの初期化を簡素化するために、コードはすべてのプロセスで共有されます。 コードのサーバーがこれに関与しており、そのプロセスは、名前コードの下のstart関数の2行目で登録されています。 この場合、インタープリタープロセスはそのクライアントです。 サーバーは、特定の位置での指示の要求、および対応する左右のブラケットの位置を見つけるための補助機能を受け入れることができます。 このサーバーは、非アクティブな状態が5秒間続くと自動的にシャットダウンします(インタープリターのすべてのスレッドが何らかの方法で終了したか、終了しないことが明らかです)。



コード自体の解釈は非常に簡単です。サーバーに指示を求め、それを実行し、以下を求めます。 そして、サーバーがこれ以上コードがないことを通知するまで(end_of_program)。 その後、プロセスを完了します。 xonix habrazerからセルのテープを保存する方法を借りました(ありがとう、ありがとう!):これは、現在のセルの前のセルのリスト、現在のセル、および現在のセルの後のセルのリストを含むタプルです。 テープの潜在的な無限性だけでなく、利用可能な言語ツールで簡単に作業できるため、非常に便利であることがわかりました。



最も重要なことは、これがすべて記述されているため、execute_tokenの定義の最後の節とforkの4行のみに含まれています。 実際には、子プロセス。 ここではすべてが非常に簡単です。セルのリボンをわずかに変更することにより、新しいプロセスを生成します。



実験として、このようなコードを実行してみてください(これは拡張されたhaloworldです):

Y[-<]++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.





そして、それを数回実行して、結果が毎回異なることを確認することをお勧めします。これは、スレッド間の同期がないためです。



もちろん、コードは参照ではありません。 そして、トレーニング目的のためにもっと書きました。 だから、誰にも役立つとは思いません。 ただし、ブログは適切に選択されています。



All Articles