-
-
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 |
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! :)
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.
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
@wstucco you might consider adding elixir benchmarks to: https://github.com/kostya/benchmarks
Hi, I read your very cool post!
So, I guess when pointer increment operator given and address is end of list,
mem
should be added new element.(For example, line 28 would be like below?)
Very sorry for you, If I get wrong...
And sorry for my very poor English... ;-)
What is anyway, I'm looking forward to post your cool
Elixir Brainfuck part2
.