defmodule Matcha do @readme "README.md" @external_resource @readme @moduledoc_blurb @readme |> File.read!() |> String.split("") |> Enum.fetch!(1) @moduledoc_snippet @readme |> File.read!() |> String.split("") |> Enum.fetch!(1) @moduledoc """ #{@moduledoc_blurb} #{@moduledoc_snippet} """ alias Matcha.Context alias Matcha.Rewrite alias Matcha.Pattern alias Matcha.Spec alias Matcha.Trace @default_context Matcha.Context.FilterMap @spec pattern(Macro.t()) :: Macro.t() @doc """ Builds a `Matcha.Pattern` that represents a pattern matching operation on a given input. For more information on match patterns, consult the `Matcha.Pattern` docs. ## Examples iex> require Matcha ...> pattern = Matcha.pattern({x, y, x}) #Matcha.Pattern<{:"$1", :"$2", :"$1"}> iex> Matcha.Pattern.match?(pattern, {1, 2, 3}) false iex> Matcha.Pattern.match?(pattern, {1, 2, 1}) true """ defmacro pattern(pattern) do source = %Rewrite{env: __CALLER__, source: pattern} |> Rewrite.ast_to_pattern_source(pattern) quote location: :keep do %Pattern{source: unquote(source)} |> Pattern.validate!() end end defp do_spec(caller, context, clauses) do require Rewrite Enum.each(clauses, fn {:->, _, _} -> :ok other -> raise ArgumentError, message: "#{__MODULE__}.spec/2 must be provided with `->` clauses," <> " got: `#{Macro.to_string(other)}`" end) context = context |> Rewrite.perform_expansion(caller) |> Context.resolve() source = %Rewrite{env: caller, context: context, source: clauses} |> Rewrite.ast_to_spec_source(clauses) quote location: :keep do %Spec{source: unquote(source), context: unquote(context)} |> Spec.validate!() end end @spec spec(Context.t(), Macro.t()) :: Macro.t() @doc """ Builds a `Matcha.Spec` that represents a destructuring, pattern matching, and re-structuring operation in a given `context`. The `context` may be #{Context.__core_context_aliases__() |> Keyword.keys() |> Enum.map_join(", ", &"`#{inspect(&1)}`")}, or a `Matcha.Context` module. This is detailed in the `Matcha.Context` docs. For more information on match specs, consult the `Matcha.Spec` docs. ## Examples iex> require Matcha ...> Matcha.spec(:table) do ...> {x, y, x} ...> when x > y and y > 0 ...> -> x ...> {x, y, y} ...> when x < y and y < 0 ...> -> y ...> end #Matcha.Spec<[{{:"$1", :"$2", :"$1"}, [{:andalso, {:>, :"$1", :"$2"}, {:>, :"$2", 0}}], [:"$1"]}, {{:"$1", :"$2", :"$2"}, [{:andalso, {:<, :"$1", :"$2"}, {:<, :"$2", 0}}], [:"$2"]}], context: Matcha.Context.Table> """ defmacro spec(context, spec) defmacro spec(context, [do: clauses] = _spec) when is_list(clauses) do do_spec(__CALLER__, context, clauses) end defmacro spec(_context, _spec = [do: not_a_list]) when not is_list(not_a_list) do raise ArgumentError, message: "#{__MODULE__}.spec/2 must be provided with `->` clauses," <> " got: `#{Macro.to_string(not_a_list)}`" end defmacro spec(_context, not_a_block) do raise ArgumentError, message: "#{__MODULE__}.spec/2 requires a block argument," <> " got: `#{Macro.to_string(not_a_block)}`" end @spec spec(Macro.t()) :: Macro.t() @doc """ Builds a `Matcha.Spec` that represents a destructuring, pattern matching, and re-structuring operation on in-memory data. Identical to calling `spec/2` with a `:filter_map` context. Note that this context is mostly used to experiment with match specs, and you should generally prefer calling `spec/2` with either a `:table` or `:trace` context depending on which `Matcha` APIs you intend to use: - Use the `:trace` context if you intend to query data with `Matcha.Trace` functions - Use the `:table` context if you intend to trace code execution with the `Matcha.Table` functions ## Examples iex> require Matcha ...> Matcha.spec do ...> {x, y, x} ...> when x > y and y > 0 ...> -> x ...> {x, y, y} ...> when x < y and y < 0 ...> -> y ...> end #Matcha.Spec<[{{:"$1", :"$2", :"$1"}, [{:andalso, {:>, :"$1", :"$2"}, {:>, :"$2", 0}}], [:"$1"]}, {{:"$1", :"$2", :"$2"}, [{:andalso, {:<, :"$1", :"$2"}, {:<, :"$2", 0}}], [:"$2"]}], context: Matcha.Context.FilterMap> """ defmacro spec(spec) defmacro spec([do: clauses] = _spec) when is_list(clauses) do do_spec(__CALLER__, @default_context, clauses) end defmacro spec(_spec = [do: not_a_list]) when not is_list(not_a_list) do raise ArgumentError, message: "#{__MODULE__}.spec/1 must be provided with `->` clauses," <> " got: `#{Macro.to_string(not_a_list)}`" end defmacro spec(not_a_block) do raise ArgumentError, message: "#{__MODULE__}.spec/1 requires a block argument," <> " got: `#{Macro.to_string(not_a_block)}`" end @doc """ Traces `function` calls to `module`, executing a `spec` on matching arguments. Tracing is a powerful feature of the BEAM VM, allowing for near zero-cost monitoring of what is happening in running systems. The functions in `Matcha.Trace` provide utilities for accessing this functionality. One of the most powerful forms of tracing uses match specifications: rather that just print information on when a certain function signature with some number of arguments is invoked, they let you: - dissect the arguments in question with pattern-matching and guards - take special actions in response (documented in `Matcha.Context.Trace`) This macro is a shortcut for constructing a `spec` with the `:trace` context via `Matcha.spec/2`, and tracing the specified `module` and `function` with it via `Matcha.Trace.calls/4`. For more information on tracing in general, consult the `Matcha.Trace` docs. ## Examples iex> require Matcha ...> Matcha.trace_calls(Enum, :join, limit: 3) do ...> [_enumerable] -> message("using default joiner") ...> [_enumerable, ""] -> message("using default joiner (but explicitly)") ...> [_enumerable, _custom] -> message("using custom joiner") ...> end ...> Enum.join(1..3) # Prints a trace message with "using default joiner" appended "123" iex> Enum.join(1..3, "") # Prints a trace message with "using default joiner (but explicitly)" appended "123" iex> Enum.join(1..3, ", ") # Prints a trace message with "using custom joiner" appended "1, 2, 3" """ defmacro trace_calls(module, function, opts \\ [], spec) do quote do require Matcha.Trace Trace.calls( unquote(module), unquote(function), Trace.spec(unquote(spec)), unquote(opts) ) end end end