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} ### Known Limitations Currently, it is not possible to: - Use the `Kernel.in/2` macro in guards. *(see: [open issue](https://github.com/christhekeele/matcha/issues/2))* - Use the `Kernel.tuple_size/1` or `:erlang.tuple_size/1` guards. *(see: [documentation](https://hexdocs.pm/matcha/Matcha.Context.Common.html#module-limitations))* - This is a fundamental limitation of match specs. - Use any `is_record` guards (neither Elixir's implementation because of the `Kernel.tuple_size/1` limitation above, nor erlang's implementation for other reasons). *(see: [documentation](https://hexdocs.pm/matcha/Matcha.Context.Common.html#module-limitations))* - Both destructure values from a data structure into bindings, and assign the datastructure to a variable, except at the top-level of a clause. - This is how match specs work by design; though there may be a work-around using `:erlang.map_get/2` for maps, but at this time introducing an inconsistency doesn't seem worth it. """ alias Matcha.Context alias Matcha.Rewrite alias Matcha.Pattern alias Matcha.Spec alias Matcha.Trace @default_context_module Context.FilterMap @default_context_type @default_context_module.__context_name__() @spec pattern(Macro.t()) :: Macro.t() @doc """ Builds a `Matcha.Pattern` that represents a "filter" operation on a given input. Match patterns represent a "filter" operation on a given input, ignoring anything that does not fit the match "shape" specified. 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 @spec spec(Context.t(), Macro.t()) :: Macro.t() @doc """ Builds a `Matcha.Spec` that represents a "filter+map" operation on a given input. The `context` may be `:filter_map`, `:table`, `:trace`, 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 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: :filter_map> """ defmacro spec(context \\ @default_context_type, spec) defmacro spec(context, _spec = [do: clauses]) when is_list(clauses) do 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__) |> Rewrite.resolve_context() 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 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 @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 Trace.calls( unquote(module), unquote(function), Matcha.spec(unquote(Context.Trace.__context_name__()), unquote(spec)), unquote(opts) ) end end end