# `MatchSpec`
[🔗](https://github.com/e-xyza/match_spec/blob/main/lib/match_spec.ex#L1)

Elixir module to help you write matchspecs.

contains functions which transform elixir-style functions into erlang matchspecs,
and vice versa.

## Functions to matchspecs

For transforming elixir-style functions into matchspecs, the following
restrictions apply:

### Function form

- The function must use the `Kernel.fn/1` macro as its form, or use `defmatchspec/2`
  or `defmatchspecp/2`, where the matchspecs form is similar to the `Kernel.fn/1` form
- The function must have arity 1.

### Function argument matching

- The function may only match whole variables or tuple patterns.
- Only one tuple pattern may be matched.
- if your tuple contains a binary pattern match the binary pattern may only
  consist of bytes and strings, and only the `size/1` modifier is allowed.
  - bitstrings matching is not supported
  - conversions such as float are not supported.

### Guards and return expression

- The function may only use guards in its `when` section.
  - for `defmatchspec/2`, `defmatchspecp/2`, or `fun2msfun/4`: a`Kernel.in/2`
    guard may take a bound variable as its second parameter.
- The function may only return a single expression that (optionally) uses guard
  functions to transform matches.

### Examples (allowed)

#### Binding a top-level variable

```elixir
fn tuple -> tuple end
```

#### Top-level tuple match

```elixir
fn {_, _, value} -> value end
```

#### When with guard

```elixir
fn {_, _, value} when is_integer(value) -> value end
```

> #### Limitations on local guards {: .warning}
>
> local (`defguardp`) guards are only supported when the macro is inside
> of a function body, due to limitations on macro resolution timing.
>
> The following use cases are currently NOT supported:
>
> - `fun2ms/2` outside of a function body (inside the module body)
> - `fun2msfun/4` outside of function body (`:lambda`, `:def`, or `:defp`)
>
> The following use cases are currently supported:
>
> - `fun2ms/2` inside a function body
> - `fun2msfun/4` inside of a function body (`:lambda` only)
> - `defmatchspec/2` always
> - `defmatchspecp/2` always

#### Structure matching inside a tuple

```elixir
fn {%{key: a}} -> a end
```

#### Binary matching inside a tuple

```elixir
fn {<<"foo" :: binary, 42, a :: binary>>} -> a end
```

#### Result expression modification by guards

```elixir
fn {a, b} -> a + b end
```

### Examples (disallowed)

#### Arity not 1

```elixir
fn foo, bar -> foo + bar end
```

#### Multiple tuple matches

```elixir
fn {_, :foo} = {:bar, value} -> value end
```

#### Non-tuple top level match

```elixir
fn "foo" <> bar -> bar end
fn %{foo: bar} -> bar end
```

#### Disallowed type in binary match

```elixir
fn {<<foo :: integer-big-endian>>} -> foo end
```

#### Non-guard function in match

```elixir
fn {a} when String.starts_with?(a, "foo") -> a end
```

#### Non-guard function in result

```elixir
fn {a, b} -> a ++ b end
```

> #### Note {: .info}
>
> The restrictions on binary matching exist due to limitations on the BIFs
> available to ets and may change in the future if OTP comes to support
> these conversions in its kernel.

# `defmatchspec`
*macro* 

Writes a matchspec-generating function based on a body.

You may provide multiple function bodies.

This macro uses the same backend as `fun2msfun/4` and will generate
identical code to that macro.

Example:

```elixir
use MatchSpec

defmatchspec my_matchspec(value) do
  {key, ^value} when key !== :foo -> key
end
```

This generates the equivalent to the following function:

```elixir
def my_matchspec(value) do
  [{{:"$1", :"$2"}, [{:"=:=", :"$2", {:const, value}}, {:"=/=", :"$1", {:const, :foo}}], [:"$1"]]
end
```

# `defmatchspecp`
*macro* 

Writes a matchspec-generating function based on a body.

You may provide multiple function bodies.

This macro uses the same backend as `fun2msfun/4` and will generate
identical code.

see `defmatchspec/2` for details

# `fun2ms`
*macro* 

converts a function ast into an ets matchspec.

The function must also be "`fn` ast"; you can't pass a shorthand lambda or
a lambda to an existing lambda.

The function lambda form is only used as a scaffolding to represent ets
matching and filtering operations, by default it will not be instantiated
into bytecode of the resulting module.

```elixir
iex> require MatchSpec
iex> MatchSpec.fun2ms(fn {k, v} = tuple when v > 1 and v < 10 -> tuple end)
[{{:"$1", :"$2"}, [{:andalso, {:>, :"$2", 1}, {:<, :"$2", 10}}], [:"$_"]}]
```

You can use variables from the calling scope in the filters.

```elixir
iex> require MatchSpec
iex> my_atom = :foo
iex> MatchSpec.fun2ms(fn {k, _} = tuple when k === my_atom -> tuple end)
[{{:"$1", :_}, [{:"=:=", :"$1", {:const, :foo}}], [:"$_"]}]
```

If you would also like the equivalent lambda, pass `with_fun: true` as an
option and the `fun2ms/2` macro will emit a tuple of the matchspec and the
lambda.

```elixir
iex> {ms, fun} = MatchSpec.fun2ms(fn {:key, value} -> value end, with_fun: true)
iex> :ets.test_ms({:key, "value"}, ms)
{:ok, "value"}
iex> fun.({:key, "value"})
"value"
```

This macro uses the same backend as `fun2msfun/4` and will emit the same
matchspec as if you passed no parameters to `fun2msfun/4`

# `fun2msfun`
*macro* 

converts a function into a function that generates a match spec based on
bindings.

This can be used to either create a named function or an anonymous function.
If you would like to use one of the free variables in your function as a part
of the head of the match, you must pin it.

if you omit the first parameter, it will create an anonymous function.

### Basic example with `:lambda` (default):

```elixir
iex> require MatchSpec

# using a variable in the match
iex> lambda = MatchSpec.fun2msfun(:lambda, fn {key, value} when key === target -> value end, [target])
iex> lambda.(:key)
[{{:"$1", :"$2"}, [{:"=:=", :"$1", {:const, :key}}], [:"$2"]}]

#pinning a variable
iex> lambda2 = MatchSpec.fun2msfun(fn {^key, value} -> value end, [key])
iex> lambda2.(:key)
[{{:"$1", :"$2"}, [{:"=:=", :"$1", {:const, :key}}], [:"$2"]}]
```

Note that the `bindings` parameter acts like pattern matching on function
arguments:  They may use complex matches and there can be more than one, the
arity of the anonymous (or def/defp) function matches the length of the
`bindings` argument.

```elixir
iex> require MatchSpec
iex> lambda = MatchSpec.fun2msfun(:lambda, fn {^key, ^value} -> true end, [%{key: key}, value])
iex> lambda.(%{key: :key}, :value)
[{{:"$1", :"$2"}, [{:"=:=", :"$1", {:const, :key}}, {:"=:=", :"$2", {:const, :value}}], [true]}]
```

### Example with (`:def`/`:defp`):

```elixir
require MatchSpec

MatchSpec.fun2msfun(:def, :matchspec, fn {key, value} when key == target -> value end, [target])
```

# `ms2fun`

converts a matchspec into elixir AST for functions.  Unfortunately, the ast
generator cannot guess names for variables, so variable names are set by
the numerical value of the matchspec token

The second parameter takes two modes:

- `:ast` emits elixir ast to write a lambda.

```elixir
iex> MatchSpec.ms2fun([{{:"$1", :"$2"}, [], [:"$2"]}], :ast)

{:fn, [],
  [{:->, [], [[{:{}, [], [{:v1, [], nil}, {:v2, [], nil}]}], {:v2, [], nil}]}]}
```

- `:code` outputs formatted elixir code.

```elixir
iex> MatchSpec.ms2fun([{{:"$1", :"$2"}, [], [:"$2"]}, {{:"$1"}, [], [:"$_"]}], :code)

"""
fn
  {v1, v2} -> v2
  tuple = {v1} -> tuple
end
"""
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
