Last active
October 26, 2017 17:26
-
-
Save 2bbb/2b3f6431098483ddf488035dd5d9ba8b to your computer and use it in GitHub Desktop.
brainfuck.exs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Brainfuck do | |
@seek_next ?> | |
@seek_prev ?< | |
@inc_head ?+ | |
@dec_head ?- | |
@print_head ?. | |
@read_input ?, | |
@loop_begin ?[ | |
@loop_end ?] | |
defmodule Tape do | |
defmodule TapeOutOfRangeError, do: defexception message: "Tape out of range." | |
@oneside :oneside | |
def oneside, do: @oneside | |
defmodule OneSide, do: defstruct head: 0, front: [], back: [], size: 0 | |
@bothside :bothside | |
def bothside, do: @bothside | |
defmodule BothSide, do: defstruct head: 0, front: [], back: [], size: 0 | |
defmodule Option, do: defstruct type: :oneside, size: 0 | |
@spec create(Tape.Option) :: Tape.OneSide | Tape.BothSide | |
def create(option) | |
def create(%Tape.Option{type: @oneside, size: size}), do: %Tape.OneSide{size: size} | |
def create(%Tape.Option{type: @bothside}), do: %Tape.BothSide{} | |
@spec head(TapeType) :: integer | |
def head(%{head: head}), do: head | |
@spec inc(TapeType) :: TapeType | |
def inc(tape), do: %{tape | head: rem(head(tape) + 1, 256)} | |
@spec dec(TapeType) :: TapeType | |
def dec(tape), do: %{tape | head: rem(256 + head(tape) - 1, 256)} | |
defprotocol Seek do | |
@spec next(TapeType) :: TapeType | |
def next(tape) | |
@spec prev(TapeType) :: TapeType | |
def prev(tape) | |
end | |
defimpl Seek, for: OneSide do | |
def next(%{head: head, front: front, back: [], size: 0}), do: %OneSide{head: 0, front: [head | front], back: []} | |
def next(%{head: head, front: front, back: [], size: size}) do | |
if size < length(front) + 2, do: raise TapeOutOfRangeError, "Maximum tape size is #{size}." | |
%OneSide{head: 0, front: [head | front], back: [], size: size} | |
end | |
def next(%{head: head, front: front, back: [next | back], size: 0}), do: %OneSide{head: next, front: [head | front], back: back} | |
def next(%{head: head, front: front, back: [next | back], size: size}) do | |
if size < length(front) + length(back) + 2, do: raise TapeOutOfRangeError, "Maximum tape size is #{size}." | |
%OneSide{head: next, front: [head | front], back: back, size: size} | |
end | |
def prev(%{front: []}), do: raise TapeOutOfRangeError, "The position of tape was made before head." | |
def prev(%{head: head, front: [prev | front], back: back}), do: %OneSide{head: prev, front: front, back: [head | back]} | |
end | |
defimpl Seek, for: BothSide do | |
def next(%{head: head, front: front, back: []}), do: %BothSide{head: 0, front: [head | front], back: []} | |
def next(%{head: head, front: front, back: [next | back]}), do: %BothSide{head: next, front: [head | front], back: back} | |
def prev(%{head: head, front: [], back: back}), do: %BothSide{head: 0, front: [], back: [head | back]} | |
def prev(%{head: head, front: [prev | front], back: back}), do: %BothSide{head: prev, front: front, back: [head | back]} | |
end | |
@spec print(TapeType) :: TapeType | |
def print(tape) do | |
IO.write <<head(tape)::utf8>> | |
tape | |
end | |
@spec read_to(TapeType) :: TapeType | |
def read_to(tape) do | |
<<value>> <> _ = String.trim(IO.gets "read byte:\n") | |
%{tape | head: value} | |
end | |
end | |
defmodule Commands do | |
defstruct curr: 0, rest: [], parsed: [], terminated: false | |
@type t(curr, rest, parsed, terminated) :: %Commands{curr: curr, rest: rest, parsed: parsed, terminated: terminated} | |
@type t :: %Commands{curr: integer, rest: List, parsed: List, terminated: Bool} | |
defmodule ParseError, do: defexception message: "Command parse error." | |
@spec next(Commands) :: Commands | |
def next(%{rest: [], terminated: true}), do: raise ParseError, "Rest of command is nothing." | |
def next(%{rest: [], terminated: false}), do: %Commands{terminated: true} | |
def next(cmd), do: %Commands{curr: hd(cmd.rest), rest: tl(cmd.rest), parsed: [cmd.curr | cmd.parsed]} | |
@spec prev(Commands) :: Commands | |
def prev(%{parsed: []}), do: raise ParseError, "Command is already top." | |
def prev(cmd), do: %Commands{curr: hd(cmd.parsed), rest: [cmd.curr | cmd.rest], parsed: tl(cmd.parsed)} | |
end | |
@spec execute(String, Tape.Option) :: :ok | :error | |
def execute(src, option \\ %Tape.Option{}) | |
def execute("", option), do: %Commands{terminated: true} |> step(option |> Tape.create) | |
def execute(src, option), do: with cmds <- to_charlist(src), do: %Commands{curr: hd(cmds), rest: tl(cmds)} |> step(option |> Tape.create) | |
@spec step(Commands, TapeType) :: :ok | :error | |
defp step(%Commands{terminated: true}, _tape), do: IO.puts "" | |
defp step(commands, tape) do | |
debug commands, tape | |
step(commands.curr, commands, tape) | |
end | |
@spec step(integer, Commands, TapeType) :: :ok | :error | |
defp step(@loop_begin, commands, tape), do: commands |> check_jump_to_end(tape |> Tape.head) |> step(tape) | |
defp step(@loop_end, commands, tape), do: commands |> check_jump_to_begin(tape |> Tape.head) |> step(tape) | |
defp step(curr, commands, tape), do: commands |> Commands.next |> step(tape |> (curr |> tape_operation).()) | |
@spec tape_operation(cmd :: integer) :: ((TapeType) :: TapeType) | |
defp tape_operation(@seek_next), do: &Tape.Seek.next/1 | |
defp tape_operation(@seek_prev), do: &Tape.Seek.prev/1 | |
defp tape_operation(@inc_head), do: &Tape.inc/1 | |
defp tape_operation(@dec_head), do: &Tape.dec/1 | |
defp tape_operation(@print_head), do: &Tape.print/1 | |
defp tape_operation(@read_input), do: &Tape.read_to/1 | |
defp tape_operation(_), do: fn x -> x end | |
@spec check_jump_to_end(Commands, integer) :: Commands | |
defp check_jump_to_end(commands, head), do: if head == 0, do: commands |> Commands.next |> search_loop_end(0), else: commands |> Commands.next | |
@spec search_loop_end(integer, Commands, integer) :: Commands | |
defp search_loop_end(@loop_end, commands, 0), do: commands |> Commands.next | |
defp search_loop_end(@loop_end, commands, num), do: commands |> Commands.next |> search_loop_end(num - 1) | |
defp search_loop_end(@loop_begin, commands, num), do: commands |> Commands.next |> search_loop_end(num + 1) | |
defp search_loop_end(_, commands, num), do: commands |> Commands.next |> search_loop_end(num) | |
@spec search_loop_end(Commands, integer) :: Commands | |
defp search_loop_end(commands, num), do: commands.curr |> search_loop_end(commands, num) | |
@spec check_jump_to_begin(Commands, integer) :: Commands | |
defp check_jump_to_begin(commands, 0), do: commands |> Commands.next | |
defp check_jump_to_begin(commands, _), do: commands |> search_loop_begin(0) | |
@spec is_loop_end(integer) :: integer | |
defp is_loop_end(cmd), do: if cmd == @loop_end, do: 1, else: 0 | |
@spec search_loop_begin(integer, Commands, integer) :: Commands | |
defp search_loop_begin(@loop_begin, commands, 0), do: commands |> Commands.next | |
defp search_loop_begin(@loop_begin, commands, num), do: commands |> Commands.prev |> search_loop_begin(num - 1) | |
defp search_loop_begin(_, commands, num), do: commands |> Commands.prev |> search_loop_begin(num + is_loop_end(Commands.prev(commands).curr)) | |
@spec search_loop_begin(Commands, integer) :: Commands | |
defp search_loop_begin(commands, num), do: commands.curr |> search_loop_begin(commands, num) | |
@debug false | |
defp debug(source, tape, enable \\ @debug) | |
defp debug(_, _, false), do: 0 | |
defp debug(source, tape, true) do | |
IO.puts "\n" | |
IO.inspect [source, tape] | |
end | |
end | |
defmodule Main do | |
def main do | |
# Brainfuck.execute("+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+.") | |
Brainfuck.execute("->+>+++>>+>++>+>+++>>+>++>>>+>+>+>++>+>>>>+++>+>>++>+>+++>>++>++>>+>>+>++>++>+>>>>+++>+>>>>++>++>>>>+>>++>+>+++>>>++>>++++++>>+>>++>+>>>>+++>>+++++>>+>+++>>>++>>++>>+>>++>+>+++>>>++>>+++++++++++++>>+>>++>+>+++>+>+++>>>++>>++++>>+>>++>+>>>>+++>>+++++>>>>++>>>>+>+>++>>+++>+>>>>+++>+>>>>+++>+>>>>+++>>++>++>+>+++>+>++>++>>>>>>++>+>+++>>>>>+++>>>++>+>+++>+>+>++>>>>>>++>>>+>>>++>+>>>>+++>+>>>+>>++>+>++++++++++++++++++>>>>+>+>>>+>>++>+>+++>>>++>>++++++++>>+>>++>+>>>>+++>>++++++>>>+>++>>+++>+>+>++>+>+++>>>>>+++>>>+>+>>++>+>+++>>>++>>++++++++>>+>>++>+>>>>+++>>++++>>+>+++>>>>>>++>+>+++>>+>++>>>>+>+>++>+>>>>+++>>+++>>>+[[->>+<<]<+]+++++[->+++++++++<]>.[+]>>[<<+++++++[->+++++++++<]>-.------------------->-[-<.<+>>]<[+]<+>>>]<<<[-[-[-[>>+<++++++[->+++++<]]>++++++++++++++<]>+++<]++++++[->+++++++<]>+<<<-[->>>++<<<]>[->>.<<]<<]") | |
# Brainfuck.execute("", %Brainfuck.Tape.Option{type: Brainfuck.Tape.oneside, size: 4}) | |
end | |
end | |
Main.main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment