# matcher
[](https://hex.pm/packages/matcher_erl)
[](https://hexdocs.pm/matcher_erl)
[](https://opensource.org/licenses/MIT)
A simple expression matcher and evaluator for Erlang.
Evaluate tuple-based expressions for comparisons, boolean logic, and substring matching — with optional case-insensitive variants and pluggable value providers.
## Installation
Add `matcher_erl` to your `rebar.config` dependencies:
```erlang
{deps, [
{matcher_erl, "~> 1.0"}
]}.
```
Or directly from GitHub:
```erlang
{deps, [
{matcher, {git, "https://github.com/ratopi/matcher.git", {tag, "1.1.0"}}}
]}.
```
Then run:
```shell
rebar3 compile
```
## Quick Start
```erlang
%% Simple equality
true = matcher:eval({'=', <<"Albert">>, <<"Albert">>}).
%% Case-insensitive equality
true = matcher:eval({'=~', <<"Albert">>, <<"albert">>}).
%% Boolean logic
true = matcher:eval({'|', [
{'=', <<"A">>, <<"B">>},
{'=', <<"A">>, <<"A">>}
]}).
%% Substring matching
true = matcher:eval({'?', <<"bert">>, <<"Albert">>}).
```
## Expressions
Expressions are tuples in one of the following forms:
```erlang
{Op, Arg} %% Unary (e.g. not)
{Op, Arg1, Arg2} %% Binary (e.g. eq, lt, part_of)
{Op, [Arg, ...]} %% N-ary (e.g. and, or)
```
Expressions can be **nested** — any argument can be an expression again:
```erlang
{'&', [{'=', <<"Albert">>, <<"Albert">>}, {'!', {'=', <<"A">>, <<"B">>}}]}
```
## Providers
`eval/2` and `match/2` accept a **Provider** as the first argument. A provider is a `fun/1` that resolves values that cannot be evaluated further.
### Map as Provider
The most common use case — look up atom keys in a map:
```erlang
Map = #{name => <<"Albert">>, age => 42}.
true = matcher:eval(Map, {'=', name, <<"Albert">>}).
true = matcher:eval(Map, {'>', age, 18}).
false = matcher:eval(Map, {'=', name, <<"Bob">>}).
```
`matcher:eval(Map, Expr)` is shorthand for `matcher:eval(map_provider(Map), Expr)`.
### Custom Provider
You can use any `fun/1`:
```erlang
Provider = fun
(temperature) -> 23.5;
(unit) -> <<"°C">>;
(X) -> X
end.
true = matcher:eval(Provider, {'>', temperature, 20}).
```
You can even implement custom operators in your provider by handling tuples like `{my_op, A, B}`.
## `eval` vs `match`
| Function | Returns | On unknown operator |
|----------|---------|---------------------|
| `eval/1`, `eval/2` | Any value | Returns unevaluated expression as-is |
| `match/1`, `match/2` | `true`, `false`, or `{error, ...}` | `{error, {unevaluated_expression, Expr}}` |
## Operators
### Unary (one operand)
| Short | Long | Description |
|-------|------|-------------|
| `!` | `not` | Logical negation |
### N-ary (list of operands)
| Short | Long | Description |
|-------|------|-------------|
| `&` | `and` | Logical AND (short-circuit) |
| `\|` | `or` | Logical OR (short-circuit) |
### Binary (two operands)
| Short | Long | Case-insensitive | Description |
|-------|------|------------------|-------------|
| `<` | `lt` | `<~` | Less than |
| `>` | `gt` | `>~` | Greater than |
| `>=`, `=>` | `ge` | `>=~`, `=>~` | Greater than or equal |
| `<=`, `=<` | `le` | `<=~`, `=<~` | Less than or equal |
| `=`, `==` | `eq` | `=~` | Equal |
| `?` | `part_of` | `?~` | First string is part of second |
Case-insensitive operators can also be written in long form, e.g. `{eq, case_insensitive}`.
## Types
The library exports the following types:
```erlang
-type provider() :: fun((term()) -> term()).
-type expression() :: {unary_op(), expression()}
| {binary_op(), term(), term()}
| {nary_op(), [expression()]}
| term().
```
See the [module documentation](https://hexdocs.pm/matcher_erl/matcher.html) for full type definitions.
## Testing
```shell
rebar3 ct
```
## License
MIT — see [LICENSE](LICENSE) for details.
I'd love to hear from you if you find this library useful. :-)
## Links
- [Hex package](https://hex.pm/packages/matcher_erl)
- [Documentation](https://hexdocs.pm/matcher_erl)
- [GitHub](https://github.com/ratopi/matcher)