diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index 7c8e8545494..ff15e3b89f6 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -6647,13 +6647,29 @@ defmodule Kernel do end defp compile_regex(binary_or_tuple, options) do - # TODO: Remove this when we require Erlang/OTP 28+ - case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do - true -> - Macro.escape(Regex.compile!(binary_or_tuple, :binary.list_to_bin(options))) + bin_opts = :binary.list_to_bin(options) - false -> - quote(do: Regex.compile!(unquote(binary_or_tuple), unquote(:binary.list_to_bin(options)))) + cond do + # TODO: Remove this when we require Erlang/OTP 28+ + is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] -> + Macro.escape(Regex.compile!(binary_or_tuple, bin_opts)) + + # TODO: Remove this when we require Erlang/OTP 28.1+ + is_binary(binary_or_tuple) and Code.ensure_loaded?(:re) and + function_exported?(:re, :import, 1) -> + %{re_pattern: exported_pattern, source: source, opts: opts} = + Regex.compile_export!(binary_or_tuple, bin_opts) + + quote do + %Regex{ + re_pattern: :re.import(unquote(Macro.escape(exported_pattern))), + source: unquote(source), + opts: unquote(opts) + } + end + + true -> + quote(do: Regex.compile!(unquote(binary_or_tuple), unquote(bin_opts))) end end diff --git a/lib/elixir/lib/regex.ex b/lib/elixir/lib/regex.ex index 8ca5a226d34..33e1e74daae 100644 --- a/lib/elixir/lib/regex.ex +++ b/lib/elixir/lib/regex.ex @@ -202,21 +202,23 @@ defmodule Regex do """ @spec compile(binary, binary | [term]) :: {:ok, t} | {:error, term} def compile(source, opts \\ "") when is_binary(source) do - do_compile(source, opts) + do_compile(source, opts, false) end - defp do_compile(source, opts) when is_binary(opts) do + defp do_compile(source, opts, export?) when is_binary(opts) do case translate_options(opts, []) do {:error, rest} -> {:error, {:invalid_option, rest}} translated_opts -> - do_compile(source, translated_opts) + do_compile(source, translated_opts, export?) end end - defp do_compile(source, opts) when is_list(opts) do - case :re.compile(source, opts) do + defp do_compile(source, opts, export?) when is_list(opts) do + compile_opts = if export?, do: [:export | opts], else: opts + + case :re.compile(source, compile_opts) do {:ok, re_pattern} -> {:ok, %Regex{re_pattern: re_pattern, source: source, opts: opts}} @@ -225,6 +227,11 @@ defmodule Regex do end end + @spec compile_export(binary, binary | [term]) :: {:ok, t} | {:error, term} + def compile_export(source, opts \\ "") when is_binary(source) do + do_compile(source, opts, true) + end + @doc """ Compiles the regular expression and raises `Regex.CompileError` in case of errors. """ @@ -236,6 +243,17 @@ defmodule Regex do end end + @doc """ + Compiles the regular expression and raises `Regex.CompileError` in case of errors. + """ + @spec compile_export!(binary, binary | [term]) :: t + def compile_export!(source, options \\ "") when is_binary(source) do + case compile_export(source, options) do + {:ok, regex} -> regex + {:error, {reason, at}} -> raise Regex.CompileError, "#{reason} at position #{at}" + end + end + @doc """ Recompiles the existing regular expression if necessary. @@ -506,6 +524,7 @@ defmodule Regex do """ @spec names(t) :: [String.t()] def names(%Regex{re_pattern: re_pattern}) do + re_pattern = maybe_import_pattern(re_pattern) {:namelist, names} = :re.inspect(re_pattern, :namelist) names end @@ -576,10 +595,15 @@ defmodule Regex do %Regex{source: source, opts: compile_opts} = regex :re.run(string, source, compile_opts ++ options) else - _ -> :re.run(string, re_pattern, options) + _ -> :re.run(string, maybe_import_pattern(re_pattern), options) end end + defp maybe_import_pattern({:re_exported_pattern, _, _, _, _} = exported), + do: :re.import(exported) + + defp maybe_import_pattern(re_pattern), do: re_pattern + @typedoc """ Options for regex functions that capture matches. """