Skip to content

Instantly share code, notes, and snippets.

@wstucco
Last active October 9, 2022 01:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wstucco/bc6a5037fe8b1fbf1cf0 to your computer and use it in GitHub Desktop.
Save wstucco/bc6a5037fe8b1fbf1cf0 to your computer and use it in GitHub Desktop.
defmodule Brainfuck do
# opcodes
@op_vinc "+" # increment value at memory address
@op_vdec "-" # decrement value at memory address
@op_pinc ">" # increment memory address
@op_pdec "<" # decrement memory address
@op_putc "." # output byte at memory address
@op_getc "," # input byte into memory address
@op_lbeg "[" # loop begin
@op_lend "]" # loop end
@empty ""
def run(program), do: run(program, 0, [0], @empty)
# final condition
defp run(@empty, addr, mem, output), do: {addr, mem, output}
# commands
defp run(@op_vinc <> rest, addr, mem, output) do
run(rest, addr, mem |> inc_at(addr), output)
end
defp run(@op_vdec <> rest, addr, mem, output) do
run(rest, addr, mem |> dec_at(addr), output)
end
defp run(@op_pinc <> rest, addr, mem, output) do
run(rest, addr+1, mem, output)
end
defp run(@op_pdec <> rest, addr, mem, output) do
run(rest, addr-1, mem, output)
end
defp run(@op_putc <> rest, addr, mem, output) do
run(rest, addr, mem, output <> (mem |> char_at addr))
end
defp run(@op_getc <> rest, addr, mem, output) do
val = case IO.getn("Input\n", 1) do
:eof -> 0
c -> c
end
run(rest, addr, mem |> put_at(addr, val), output)
end
# drop every other character
defp run(<<_>> <> rest, addr, mem, output), do: run(rest, addr, mem, output)
# helpers
defp inc_at(list, addr), do: List.update_at(list, addr, &(&1+1 |> rem 255))
defp dec_at(list, addr), do: List.update_at(list, addr, &(&1-1 |> rem 255))
defp put_at(list, addr, val), do: List.replace_at(list, addr, val)
defp byte_at(list, addr), do: list |> Enum.at addr
defp char_at(list, addr), do: [list |> byte_at addr] |> to_string
end
@wstucco
Copy link
Author

wstucco commented Nov 5, 2014

First of all, thanks!
I was going to explain the auto expanding memory in the second part, but you guessed right.
I have only one advice: you're still thinking imperative, you don't need the if in Elixir, you can take advantage of guard clauses and basically add this two functions, this way you are modular and add the features you need, without modifying code that already works (I'll talk about testing Elixir code and the command line tools in part 3)

# we are moving past the end of the memory tape
defp run(@op_pinc <> rest, addr, mem, output) when addr + 1 == mem |> length do
  # append a new cell, initialize its value to zero, return the next address as new address
  run(rest, addr+1, mem ++ [0], output)
end

# we are moving to the left of the first cell of the memory tape
defp run(@op_pdec <> rest, addr, mem, output) when addr == 0 do
    # prepend a new empty cell, initialize its value to zero, return zero as new address
  run(rest, 0, [0] ++ mem, output)
end

I'm very glad you liked the first part, the second will be even more fun! :)

@mtwtkman
Copy link

Thanks for your reply!
And i'm glad to read next part this topic :)

I can feel that pattern matching and guard clauses are really elegant, beautiful.

@wstucco
Copy link
Author

wstucco commented Nov 11, 2014

Thanks to you!
the second part is out, you can read it at http://dev.mikamai.com/post/102283561929/elixir-as-a-parsing-tool-writing-a-brainfuck

@ylluminate
Copy link

@wstucco you might consider adding elixir benchmarks to: https://github.com/kostya/benchmarks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment