lib/matcha/pattern.ex

defmodule Matcha.Pattern do
  @moduledoc """
  About patterns.
  """

  alias __MODULE__

  alias Matcha.Context
  alias Matcha.Error
  alias Matcha.Rewrite
  alias Matcha.Source

  alias Matcha.Spec

  import Kernel, except: [match?: 2]

  defstruct [:source]

  @test_spec_context Matcha.Context.Match
  @default_to_spec_context @test_spec_context

  @type t :: %__MODULE__{
          source: Source.pattern()
        }

  @compile {:inline, source: 1}
  @spec source(t()) :: Source.pattern()
  def source(%__MODULE__{source: source} = _pattern) do
    source
  end

  @spec filter(t(), Enumerable.t()) :: Enumerable.t()
  def filter(%__MODULE__{} = pattern, enumerable) do
    with {:ok, spec} <- to_spec(@test_spec_context, pattern) do
      Spec.run(spec, enumerable)
    end
  end

  @spec match?(t(), term()) :: boolean()
  def match?(%__MODULE__{} = pattern, term) do
    case do_test(pattern, term) do
      {:ok, {:matched, ^term}} -> true
      _ -> false
    end
  end

  @spec match!(t(), term()) :: term() | no_return()
  def match!(%__MODULE__{} = pattern, term) do
    if match?(pattern, term) do
      term
    else
      raise MatchError, term: term
    end
  end

  @spec to_spec(context :: Context.t(), t()) :: {:ok, Spec.t()} | {:error, Error.problems()}
  def to_spec(context \\ @default_to_spec_context, %__MODULE__{} = pattern) do
    context
    |> Context.resolve()
    |> Rewrite.pattern_to_spec(pattern)
  end

  @spec validate(t()) :: {:ok, t()} | {:error, Error.problems()}
  def validate(%__MODULE__{} = pattern) do
    case do_test(pattern) do
      {:ok, _result} -> {:ok, pattern}
      {:error, problems} -> {:error, problems}
    end
  end

  @spec validate!(t()) :: t() | no_return()
  def validate!(%__MODULE__{} = pattern) do
    case validate(pattern) do
      {:ok, pattern} ->
        pattern

      {:error, problems} ->
        raise Pattern.Error,
          source: pattern,
          details: "when validating pattern",
          problems: problems
    end
  end

  defp do_test(%__MODULE__{} = pattern) do
    with {:ok, spec} <- to_spec(@test_spec_context, pattern) do
      Context.test(spec)
    end
  end

  defp do_test(%__MODULE__{} = pattern, match_target) do
    with {:ok, spec} <- to_spec(@test_spec_context, pattern) do
      Context.test(spec, match_target)
    end
  end
end