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

The names and types an expression may refer to, known before it runs.

`Feel.prepare/2` uses a scope to resolve names, to match names that contain
spaces or operator characters, and to check calls and infer types. A scope has
these parts:

  * `names` - the variables, with the type of each value they will be bound to.
    A function is a variable whose type is a function type.
  * `types` - the type names an expression may use, such as the item
    definitions of a DMN model.
  * `allowed_values` - for some variables, the values they may have, as the source
    text of a list of unary tests, as in the `allowedValues` of a DMN item definition
    (`"[0..150]"`, `""a", "b""`, `"< 100"`). See below.
  * `constraints` - the allowed values and type constraints for variables and their members,
    with key steps through contexts and `:each` through lists (`:type_constraints` in `new/2`).
  * `result_type` and `result_allowed_values` - what the expression gives: the type of its
    result, and the values it may have. They are the `typeRef` and the `allowedValues` of
    the variable of a DMN decision.

## Allowed values

The bound value of a variable with allowed values is verified when a run starts (D-138), for
the variables that the expression reads: a value that none of the tests accepts is replaced by
`null`, with a `:value_not_allowed` error, and `null` itself is exempt. The tests are tested
against the value as `?`, and may use the other names of the scope. They are read when an
expression that uses the variable is prepared, and a test that cannot be read is an error of that
expression, with the code `:invalid_allowed_values`. Their source is kept as it is written, since
a scope is data; `Feel.Analysis.AllowedValues` compiles it.

A variable declared as a list (`{:list, :number}`) has the tests applied to each element, as the
`allowedValues` of a DMN item definition with `isCollection` are (D-51): `"[0..10]"` for
`list<number>` means that every element, other than `null`, is in `[0..10]`. The first element
that is not makes the whole value `null`, with an error that says which. So are the allowed values of
the result, when its `result_type` is a list (D-138).

## Type constraints, and the members of a value

The `typeConstraint` of a DMN item definition is a list of unary tests like the allowed values, but for the value as a whole:
for a list, `"count(?) > 0"` says the list has an element, where allowed values are for each element. They are given as
`type_constraints`, by the name of the variable. For a value that is not a list the two are the same.

Both can be given for a **member** of a value, by a subject `{name, steps}`: the steps are the keys of the context to
go down, and `:each` to go to the elements of a list. `{"person", ["age"]}` is the age of a person, `{"orders", [:each,
"total"]}` the total of each order. The tests are for the member as the tests of a variable are for the variable, and a
member that is not there or is `null` is exempt. A value with a member that is not allowed is not allowed, as a list with
an element that is not (the whole value is `null`, with an error that says which member). They narrow the type of the
member, so `person.age` is a number in `[0..150]`.

## Names that are never `null`

In the specification `null` is a value of every type, so a variable declared `:number` may be
`null`, and so is anything computed from it. A type marked not null (`{:non_null, :number}`, or
`"number!"` for `Feel.Type.parse/2`, D-48) says that the variable is never `null`: with the default
`declarations: :enforce` the analysis types the expression without the `null` that the variable would
bring, and a run ends with an error of the kind `:input` if the bound value is `null`, is not
bound, cannot be converted to the type, or is not one of its allowed values. This is an extension
to standard FEEL: expression type syntax also accepts the mark in function parameters and
`instance of` targets. Preparation currently rejects an outer non-null result declaration
with `:not_implemented`; result constraints are not a guarantee that evaluation cannot return null.

## Types the lattice cannot say

The types of the specification have no union and no negation. A name that is anything but a number is `{:not, :number}`
(`"Any except number"`), and one that is a number or a string is `{:union, [:number, :string]}` (`"number | string"`), for
`Feel.Type.parse/2` (D-178). They are declared like any other type, in the names, the `:types`, the `:result_type` and the
`:input`, inside a list or a context, and under the mark: `{:non_null, {:union, [:number, :string]}}`. `null` is in them as it is in every
declared type, unless the whole is marked. The type of the name in the analysis is the union they make, and with the default
`declarations: :enforce` the run makes it true: a value that is not in it is `null` with a `:type_mismatch` error. See
`Feel.Type.Composite`.

## The result

The result of the expression is converted to the `result_type` and has to conform to it, and
then it has to satisfy the `result_allowed_values` (D-138). In DMN this is how a decision gives
its value to its variable. A scope is shared by the expressions that see the same names, so the
scope of one expression is made with `put_result/2` from the scope of the others, which is
cheap. The options `:result_type` and `:result_allowed_values` of `Feel.prepare/2` take the
place of those of the scope.

The built-in functions are not stored here; they are always in scope, in the
outermost layer, and a name in this scope shadows them.

All names in a scope are canonical (see `Feel.Name`). `new/2` canonicalizes
the names it is given. The functions that put and fetch single entries expect
canonical names already, since the analyzer gets them from the parser.

# `kind`

```elixir
@type kind() :: :allowed_values | :type_constraint
```

The allowed values (for each element of a list) or the type constraint (for the value as a whole).

# `step`

```elixir
@type step() ::
  Feel.Name.t()
  | :each
  | {:recursive, String.t(), String.t(), Feel.Type.Recursive.graph()}
```

A context key, `:each` for list elements, or a guarded declaration graph selector. The latter visits
each occurrence of its target declaration from the root, retaining concrete runtime paths.

# `subject`

```elixir
@type subject() :: Feel.Name.t() | {Feel.Name.t(), [step()]}
```

What is constrained: the variable itself (no steps), or a member of it.

# `t`

```elixir
@type t() :: %Feel.Scope{
  allowed_values: %{optional(Feel.Name.t()) =&gt; String.t()},
  constraints: %{
    optional({Feel.Name.t(), [step()], kind()}) =&gt; [String.t(), ...]
  },
  names: %{optional(Feel.Name.t()) =&gt; Feel.Type.t()},
  result_allowed_values: String.t() | nil,
  result_type: Feel.Type.t() | nil,
  types: %{optional(Feel.Name.t()) =&gt; Feel.Type.t()}
}
```

# `allowed_values`

```elixir
@spec allowed_values(t()) :: %{optional(Feel.Name.t()) =&gt; String.t()}
```

Returns the allowed values of the scope: a map from variable name to source text.

# `constraints`

```elixir
@spec constraints(t(), Feel.Name.t()) :: [
  %{steps: [step()], kind: kind(), source: String.t(), index: non_neg_integer()}
]
```

Everything that is constrained of a variable, whole or by member: the allowed values of the variable itself, its type
constraint, and those of its members, each with the `steps` down to it, the `kind` and the `source` text. The name must be
canonical.

    iex> scope = Feel.Scope.new(%{"p" => {:context, %{"age" => :number}}}, allowed_values: %{{"p", ["age"]} => "[0..150]"})
    iex> Feel.Scope.constraints(scope, "p")
    [%{steps: ["age"], kind: :allowed_values, source: "[0..150]", index: 0}]

# `fetch_allowed_values`

```elixir
@spec fetch_allowed_values(t(), Feel.Name.t()) :: {:ok, String.t()} | :error
```

Looks up the allowed values of a variable. The name must be canonical.

# `fetch_name`

```elixir
@spec fetch_name(t(), Feel.Name.t()) :: {:ok, Feel.Type.t()} | :error
```

Looks up the type of a variable. The name must be canonical.

# `fetch_type`

```elixir
@spec fetch_type(t(), Feel.Name.t()) :: {:ok, Feel.Type.t()} | :error
```

Looks up a type by name. The name must be canonical.

# `merge`

```elixir
@spec merge(t(), t()) :: t()
```

Merges two scopes. Entries of the second scope replace those of the first.

# `names`

```elixir
@spec names(t()) :: [Feel.Name.t()]
```

Returns the variable names in the scope, sorted.

# `new`

```elixir
@spec new(%{optional(String.t()) =&gt; Feel.Type.t()},
  types: %{optional(String.t()) =&gt; Feel.Type.t()},
  allowed_values: %{optional(subject()) =&gt; String.t() | [String.t()]},
  type_constraints: %{optional(subject()) =&gt; String.t() | [String.t()]},
  result_type: Feel.Type.t(),
  result_allowed_values: String.t(),
  not_null: :all | [String.t()]
) :: t()
```

Builds a scope from variable names and, optionally, type names and allowed values.

## Options

  * `:types` - a map from type name to `t:Feel.Type.t/0`.
  * `:allowed_values` - a map from the name of a variable, or a subject `{name, steps}` (a member of it), to the source text of
    its allowed values, a list of unary tests, or a list of such texts, which all have to hold (a definition that has
    allowed values and refers to one that has). Every name must be a variable of the scope.
  * `:type_constraints` - the same for the type constraint, which is for the value as a whole.
  * `:result_type` - the type of the result of the expression, a `t:Feel.Type.t/0`.
  * `:result_allowed_values` - the allowed values of the result, as above.
  * `:not_null` - `:all`, or a list of names: the variables whose types are marked not null (`put_not_null/2`, D-48).

Names are canonicalized with `Feel.Name.canonical/1`. Raises `ArgumentError` for an
allowed value that is not a string, or that is for a name that is not in the scope.

    iex> scope = Feel.Scope.new(%{"age" => :number}, allowed_values: %{"age" => "[0..150]"})
    iex> Feel.Scope.fetch_allowed_values(scope, "age")
    {:ok, "[0..150]"}
    iex> scope = Feel.Scope.new(%{"age" => :number, "name" => :string}, not_null: ["age"])
    iex> {Feel.Scope.fetch_name(scope, "age"), Feel.Scope.fetch_name(scope, "name")}
    {{:ok, {:non_null, :number}}, {:ok, :string}}

# `put_allowed_values`

```elixir
@spec put_allowed_values(t(), subject(), String.t()) :: t()
```

Sets the allowed values of a variable: the source text of a list of unary tests.

The name must be canonical and a variable of the scope. Raises `ArgumentError` when it
is not, or when the source is not a string.

    iex> scope = Feel.Scope.put_name(%Feel.Scope{}, "age", :number)
    iex> scope = Feel.Scope.put_allowed_values(scope, "age", "[0..150]")
    iex> Feel.Scope.allowed_values(scope)
    %{"age" => "[0..150]"}

# `put_name`

```elixir
@spec put_name(t(), Feel.Name.t(), Feel.Type.t()) :: t()
```

Adds or replaces a variable. The name must be canonical.

# `put_not_null`

```elixir
@spec put_not_null(t(), :all | [Feel.Name.t()]) :: t()
```

Marks the types of variables as not null (D-48): `:all` of them, or the ones in a list of canonical names.

A caller whose inputs are all required says so once, and not for each type. The mark is the one of a type written
with `!`: with the default `declarations: :enforce` the analysis types an expression without the `null` that the variable would
bring, and a run ends with an error of the kind `:input` if the bound value is `null` or is not bound. A type that is
marked already stays as it is, and a variable of the type `Null`, which has no value but `null`, is left as it is. Raises
`ArgumentError` for a name that is not a variable of the scope.

    iex> scope = Feel.Scope.new(%{"age" => :number, "ages" => {:list, :number}})
    iex> scope = Feel.Scope.put_not_null(scope, :all)
    iex> {Feel.Scope.fetch_name(scope, "age"), Feel.Scope.fetch_name(scope, "ages")}
    {{:ok, {:non_null, :number}}, {:ok, {:non_null, {:list, :number}}}}
    iex> Feel.Scope.put_not_null(scope, ["other"])
    ** (ArgumentError) not_null for "other", which is not a name of the scope

# `put_result`

```elixir
@spec put_result(t(),
  result_type: Feel.Type.t() | nil,
  result_allowed_values: String.t() | nil
) :: t()
```

Sets what the expression gives: the type of its result, and its allowed values.

## Options

  * `:result_type` - a `t:Feel.Type.t/0`. A type that names a type of the scope
    (`{:named, "tAge"}`) is resolved when the expression is prepared.
  * `:result_allowed_values` - the source text of a list of unary tests, as for a variable.

An option that is not given, or is `nil`, leaves what the scope has. Raises `ArgumentError`
for allowed values that are not a string.

    iex> scope = Feel.Scope.put_result(%Feel.Scope{}, result_type: :number, result_allowed_values: "[0..100]")
    iex> {Feel.Scope.result_type(scope), Feel.Scope.result_allowed_values(scope)}
    {:number, "[0..100]"}

# `put_type`

```elixir
@spec put_type(t(), Feel.Name.t(), Feel.Type.t()) :: t()
```

Adds or replaces a type name. The name must be canonical.

# `put_type_constraint`

```elixir
@spec put_type_constraint(t(), subject(), String.t()) :: t()
```

Sets the type constraint of a variable or of a member of it: the source text of a list of unary tests for the value as a
whole, which for a list is the list (`"count(?) > 0"`) and not each element.

    iex> scope = Feel.Scope.new(%{"xs" => {:list, :number}}, type_constraints: %{"xs" => "count(?) > 0"})
    iex> Feel.Scope.constraints(scope, "xs")
    [%{steps: [], kind: :type_constraint, source: "count(?) > 0", index: 0}]

# `result_allowed_values`

```elixir
@spec result_allowed_values(t()) :: String.t() | nil
```

Returns the allowed values of the result, as source text, or `nil` when there are none.

# `result_type`

```elixir
@spec result_type(t()) :: Feel.Type.t() | nil
```

Returns the type of the result of the expression, or `nil` when there is none.

# `type_names`

```elixir
@spec type_names(t()) :: [Feel.Name.t()]
```

Returns the type names in the scope, sorted.

---

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