# `Feel.Runtime.UnaryTests`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/runtime/unary_tests.ex#L1)

What it means for a value to satisfy unary tests (DMN 1.6, sections 7.3.2 and
10.3.2.10; notes 08 §4.6 and §4.8).

The analyzer lowers each test to an expression about the input `?`: `< 5` becomes
`? < 5`, `[1..5]` becomes `? >= 1 and ? <= 5`, and a value such as `"a"` becomes
`? = "a"` (D-40). Two things are left for the run, and are here:

  * `matches/3`: a test that is an expression whose value is only known at run time. Its
    value decides what the test means: a list is a set of alternatives, a range is
    containment, a boolean that is made from `?` is the answer, and anything else is
    equality with the input.
  * `satisfied/2`: the tests together. They are alternatives, so a `true` decides and a
    `null` is neither true nor false; the result is satisfied only when it is `true`, or
    for `not(...)` only when it is `false`.

Neither function gives `null`. A test that gives `null` (a `null` input against `< 5`, or
a number against `"a"`) is not satisfied, and a negated list of tests with such a result
is not satisfied either (U2 in the notes).

# `any`

```elixir
@spec any([boolean() | nil]) :: boolean() | nil
```

The three-valued OR of the values of some tests: a `true` decides, and anything that is not a `false`
leaves it open. It is what `in` gives for its tests (D-61), where `satisfied/2` turns the open case
into `false`.

    iex> Feel.Runtime.UnaryTests.any([false, true, nil])
    true
    iex> Feel.Runtime.UnaryTests.any([false, nil])
    nil
    iex> Feel.Runtime.UnaryTests.any([false, false])
    false
    iex> Feel.Runtime.UnaryTests.any([])
    false

# `matches`

```elixir
@spec matches(Feel.Value.t(), Feel.Value.t(), boolean()) :: Feel.Runtime.Env.outcome()
```

Returns whether the input matches the value of a test that is an expression.

`uses_input?` says whether the expression refers to `?`.

  * An expression that uses `?` and is a boolean (or `null`) is the answer itself.
  * A list matches when the input equals one of its elements.
  * A range matches when the input lies within it.
  * Any other value matches when the input equals it: a `null` too, if the expression does not use `?`.

The value decides and the type of the expression never does (D-209): an expression of `?` that is `null` is
`null`, which is not satisfied, as `null < 5` is, and a `null` that does not come from `?` (a name that is
`null`) is equal to a `null` input. The analysis lowers a test to `? = expression` only where that is the same.

## Examples

    iex> Feel.Runtime.UnaryTests.matches(Decimal.new(2), true, true)
    {:ok, true}
    iex> Feel.Runtime.UnaryTests.matches(Decimal.new(2), [Decimal.new(1), Decimal.new(2)], false)
    {:ok, true}
    iex> range = %Feel.Value.Range{start: Decimal.new(1), end: Decimal.new(5), start_included: true, end_included: false}
    iex> Feel.Runtime.UnaryTests.matches(Decimal.new(5), range, false)
    {:ok, false}
    iex> Feel.Runtime.UnaryTests.matches(nil, nil, false)
    {:ok, true}
    iex> Feel.Runtime.UnaryTests.matches(nil, nil, true)
    {:ok, nil}
    iex> Feel.Runtime.UnaryTests.matches(Decimal.new(2), "a", false) |> elem(0)
    :error

# `member`

```elixir
@spec member(Feel.Value.t(), Feel.Value.t(), boolean()) :: Feel.Runtime.Env.outcome()
```

Returns whether the value of the subject of `in` is a member of the value of one of its tests (D-61).

It is `matches/3`, except for a list, which Table 56 reads as `list contains`: the subject is in it when
it equals an element, or lies within an element that is a range, and an element that it cannot be
compared with is not equal to it, where `matches/3` makes that an error.

    iex> list = [Decimal.new(1), "a", Decimal.new(3)]
    iex> Feel.Runtime.UnaryTests.member(Decimal.new(3), list, false)
    {:ok, true}
    iex> Feel.Runtime.UnaryTests.member(Decimal.new(2), list, false)
    {:ok, false}
    iex> range = %Feel.Value.Range{start: Decimal.new(5), end: Decimal.new(9), start_included: true, end_included: true}
    iex> Feel.Runtime.UnaryTests.member(Decimal.new(6), [Decimal.new(1), range], false)
    {:ok, true}
    iex> Feel.Runtime.UnaryTests.member(true, [false, Decimal.new(2)], false)
    {:ok, false}

A subject that is found at the front stops the search:

    iex> list = [Decimal.new(1), "a", Decimal.new(3)]
    iex> Feel.Runtime.UnaryTests.member(Decimal.new(1), list, false)
    {:ok, true}

# `satisfied`

```elixir
@spec satisfied(:any | :positive | :negated, [boolean() | nil]) :: boolean()
```

Returns whether unary tests are satisfied, given the value of each test.

`kind` is `:any` for `-`, `:positive` for a list of tests, and `:negated` for
`not(...)`. Each value is `true`, `false` or `null`.

    iex> Feel.Runtime.UnaryTests.satisfied(:positive, [false, true])
    true
    iex> Feel.Runtime.UnaryTests.satisfied(:positive, [false, nil])
    false
    iex> Feel.Runtime.UnaryTests.satisfied(:negated, [false, false])
    true
    iex> Feel.Runtime.UnaryTests.satisfied(:negated, [false, nil])
    false
    iex> Feel.Runtime.UnaryTests.satisfied(:any, [])
    true

---

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