Skip to content

Instantly share code, notes, and snippets.

@2bbb
Last active October 26, 2017 17:26
Show Gist options
  • Save 2bbb/2b3f6431098483ddf488035dd5d9ba8b to your computer and use it in GitHub Desktop.
Save 2bbb/2b3f6431098483ddf488035dd5d9ba8b to your computer and use it in GitHub Desktop.
brainfuck.exs
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