lib/chrysopoeia/sequence.ex

defmodule Chrysopoeia.Sequence do
  @moduledoc """
  Combinators for applying parsers in sequence.
  """

  alias Chrysopoeia, as: Chr

  @doc """
  A combinator. Takes a list of parsers, uses each parser in the
  list sequentially, and returns a list of their outputs.

  By wrapping a parser in `{:ig, parser}`, its output will not be included in
  the final output.
  """
  # I don't know how to spec this, because non-homogenous lists are weird.
  def list(parsers) do
    &list_parser(&1, parsers)
  end

  defp list_parser(str, []) do
    {:ok, [], str}
  end

  defp list_parser(str, [{:ig, parser} | parsers]) do
    with {:ok, _, str} <- parser.(str),
         {:ok, out, str} <- list_parser(str, parsers) do
      {:ok, out, str}
    end
  end

  defp list_parser(str, [parser | parsers]) do
    with {:ok, data, str} <- parser.(str),
         {:ok, out, str} <- list_parser(str, parsers) do
      {:ok, [data | out], str}
    end
  end

  @doc """
  A combinator. First applies the `prefix` parser, then applies `parser`, and
  returns the result of `parser`.
  """
  @spec prefix(Chr.parser(i, any(), e1), Chr.parser(i, o, e2)) :: Chr.parser(i, o, e1 | e2)
        when i: var, e1: var, o: var, e2: var
  def prefix(prefix, parser) do
    fn str ->
      with {:ok, _, str} <- prefix.(str) do
        parser.(str)
      end
    end
  end

  @doc """
  A combinator. First applies `parser`, then the `suffix` parser, and returns
  the result of `parser`.
  """
  @spec suffix(Chr.parser(i, o, e1), Chr.parser(i, any(), e2)) :: Chr.parser(i, o, e1 | e2)
        when i: var, e1: var, o: var, e2: var
  def suffix(parser, suffix) do
    fn str ->
      with {:ok, out, str} <- parser.(str),
           {:ok, _, str} <- suffix.(str) do
        {:ok, out, str}
      end
    end
  end
end