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

The FEEL type `context` (DMN 1.6, section 10.3.2.6): a collection of key/value entries, like a record or a JSON
object, in the order they were written (D-135).

The specification calls a context "partially ordered", and says no more of the order of the entries than that the
entries of a context literal are evaluated in the order of what they depend on. The functions that show the order are
`get entries`, `context put`, `context merge` and `context(entries)`, and so does the text of a value (`string(...)`),
where the specification's examples have the entries in the order they are written (`context put({x:1}, "y", 2)` is
`{x:1, y:2}`). Equality ignores it (`{a: 1, b: 2} = {b: 2, a: 1}` is `true`, Table 54). So a context keeps its entries in
order, and nothing else does anything with it.

## The representation

A context the engine makes is a `%Feel.Value.Context{}`: the entries by key in a map, and the keys in the order of
the entries. The keys are strings (see `Feel.Name`) and the values are any FEEL value, `null` included.

The engine also reads a **plain map with string keys** as a context, in the order of its keys, so that a host can bind
`%{"age" => Decimal.new(21)}` as it is. A host that has an order to give builds the context with `new/1`.

Outside the engine a context is a plain map, as the specification and the TCK have it (the entries by name, no order):
the value of a run is converted with `to_host/1`, unless the run is asked to keep the order (`contexts: :ordered`).
Native functions get and give plain maps as well (`Feel.Value.Function`).

## The order

  * A context literal has the order it is written in, and not the order its entries are evaluated in.
  * `put/3` replaces the value of an entry where it is, and adds a new entry last.
  * `merge/2` keeps the entries of the first context where they are and adds the new ones of the second after them; an
    entry that both have takes the value of the second, in the place of the first.
  * A plain map is in the order of its keys.

    iex> context = Feel.Value.Context.new([{"b", 1}, {"a", 2}])
    iex> Feel.Value.Context.keys(context)
    ["b", "a"]
    iex> context |> Feel.Value.Context.put("c", 3) |> Feel.Value.Context.put("b", 0) |> Feel.Value.Context.to_list()
    [{"b", 0}, {"a", 2}, {"c", 3}]
    iex> Feel.Value.Context.to_host(context)
    %{"a" => 2, "b" => 1}

# `any_context`

```elixir
@type any_context() :: t() | host()
```

A context in either of the forms the engine reads.

# `host`

```elixir
@type host() :: %{optional(String.t()) =&gt; Feel.Value.t()}
```

A plain map with string keys, which the engine reads as a context in the order of its keys.

# `t`

```elixir
@type t() :: %Feel.Value.Context{
  map: %{optional(String.t()) =&gt; Feel.Value.t()},
  order: [String.t()] | {:sorted, [String.t()]}
}
```

A context: the entries by key, and the keys in the order of the entries. Treat it as opaque and read it through the
functions of this module.

`order` is the keys **last first** (so that an entry is added in constant time), or `{:sorted, extras}`: the keys that
the map has but the extras in the order of the keys, and then the extras (last first). The second is what `put/3` and
`merge/2` make of a plain map, which has the order of its keys but is not sorted until someone asks for it: a map of a
hundred thousand entries is put into in constant time, and a loop of puts is not a loop of sorts.

# `fetch`

```elixir
@spec fetch(any_context(), String.t()) :: {:ok, Feel.Value.t()} | :error
```

The value of an entry: `{:ok, value}`, or `:error` for a key it does not have.

    iex> Feel.Value.Context.fetch(Feel.Value.Context.new([{"a", nil}]), "a")
    {:ok, nil}
    iex> Feel.Value.Context.fetch(%{"a" => 1}, "b")
    :error

# `from_host`

```elixir
@spec from_host(Feel.Value.t()) :: Feel.Value.t()
```

A value as the engine holds it: a plain map is a context in the order of its keys, in a list and in a context as well.
A value that has none is given back as it is.

    iex> Feel.Value.Context.from_host([%{"b" => 1}]) == [Feel.Value.Context.new([{"b", 1}])]
    true

# `from_map`

```elixir
@spec from_map(any_context()) :: t()
```

The context of a map, in the order of its keys. A context is given back as it is.

    iex> Feel.Value.Context.from_map(%{"b" => 1, "a" => 2}) |> Feel.Value.Context.keys()
    ["a", "b"]

# `get`

```elixir
@spec get(any_context(), String.t()) :: Feel.Value.t()
```

The value of an entry, or `nil` for a key it does not have.

# `has_key?`

```elixir
@spec has_key?(any_context(), String.t()) :: boolean()
```

Whether the context has an entry with the key.

# `is_context`
*macro* 

Whether a term is a context in either form: a `%Feel.Value.Context{}` or a plain map. For a guard; it does not look at the keys.

# `keys`

```elixir
@spec keys(any_context()) :: [String.t()]
```

The keys, in the order of the entries.

# `merge`

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

The entries of the second context added to the first: those the first has take the value of the second where they are,
and the new ones come last, in the order of the second.

    iex> a = Feel.Value.Context.new([{"x", 1}, {"y", 0}])
    iex> b = Feel.Value.Context.new([{"z", 3}, {"y", 2}])
    iex> a |> Feel.Value.Context.merge(b) |> Feel.Value.Context.to_list()
    [{"x", 1}, {"y", 2}, {"z", 3}]

# `new`

```elixir
@spec new() :: t()
```

The context with no entry.

# `new`

```elixir
@spec new([{String.t(), Feel.Value.t()}]) :: t()
```

A context of entries, in their order. An entry with a key that an earlier one has takes its value, in the place of the
earlier (as `put/3` does).

    iex> Feel.Value.Context.new([{"b", 1}, {"a", 2}]) |> Feel.Value.Context.to_list()
    [{"b", 1}, {"a", 2}]
    iex> Feel.Value.Context.new([{"a", 1}, {"b", 2}, {"a", 3}]) |> Feel.Value.Context.to_list()
    [{"a", 3}, {"b", 2}]

# `ordered`

```elixir
@spec ordered(%{optional(String.t()) =&gt; Feel.Value.t()}, [String.t()]) :: t()
```

The context made of a map, with the keys in the given order: the order a context literal is written in, for the entries were
evaluated in another. `keys` has the keys of the map and no other.

    iex> Feel.Value.Context.ordered(%{"a" => 1, "b" => 2}, ["b", "a"]) |> Feel.Value.Context.to_list()
    [{"b", 2}, {"a", 1}]

# `ordered_reversed`

```elixir
@spec ordered_reversed(%{optional(String.t()) =&gt; Feel.Value.t()}, [String.t()]) :: t()
```

The same, for the keys given last first, which is how the context keeps them. For the code that the BEAM backend
generates, which has the keys of the literal in that order already.

# `put`

```elixir
@spec put(any_context(), String.t(), Feel.Value.t()) :: t()
```

The context with the entry set to the value: where it is if it has the key, and last if it has not.

    iex> context = Feel.Value.Context.new([{"a", 1}, {"b", 2}])
    iex> context |> Feel.Value.Context.put("a", 0) |> Feel.Value.Context.to_list()
    [{"a", 0}, {"b", 2}]
    iex> context |> Feel.Value.Context.put("c", 3) |> Feel.Value.Context.to_list()
    [{"a", 1}, {"b", 2}, {"c", 3}]

# `size`

```elixir
@spec size(any_context()) :: non_neg_integer()
```

The number of entries.

# `to_host`

```elixir
@spec to_host(Feel.Value.t()) :: Feel.Value.t()
```

A value as a host has it: a context is a plain map, in a list and in a context as well. The specification's contexts and
those of the TCK are that: entries by name, equal whatever their order.

    iex> Feel.Value.Context.to_host([Feel.Value.Context.new([{"b", Feel.Value.Context.new([{"a", 1}])}])])
    [%{"b" => %{"a" => 1}}]

# `to_list`

```elixir
@spec to_list(any_context()) :: [{String.t(), Feel.Value.t()}]
```

The entries as `{key, value}`, in order.

    iex> Feel.Value.Context.to_list(%{"b" => 1, "a" => 2})
    [{"a", 2}, {"b", 1}]

# `to_map`

```elixir
@spec to_map(any_context()) :: host()
```

The entries as a plain map, which has no order.

    iex> Feel.Value.Context.new([{"b", 1}, {"a", 2}]) |> Feel.Value.Context.to_map()
    %{"a" => 2, "b" => 1}

# `type`

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

Returns the descriptor of a context type with the given member types.

# `value?`

```elixir
@spec value?(term()) :: boolean()
```

Returns `true` for a context in either form: a `%Feel.Value.Context{}`, or a map that is not a struct and has only
string keys.

The values are not examined; use `Feel.Type.instance?/2` for that.

    iex> Feel.Value.Context.value?(%{"a" => 1})
    true
    iex> Feel.Value.Context.value?(Feel.Value.Context.new([{"a", 1}]))
    true
    iex> Feel.Value.Context.value?(%{a: 1})
    false
    iex> Feel.Value.Context.value?(~D[2026-10-12])
    false

# `values`

```elixir
@spec values(any_context()) :: [Feel.Value.t()]
```

The values, in the order of the entries.

---

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