Skip to main content

lib/regex_solver.ex

defmodule RegexSolver do
  @moduledoc """
  Regex operations using Rust's [RegexSolver](https://github.com/RegexSolver/regexsolver)
  """
  version = Mix.Project.config()[:version]
  github_url = Mix.Project.config()[:package][:links]["GitHub"]

  use RustlerPrecompiled,
    otp_app: :regex_solver,
    base_url: "#{github_url}/releases/download/v-#{version}",
    force_build: System.get_env("RUSTLER_BUILD") in ["1", "true"],
    targets:
      Enum.uniq(["aarch64-unknown-linux-musl" | RustlerPrecompiled.Config.default_targets()]) --
        ["riscv64gc-unknown-linux-gnu"],
    version: version

  @type op_error :: :invalid_regex | :empty_intersection

  @doc """
  Computes the intersecion of two regular expressions

  ## Examples
      iex> RegexSolver.intersect(~r/[a-z]+/, ~r/abc|def/)
      {:ok, "(abc|def)"}

      iex> RegexSolver.intersect("[A-Z]+_[A-Z]+", "ENV_[A-Z]+")
      {:ok, "ENV_[A-Z]+"}

      iex> RegexSolver.intersect(~r/[0-9]+/, ~r/[A-Z][a-z]+/)
      {:error, :empty_intersection}

      iex> RegexSolver.intersect("a|b", "b")
      {:ok, "b"}

      iex> RegexSolver.intersect("a)", "a|b|c")
      {:error, "invalid regex: a)"}
  """
  @spec intersect(Regex.t() | String.t(), Regex.t() | String.t()) ::
          {:ok, String.t()} | {:error, op_error()}
  def intersect(%Regex{} = re1, re2), do: intersect(re1.source, re2)
  def intersect(re1, %Regex{} = re2), do: intersect(re1, re2.source)

  def intersect(re1, re2) when is_binary(re1) and is_binary(re2) do
    case intersection(re1, re2) do
      {:ok, "[]"} -> {:error, :empty_intersection}
      {:ok, pattern} -> {:ok, pattern}
      {:error, _reason} = error -> error
    end
  end

  defp intersection(_str1, _str2), do: :erlang.nif_error(:nif_not_loaded)
end