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

Selecting from a value by a name: `e.name` (DMN 1.6, Tables 65 to 68 and section 10.3.2.5, D-65).

`select/2` takes the value of `e` and the canonical name (`Feel.Name`) and returns an
`t:Feel.Runtime.Env.outcome/0`. What it selects depends on the kind of the value:

| The value                 | `e.name`                                                                 |
| ------------------------- | ------------------------------------------------------------------------ |
| a context                 | the entry of that key; `null` if the entry is `null`; **an error** if there is none |
| a list                    | a list of the same length: the selection from each element (see below)   |
| a date                    | `year`, `month`, `day`, `weekday`, `value`                               |
| a time                    | `hour`, `minute`, `second`, `time offset`, `timezone`, `value`           |
| a date and time           | all of those                                                             |
| a years and months duration | `years`, `months`, `value`                                             |
| a days and time duration  | `days`, `hours`, `minutes`, `seconds`, `value`                           |
| a range                   | `start`, `end`, `start included`, `end included`                         |
| `null`                    | **an error** (`null.b` is `null`, flagged as an error: TCK `0057#010`)   |
| anything else             | **an error**                                                             |

A name that the kind does not have is an error with the code `:invalid_property`, and so is a key that
a context does not have (`{a: 1}.b`, TCK `0057#009`). So is a component of the other kind of duration
(`duration("P1Y").days`): Table 67 says `null` for it, and the TCK flags an error (`0074#ym_duration_003`
to `#006`, `dt_duration_001`, `#002`), which is what the run reports (D-65). What Table 67 says is `null`
and the TCK does not flag is not an error: `time offset` and `timezone` of a value that has none. The
components of a duration carry its sign, and are the normalized ones (`duration("P1DT25H").days` is 2,
and `duration("-P1Y2M").months` is -2, notes 05, E36).

## A list

The selection from a list is the list of the selections from its elements (section 10.3.2.5):
`[{x: 1, y: 2}, {x: 2}].y` is `[2, null]`. Inside a list a missing entry is `null` and not an error
(TCK `0069#033`, `#034`), and so is an element that has no such entry because it is no context, a
date with no such property, `null`, or a list: there is no rule for a list of lists, and it is not
looked into (notes 02, A-29). An empty list gives the empty list.

## The descendant

`e...name` is the values that a context has under the key `name`, at any depth (section 10.3.2.5, Table 71): the
context's own entry first, then those of the contexts nested in its entries, each in turn
(`{a: {b: {b: 1}}}...b` is `[{b: 1}, 1]`). It is a list always, and `[]` when there is none (notes 05, E38).
`descend/3` reads it as the notes do, where the text is silent (D-65); with the environment of the run it also holds
the list of a context to `max_list_length` as it makes it:

  * **The order** is a pre-order walk, with the entries of a context taken in the order they have (D-135).
  * **Contexts nested in a list are not looked into**; the text speaks of nested contexts. An entry whose value is a
    list is a value like any other, and is in the result if it is the key.
  * **A list** on the left is the list of the descendants of each element, as a path is (`[c1, c2]...b` is a
    list of two lists), with `null` for an element that is no context.
  * **`null`**, and a value that is not a context, are an error, as for a path.

## `value`

The `value` of a duration is its months or its seconds. That of a date, a time and a date and time is the
number of seconds that `Feel.Runtime.Temporal.value/1` gives (D-68): from the epoch
`1970-01-01T00:00:00Z`, for a date and for a date and time with an offset or a zone id, and the seconds of the
day less the offset for a time. A date and time with no zone has none, and is `null` with an error (`:invalid_property`).

    iex> Feel.Runtime.Path.select(%{"a" => Decimal.new(1)}, "a")
    {:ok, Decimal.new(1)}
    iex> {:error, %Feel.Diagnostic{code: :invalid_property}} = Feel.Runtime.Path.select(%{"a" => 1}, "b")
    iex> Feel.Runtime.Path.select([%{"y" => Decimal.new(2)}, %{"x" => Decimal.new(1)}], "y")
    {:ok, [Decimal.new(2), nil]}
    iex> Feel.Runtime.Path.select(~D[2018-12-10], "weekday")
    {:ok, Decimal.new(1)}
    iex> Feel.Runtime.Path.select(Feel.Value.DaysAndTimeDuration.new(93_784), "minutes")
    {:ok, Decimal.new(3)}

# `descend`

```elixir
@spec descend(Feel.Value.t(), Feel.Name.t(), Feel.Runtime.Env.t() | nil) ::
  Feel.Runtime.Env.outcome()
```

Selects the values under a key from a context and from the contexts nested in it. See the module documentation.

With the environment of the run, the descendants of a context are counted as they are found, and the walk ends at the
first one over `max_list_length`, which is the error that a finished list gets
(`Feel.Runtime.Env.check_list_length/2`). A context that is in a context twice is walked twice, so `n` levels of
it are 2ⁿ descendants, and the list is made whole before the limit looks at it. The descendants of the contexts in a
list are lists of their own, and are not looked at: no list but the one that a run gives is checked.

    iex> inner = %{"b" => Decimal.new(1)}
    iex> Feel.Runtime.Path.descend(%{"a" => %{"b" => inner}}, "b")
    {:ok, [inner, Decimal.new(1)]}
    iex> Feel.Runtime.Path.descend(%{"a" => 1}, "b")
    {:ok, []}
    iex> {:error, %Feel.Diagnostic{code: :invalid_property}} = Feel.Runtime.Path.descend(nil, "b")
    iex> shared = %{"a" => %{"b" => 1}}
    iex> env = Env.new(limits: Limits.new(max_list_length: 2))
    iex> {:ok, _} = Feel.Runtime.Path.descend(%{"x" => shared, "y" => shared}, "b", env)
    iex> {:error, %Error{kind: :limit}} = Feel.Runtime.Env.run(env, fn env ->
    ...>   {Feel.Runtime.Path.descend(%{"x" => shared, "y" => shared, "z" => shared}, "b", env), env}
    ...> end)

# `select`

```elixir
@spec select(Feel.Value.t(), Feel.Name.t()) :: Feel.Runtime.Env.outcome()
```

Selects the entry or the property of a name from a value. See the module documentation.

---

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