git @ Cat's Eye Technologies pibfi / master src / bf.erl
master

Tree @master (Download .tar.gz)

bf.erl @masterraw · history · blame

%%% BEGIN bf.erl %%%
%%%
%%% bf - pedantic Brainf*ck interpreter in Erlang
%%% Copyright (c)2002 Cat's Eye Technologies.  All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions
%%% are met:
%%%
%%%   Redistributions of source code must retain the above copyright
%%%   notice, this list of conditions and the following disclaimer.
%%%
%%%   Redistributions in binary form must reproduce the above copyright
%%%   notice, this list of conditions and the following disclaimer in
%%%   the documentation and/or other materials provided with the
%%%   distribution.
%%%
%%%   Neither the name of Cat's Eye Technologies nor the names of its
%%%   contributors may be used to endorse or promote products derived
%%%   from this software without specific prior written permission.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
%%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
%%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
%%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
%%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
%%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
%%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
%%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
%%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
%%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
%%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
%%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
%%% POSSIBILITY OF SUCH DAMAGE. 

%% @doc Cat's Eye Technologies' Erlang Brainf*ck Interpreter.
%%
%% <p>An implementation of the world's most beautifulest langugage
%% in the world's second most beautifulest language.</p>
%% <p>This program demonstrates:</p>
%% <ul>
%% <li> sequential programming in Erlang </li>
%% <li> simulating Brainf*ck's updatable store by passing arguments </li>
%% </ul>
%% <p>Variable names used in this program:</p>
%% <ul>
%% <li> I = Instruction pointer </li>
%% <li> B = Brainf*ck program </li>
%% <li> D = Data pointer (tape head position) </li>
%% <li> M = Memory (Brainf*ck tape) </li>
%% </ul>
%%
%% @end

-module(bf).
-vsn('2002.1208').
-copyright('Copyright (c)2002 Cat`s Eye Technologies. All rights reserved.').

-export([interpret/1, interpret/2, test/1]).

%% @spec interpret(B::[instruction()], StorageSize::integer()) ->
%%   {integer(), tuple()}
%%         instruction() = integer()
%% @doc The main user interface to the Brainf*ck interpreter.
%% The user generally passes a list, which is parsed into a tuple.
%% In this tuple, each Brainf*ck instruction represented by
%% an atom, except for <code>[</code> and <code>]</code>,
%% which are represented by a nested tuple to make interpretation simpler.
%% The return value is a tuple containing the data pointer and
%% another tuple representing the state of the Brainf*ck tape
%% after all is said and done.

interpret(B, N) when tuple(B) -> interpret(1, B, 1, erlang:make_tuple(N, 0));
interpret(B, N) when list(B)  -> interpret(parse(B), N).

%% @spec interpret(B::[instruction()]) -> {integer(), tuple()}
%% @equiv interpret(Program, 512)

interpret(B) -> interpret(B, 512).  % default memsize, use interpret/2 to specify

%% @spec interpret(I::integer(), B::[instruction()], D::integer(), M::tuple) -> {integer(), tuple()}
%% @doc The internal driver which implements the execution loop.
%% When the I pointer is at the end of the program, processing is finished.
%% But more usually, I will be travelling forward through B.

interpret(I, B, D, M) when I > size(B) -> {D, M};
interpret(I, B, D, M) ->
  {D2, M2} = execute(element(I, B), D, M),
  interpret(I + 1, B, D2, M2).

%% @spec execute(instruction(), D::integer(), M::tuple) -> {integer(), tuple()}
%% @doc Executes specific, individual instructions.  Erlang doesn't have an
%% updatable store like Brainf*ck (unless you count the process dictionary
%% (which you probably shouldn't,)) so instead we approach the problem by
%% continually deriving new stores from old stores, returning the new
%% store to the caller each time this function is called.

execute($>, D, M) -> {D + 1, M};
execute($<, D, M) -> {D - 1, M};
execute($+, D, M) -> {D, setelement(D, M, element(D, M) + 1)};
execute($-, D, M) -> {D, setelement(D, M, element(D, M) - 1)};

%% <p>I/O is fairly crude, and could stand to be improved.</p>

execute($., D, M) -> io:put_chars([element(D, M)]), {D, M};
execute($,, D, M) -> {D, setelement(D, M, hd(io:get_chars('bf> ', 1)))};

%% <p>The 'while' loop.  A tuple represents a [...] structure; if
%% the data pointer points to a non-zero, the nested Brainf*ck
%% subprogram is executed, and the check is repeated.</p>

execute(B,  D, M) when tuple(B), element(D, M) == 0 -> {D, M};
execute(B,  D, M) when tuple(B) ->
  {D2, M2} = interpret(1, B, D, M),
  execute(B, D2, M2);

%% <p>Finally, comments and other line noise are ignored.</p>

execute(_, D, M) -> {D, M}.

%% @spec parse([instruction()]) -> tuple()
%% @doc Takes a string (list of ASCII values) and butchers
%% it into a nested tuple data structure, suitable for interpretation.
%% Writing this function elegantly
%% was much trickier than writing the imperative processing engine above.

parse(L) -> parse({}, L).  % default is to add to fresh tuple

parse(U, []) -> U;
parse(U, [$]|T]) -> {U, T};
parse(U, [$[|T]) ->
  {V, L} = parse({}, T),
  parse(erlang:append_element(U, V), L);
parse(U, [H |T]) ->
  parse(erlang:append_element(U, H), T).

%% @spec test(Test::integer()) -> {integer(), tuple()}
%% @doc Test functions, numbered from 1 upwards.  They implement two of the
%% programs from the original Brainf*ck archive (hello and atoi).

test(1) -> test(hello);
test(hello) -> interpret("
>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-]
<.>+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]>++++++++[
<++++>-]<+.[-]++++++++++."
);

test(2) -> test(atoi);
test(atoi) -> interpret("
==== ==== ====
cont digi num
==== ==== ====

+
[
 -                         cont=0
 >,
 ======SUB10======
 ----------
 
 [                         not 10
  <+>                      cont=1
  =====SUB38======
  ----------
  ----------
  ----------
  --------

  >
  =====MUL10=======
  [>+>+<<-]>>[<<+>>-]<     dup

  >>>+++++++++
  [
   <<<
   [>+>+<<-]>>[<<+>>-]<    dup
   [<<+>>-]
   >>-
  ]
  <<<[-]<
  ======RMOVE1======
  <
  [>+<-]
 ]
 <
]
>>[<<+>>-]<<
#"
).

%%% END of bf.erl %%%