# `Feel.Analysis.AllowedValues`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/analysis/allowed_values.ex#L1)

Compiles allowed values: those of a variable of a `Feel.Scope`, and those of the result of
an expression (D-138).

Allowed values are the source text of a list of unary tests, as in the `allowedValues` of a
DMN item definition. A variable can have more than these for its value: a type constraint (for the value as a whole), and
allowed values and type constraints for its members (D-161, step E); `compile_all/3` compiles all of them. `compile/3` reads the text of a variable, with the other names of the
scope in view, and analyzes the tests with `?` typed as the variable, so that `[0..150]` for a
number is `? >= 0 and ? <= 150`. For a variable declared as a list, the tests are for its
elements, as the `allowedValues` of a DMN item definition with `isCollection` are (notes 03,
section 4.3): `?` is typed as an element, and `each` is `true` in what `compile/3` returns.
`compile_result/4` does the same for the text given for the
result of an expression, with `?` typed as the expression. The result is the IR of the tests,
which `Feel.Analysis.Analyzer` puts in a `:verify_bindings` node (for a variable) or an
`:allowed` node (for the result), and what a later step reads ranges and sets of values from.

The tests of a constraint read the names of the scope as the expression does (D-170): converted to their declared types,
and verified against their allowed values, so that a name that the run turns down is `null` in a test, and a name that is
allowed has the type its allowed values give it (`age` with `<= max age` is cut by the type of `max age`, which is a type
that holds only if the value a test sees is one that is allowed). The constraints that are being compiled, and the ones whose
tests read them, are not applied to a name that is read, and it is read as it was bound as far as they go: a test that reads
the name it belongs to, or a cycle of two, does not loop, and the tests of `person.age` that read `person.limit` see a
`person` that the other constraints of it (those of `limit`) hold. The stances that leave a value as it is (`:report`,
`:audit`, `:trust`, `:ignore`) read every name as it was bound, as they did.

The tests are read and analyzed with the extensions of the expression (`:extensions`, D-81), so a test may call a function
that an extension adds, as the expression may.

A test that cannot be read or analyzed is an error with the code `:invalid_allowed_values`
at the span of the name that was read, or of the expression for its result. The warnings of a test that can are given the same
span, since the text of the tests is not the source of the expression.

# `compile_option`

```elixir
@type compile_option() ::
  {:span, Feel.Diagnostic.span() | nil}
  | {:limits, Feel.Limits.t()}
  | {:extensions, Feel.Extensions.t() | [atom()]}
  | {:reads, {Feel.Analysis.Declarations.stance(), [constraint()]} | nil}
```

An option of `compile/3` and `compile_result/4`. `:reads` is how the tests read the names of the scope: `nil` as they were
bound, or `{stance, unverified}`, the stance (`:enforce` or `:enforce_wide`) the expression is analyzed with and the
constraints that are not applied to a name that is read (D-170).

# `compiled`

```elixir
@type compiled() :: %{
  ir: Feel.IR.t(),
  free_names: MapSet.t(Feel.Name.t()),
  diagnostics: [Feel.Diagnostic.t()],
  each: boolean(),
  grain: Feel.Analysis.Grain.t(),
  steps: [Feel.Scope.step()],
  kind: Feel.Scope.kind(),
  index: non_neg_integer()
}
```

What `compile/3` returns: the IR of the tests, the names they read, their diagnostics, and whether the
tests are for each element of a list (`each`) and not for the value.

# `constraint`

```elixir
@type constraint() ::
  {Feel.Name.t(), [Feel.Scope.step()], Feel.Scope.kind(), non_neg_integer()}
```

A constraint of a name, as `Feel.Scope.constraints/2` has it: the name, the steps down to the member, the kind and the index.

# `accepts_nothing?`

```elixir
@spec accepts_nothing?(Feel.Type.t() | Feel.Type.Union.t(), Feel.IR.t()) :: boolean()
```

Whether the tests accept no value of the type, `null` apart. A type that the tests do not narrow (one with several kinds
besides `null`, a list, a context) is never taken for one that nothing satisfies.

    iex> {:ok, compiled} = Feel.Analysis.AllowedValues.compile_result(nil, ~s("a"), :number)
    iex> Feel.Analysis.AllowedValues.accepts_nothing?(:number, compiled.ir)
    true
    iex> {:ok, compiled} = Feel.Analysis.AllowedValues.compile_result(nil, "[0..10]", :number)
    iex> Feel.Analysis.AllowedValues.accepts_nothing?(:number, compiled.ir)
    false

# `at`

```elixir
@spec at(
  {:ok, compiled()} | :none | {:error, [Feel.Diagnostic.t(), ...]},
  Feel.Diagnostic.span() | nil
) ::
  {:ok, compiled()} | :none | {:error, [Feel.Diagnostic.t(), ...]}
```

What `compile/3` gave, for a read of the variable at another span: the diagnostics are given that span. The tests are
read once and put at the span of each read, and are those that reading them there would give.

    iex> scope = Feel.Scope.new(%{"age" => :number}, allowed_values: %{"age" => ~s(<= "x")})
    iex> compiled = Feel.Analysis.AllowedValues.compile(scope, "age")
    iex> {:ok, at} = Feel.Analysis.AllowedValues.at(compiled, {4, 7})
    iex> {:ok, ^at} = Feel.Analysis.AllowedValues.compile(scope, "age", span: {4, 7})
    iex> Enum.map(at.diagnostics, & &1.span)
    [{4, 7}]

# `compile`

```elixir
@spec compile(Feel.Scope.t(), Feel.Name.t(), [compile_option()]) ::
  {:ok, compiled()} | :none | {:error, [Feel.Diagnostic.t(), ...]}
```

Compiles the allowed values of a variable.

Returns `:none` when the variable has none. The name must be canonical.

## Options

  * `:span` - the span of the name in the expression that reads it, which goes on the
    diagnostics. `nil` by default.
  * `:limits` - a `t:Feel.Limits.t/0`.
  * `:extensions` - the extensions that are on (`Feel.Extensions`, D-81), a list or a resolved set: the tests
    are read and analyzed with them, as the expression is, so a test may call a function that an extension adds.

## Examples

    iex> scope = Feel.Scope.new(%{"age" => :number}, allowed_values: %{"age" => "[0..150]"})
    iex> {:ok, compiled} = Feel.Analysis.AllowedValues.compile(scope, "age")
    iex> {Feel.IR.kind(compiled.ir), compiled.free_names, compiled.each}
    {:unary_tests, MapSet.new([]), false}
    iex> scope = Feel.Scope.new(%{"ages" => {:list, :number}}, allowed_values: %{"ages" => "[0..150]"})
    iex> {:ok, compiled} = Feel.Analysis.AllowedValues.compile(scope, "ages")
    iex> compiled.each
    true
    iex> Feel.Analysis.AllowedValues.compile(Feel.Scope.new(%{"age" => :number}), "age")
    :none

# `compile_all`

```elixir
@spec compile_all(Feel.Scope.t(), Feel.Name.t(), [compile_option()]) ::
  {:ok, [compiled(), ...]} | :none | {:error, [Feel.Diagnostic.t(), ...]}
```

Compiles everything that a variable has constrained (D-161, step E): its allowed values, its type constraint, and those of its
members, each as `compile/3` does, with the `steps` down to the member and the `kind`. The tests of a member are tested
against the member (`?` is typed as it), and the allowed values of a list are for its elements, where a type constraint is
for the list.

Returns `:none` when the variable has no constraint, and the first error otherwise: a constraint that cannot be read, or
a member the declared type does not have.

    iex> scope = Feel.Scope.new(%{"xs" => {:list, :number}}, allowed_values: %{"xs" => "[0..10]"}, type_constraints: %{"xs" => "count(?) > 0"})
    iex> {:ok, [allowed, constraint]} = Feel.Analysis.AllowedValues.compile_all(scope, "xs")
    iex> {allowed.kind, allowed.each, constraint.kind, constraint.each}
    {:allowed_values, true, :type_constraint, false}
    iex> scope = Feel.Scope.new(%{"p" => {:context, %{"age" => :number}}}, allowed_values: %{{"p", ["age"]} => "[0..150]"})
    iex> {:ok, [age]} = Feel.Analysis.AllowedValues.compile_all(scope, "p")
    iex> age.steps
    ["age"]
    iex> scope = Feel.Scope.new(%{"p" => {:context, %{"age" => :number}}}, allowed_values: %{{"p", ["height"]} => "> 0"})
    iex> {:error, [diagnostic]} = Feel.Analysis.AllowedValues.compile_all(scope, "p")
    iex> diagnostic.code
    :invalid_allowed_values

# `compile_result`

```elixir
@spec compile_result(
  Feel.Scope.t() | nil,
  String.t(),
  Feel.Type.t() | Feel.Type.Union.t(),
  [
    compile_option() | {:declared, Feel.Type.t()}
  ]
) :: {:ok, compiled()} | {:error, [Feel.Diagnostic.t(), ...]}
```

Compiles the allowed values of the result of an expression, given as source text.

`type` is the type of the result, a `t:Feel.Type.t/0` or a `t:Feel.Type.Union.t/0`. The tests
run only for a result that is not `null`, so `?` has that type without the `null`. The scope
is the one the expression is analyzed in, or `nil` when it has none. The options are those
of `compile/3`, and `:declared`, the declared type of the result as a `t:Feel.Type.t/0` with its names resolved: when
it is a list (possibly marked `!`) the tests are for its elements, as those of a variable declared as a list are (D-51):
`?` has the type of the elements of `type`, and `each` is `true` in what is returned.

    iex> {:ok, compiled} = Feel.Analysis.AllowedValues.compile_result(nil, "[0..100]", :number)
    iex> Feel.IR.kind(compiled.ir)
    :unary_tests
    iex> {:error, [diagnostic]} = Feel.Analysis.AllowedValues.compile_result(nil, "[0..)", :number)
    iex> diagnostic.code
    :invalid_allowed_values
    iex> {:ok, compiled} = Feel.Analysis.AllowedValues.compile_result(nil, "[0..100]", [{:list, [:number]}], declared: {:list, :number})
    iex> compiled.each
    true

# `compile_result_constraint`

```elixir
@spec compile_result_constraint(
  Feel.Scope.t() | nil,
  Feel.ItemDefinitions.constraint(),
  Feel.Type.t() | Feel.Type.Union.t(),
  [compile_option() | {:declared, Feel.Type.t() | nil}]
) :: {:ok, compiled()} | {:error, [Feel.Diagnostic.t(), ...]}
```

Compiles one constraint of the result of an expression: what a declared type has the value, or a member of it, satisfy (the
constraints of `Feel.ItemDefinitions.variable/2`), in the order of `compile_all/3` for a variable.

`constraint` is `%{steps: steps, kind: :allowed_values | :type_constraint, source: text}`: the unary tests of `source` are for the
member of the result at the end of `steps` (a key of a context, `:each` for the elements of a list), or for the result itself
with none. The allowed values of a list are for its elements, and a type constraint is for the list. `type` is the type of the
result, and `:declared` the declared type with its names resolved, which the steps go down; a member it does not have is an error
(`:invalid_allowed_values`), and so is any step when nothing is declared. The result has `steps` and `kind` of the constraint
(and `each` as for `compile_result/4`), which the `:allowed` node of the analyzer puts in its `meta`.

    iex> type = {:context, %{"age" => :number}}
    iex> constraint = %{steps: ["age"], kind: :allowed_values, source: "[0..150]"}
    iex> {:ok, compiled} = Feel.Analysis.AllowedValues.compile_result_constraint(nil, constraint, type, declared: type)
    iex> {compiled.steps, compiled.kind, compiled.each}
    {["age"], :allowed_values, false}
    iex> constraint = %{steps: ["height"], kind: :allowed_values, source: "> 0"}
    iex> {:error, [diagnostic]} = Feel.Analysis.AllowedValues.compile_result_constraint(nil, constraint, type, declared: type)
    iex> diagnostic.code
    :invalid_allowed_values
    iex> constraint = %{steps: [], kind: :type_constraint, source: "count(?) > 0"}
    iex> {:ok, compiled} = Feel.Analysis.AllowedValues.compile_result_constraint(nil, constraint, {:list, :number}, declared: {:list, :number})
    iex> {compiled.kind, compiled.each}
    {:type_constraint, false}

# `tests`

```elixir
@spec tests(Feel.IR.t()) :: Feel.IR.t()
```

The unary tests of what `compile/3` and `compile_all/3` gave, without the conversion and the verification of the names they
read (D-170): the tests are `ir` itself where they read no name with a declared type, and are inside the nodes of those
names where they do. It is what the sets of the narrowing and the proofs read.

    iex> scope = Feel.Scope.new(%{"age" => :number, "max" => :number}, allowed_values: %{"age" => "<= max", "max" => "[18..65]"})
    iex> {:ok, compiled} = Feel.Analysis.AllowedValues.compile(scope, "age", reads: {:enforce, ["age"]})
    iex> Feel.IR.kind(compiled.ir)
    :conform_bindings
    iex> Feel.IR.kind(Feel.Analysis.AllowedValues.tests(compiled.ir))
    :unary_tests

# `unsatisfiable?`

```elixir
@spec unsatisfiable?(
  Feel.Scope.t(),
  Feel.Name.t(),
  Feel.IR.t(),
  [Feel.Scope.step()],
  Feel.Scope.kind()
) ::
  boolean()
```

Whether the tests of a variable, as `compile/3` gave them, accept no value of its declared type (or of the type of its
elements, for a list), `null` apart: every value that is not `null` is then not allowed (D-138).

    iex> scope = Feel.Scope.new(%{"age" => :number}, allowed_values: %{"age" => ~s("a")})
    iex> {:ok, compiled} = Feel.Analysis.AllowedValues.compile(scope, "age")
    iex> Feel.Analysis.AllowedValues.unsatisfiable?(scope, "age", compiled.ir)
    true
    iex> scope = Feel.Scope.new(%{"age" => :number}, allowed_values: %{"age" => "[0..150]"})
    iex> {:ok, compiled} = Feel.Analysis.AllowedValues.compile(scope, "age")
    iex> Feel.Analysis.AllowedValues.unsatisfiable?(scope, "age", compiled.ir)
    false

---

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