Current section
Files
Jump to
Current section
Files
lib/program.ex
defmodule TuringMachine.Program do
@moduledoc """
Programs for a turing machine.
You can define programs directly constructing list of 5-tuples,
or generating from codes by `from_string/1` or `from_file/1`.
The 5-tuple `{0, 1, 2, :right, 3}` means a command that
when the machine state is `0`, and the value of tape at now position is `1`,
then turn it into `2`, go `:right` and make the machine state `3`.
"""
@type direction :: :right | :left | :stay | 1 | -1 | 0
@type t :: [
{
TuringMachine.state,
TuringMachine.value,
TuringMachine.value,
direction,
TuringMachine.state
}
]
@doc """
Transforms `direction` into diff of position.
"""
@spec direction_to_diff(direction) :: 1 | -1 | 0
def direction_to_diff(direction) do
case direction do
:right -> 1
:left -> -1
:stay -> 0
diff when is_integer(diff) -> diff
end
end
@doc """
Interprets string to a direction.
Returns `:error` when it fails, `{:ok, direction}` otherwise.
`R`, `L`, `S`, `right`, `left`, `stay`, `1`, `-1`, `0` are supported
(case sensitive).
"""
@spec direction_from_string(String.t) :: {:ok, direction} | :error
%{
"R" => :right,
"L" => :left,
"S" => :stay,
"right" => :right,
"left" => :left,
"stay" => :stay,
"1" => 1,
"-1" => -1,
"0" => 0,
}
|> Enum.each(fn {dir_str, dir} ->
def direction_from_string(unquote(dir_str)), do: {:ok, unquote(dir)}
end)
def direction_from_string(_other), do: :error
@doc """
Generates a program from the given `code`.
Each line of code is converted into a command.
For example, `"0, 1, 2, R, 3"` becomes `{"0", "1", "2", :right, "3"}`.
A command is described by comma separated 5-tuple.
Spaces before and after each element are trimmed.
Characters after `#` is ignored, so you can insert comments like:
`"0, 1, 2, R, 3 # This is a comment"`
The direction can be written as one of the followings:
`R`, `L`, `S`, `right`, `left`, `stay`, `1`, `-1`, `0`
Lines that doesn't match to the command pattern are just ignored.
"""
@spec from_string(String.t) :: t
def from_string(code) do
code
|> String.replace(~r/#.*/, "")
|> String.split("\n")
|> Enum.map(&String.split(&1, ","))
|> Enum.filter_map(
fn list -> length(list) == 5 end,
fn list -> Enum.map(list, &String.trim/1) end
)
|> Enum.filter_map(
fn [_, _, _, dir_str, _] ->
match?({:ok, _}, direction_from_string(dir_str))
end,
fn [state0, value0, value1, dir_str, state1] ->
{:ok, dir} = direction_from_string(dir_str)
{state0, value0, value1, dir, state1}
end
)
end
@doc """
Reads code from a file.
Raises when it fails to read the file.
"""
@spec from_file(String.t) :: t | none
def from_file(path) do
from_string(File.read!(path))
end
end