lib/gcode/model/program.ex

defmodule Gcode.Model.Program do
  defstruct elements: []
  alias Gcode.Model.{Block, Comment, Program, Tape}
  use Gcode.Result

  @moduledoc """
  A G-code program is the high level object which contains each of the G-code
  blocks, comments, etc.

  ## Example

      iex> Program.init()
      ...> |> Result.unwrap!()
      ...> |> Enum.count()
      0
  """

  @typedoc "A G-code program"
  @type t :: %Program{
          elements: [element]
        }

  @type element :: Block.t() | Comment.t() | Tape.t()
  @type error :: {:program_error, String.t()}

  @doc """
  Initialise a new, empty G-code program.

      iex> Program.init()
      {:ok, %Program{elements: []}}
  """
  @spec init :: Result.t(t())
  def init, do: ok(%Program{})

  @doc """
  Push a program element onto the end of the program.

      iex> {:ok, program} = Program.init()
      ...> {:ok, tape} = Tape.init()
      ...> Program.push(program, tape)
      {:ok, %Program{elements: [%Tape{}]}}
  """
  @spec push(t, element) :: Result.t(t, error)
  def push(%Program{elements: elements} = program, element)
      when is_list(elements) and
             (is_struct(element, Block) or is_struct(element, Comment) or is_struct(element, Tape)),
      do: ok(%Program{program | elements: [element | elements]})

  def push(%Program{elements: elements}, _element) when not is_list(elements),
    do: error({:program_error, "Program elements is not a list"})

  def push(%Program{}, element),
    do: error({:program_error, "Expected a valid program element, received #{inspect(element)}"})

  def push(program, _element),
    do: error({:program_error, "Expected a valid program, received #{inspect(program)}"})
end