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

The environment of one run: its mode, its dialect, its extensions, its clock, its limits, and the
diagnostics it has produced so far.

The runtime is made of pure functions on values, and the environment is the one
thing that is threaded through a run. Everything that depends on the run and not
on the expression lives here:

  * `:mode` - `:lenient` (the default) or `:strict`, as in `Feel.run/3`.
  * `:dialect` - the dialect the expression was prepared in (`Feel.Dialect`, D-49). It is a
    property of the prepared expression, not of a run: the backend puts it here. What is
    defined for an error in FEEL is `null`; B-FEEL will read this to give the value of the
    type instead. S-FEEL has the semantics of FEEL, so nothing reads it for that.
  * `:extensions` - the `Feel.Extensions` the expression was prepared with (D-81). Like the dialect, it is a
    property of the prepared expression: the backend puts it here, and a call of a function finds the function
    through it (`Feel.Builtin.call/3`).
  * `:clock` - the moment `now()` and `today()` see. It is fixed when the environment is built, so repeated calls
    in one run agree. A `DateTime` that was given is kept as it is. Without one (D-148), the instant is taken as a number
    of microseconds (`{:os_time, microseconds}`), and made into a `DateTime` only when `clock/1` is asked, since
    most expressions never ask and a `DateTime` costs about as much as everything else a run does. Read it with
    `clock/1`. It is `nil` in the template of an executable (`template/1`), which has no moment yet.
  * `:limits` - the `t:Feel.Limits.t/0` of the run.
  * `:entropy` - where random bytes come from, for the functions of an extension that are random (D-81): a function from
    a number of bytes to that many bytes, `:crypto.strong_rand_bytes/1` by default. A run can be given its own to be
    repeatable. Nothing in DMN 1.6 is random.
  * `:diagnostics` - what has been reported, newest first. Use `diagnostics/1`.
  * `:diagnostic_count` - how many there are in `:diagnostics`, kept so that the ones that an operation reported
    can be told from the ones before it without counting the list (D-103). Whoever puts another list in
    `:diagnostics` puts its length here.
  * `:declared` - the errors that the checks of the bound values reported when the run began (`declared/2`, D-103),
    newest first. They are at the span of the first read of each name, and so inside the operations that read it.
  * `:call_depth` - how many calls of a function value the run is in (D-82), which `max_call_depth` of the limits caps.
  * `:calls` - how many calls of a function of FEEL the run has made, nested or not (D-154), which `max_calls` of the
    limits caps.
  * `:iterations` - how many elements the iterators of `for`, `some` and `every` have taken in the run (D-86), which
    `max_iterations` of the limits caps.
  * `:links` - the closures that `Feel.Recursion` has made in this run (D-200), by the link and the member they are
    for. A function that calls itself needs its closure at each call, and makes it at the first: the closure is
    a value of the run like the diagnostics, and a function value that outlives the run finds this empty in the next
    one and makes it again.
  * `:origin` - the origin of the text that is being run (D-201): the `:origin` option of the run (`nil` unless the host
    gave one) for the expression of the run, and for the body of a function the origin that function has, which a
    function written in an expression takes from the run it is made in. A diagnostic is reported with it
    (`Feel.Diagnostic`, `:origin`), so that its span can be found in the text it is of.

## Outcomes

A runtime operation does not touch the environment. It returns an `t:outcome/0`,
and `settle/3` turns that into a value and a new environment:

  * `{:ok, value}` - the result. The value may be `null`: some results are defined
    to be.
  * `{:ok, value, env}` - the result, from a function that called a function value on the way, as
    `list replace` calls its `match` (D-82). The environment it gives back has what those calls reported, and
    the diagnostics in it that have no span are given the one of the operation.
  * `{:ok, value, [diagnostic]}` - the result, with what the operation did on a value's behalf and says (the implicit
    conversion of a date in a subtraction, D-56): the diagnostics are reported, with the span of the operation if they
    have none, and then the value is the result.
  * `:missing` - the result is `null` because an operand is `null`: missing data,
    not a mistake. Nothing is reported.
  * `{:error, diagnostic}` - the result is `null` because of a semantic error, such
    as a type mismatch or a division by zero (DMN 1.6, section 7.3.8). It is
    reported.

Which `null` results count as errors is D-103 (was D-103), after the TCK's flags: a `null` operand of
an arithmetic operator, and of `<=` and `>=`, is `{:error, _}` (`Feel.Runtime.NullOperand`), and one of
`<`, `>`, `and`, `or` and `not` is `:missing`; `settle/3` is the one place that applies the split. In lenient mode a reported diagnostic is collected and evaluation goes on. In
strict mode the first error ends the run, and warnings and infos do not. `run/2` is where
that ends up as `{:error, error}`.

## Limits

`settle/3` also checks the size of the value it is given against `:limits`, since
every backend passes its results through it. A string or list that is too long ends
the run with an error of kind `:limit`, in either mode. A function whose result can be as long as the product of
the sizes of its arguments asks the same question before it makes the result, with `check_string_length/2` and
`check_list_length/2` (D-154 does it for a loop), so that the error is the same and nothing is built that the limit
refuses (`Feel.Builtin.Size`).

`report/2` counts the diagnostics against `max_diagnostics`.

# `mode`

```elixir
@type mode() :: :lenient | :strict
```

# `outcome`

```elixir
@type outcome() ::
  {:ok, Feel.Value.t()}
  | {:ok, Feel.Value.t(), t()}
  | {:ok, Feel.Value.t(), [Feel.Diagnostic.t(), ...]}
  | :missing
  | {:error, Feel.Diagnostic.t()}
```

What a runtime operation returns; see the module documentation.

# `t`

```elixir
@type t() :: %Feel.Runtime.Env{
  budget: Feel.Runtime.Budget.t() | nil,
  call_depth: non_neg_integer(),
  calls: non_neg_integer(),
  clock: DateTime.t() | {:os_time, integer()} | nil,
  declared: [Feel.Diagnostic.t()],
  diagnostic_count: non_neg_integer(),
  diagnostics: [Feel.Diagnostic.t()],
  dialect: Feel.Dialect.t(),
  entropy: (non_neg_integer() -&gt; binary()) | nil,
  extensions: Feel.Extensions.t(),
  iterations: non_neg_integer(),
  limits: Feel.Limits.t(),
  links: %{optional({reference(), term()}) =&gt; Feel.Value.t()},
  mode: mode(),
  monitor: (Feel.IR.t(), Feel.Value.t() -&gt; term()) | nil,
  origin: term()
}
```

# `abort`

```elixir
@spec abort(t(), Feel.Error.kind(), Feel.Diagnostic.t()) :: no_return()
```

Ends the run with an error, in either mode.

A strict run ends at its first error (`report/2`), and a run that passes a limit ends in
either mode; this is for a mistake that is not the expression's and that makes the run
worthless, as a value that is not allowed by the declaration of the scope. `run/2` returns
`{:error, error}`. The error has the diagnostic and, after it, those reported so far.

    iex> env = Feel.Runtime.Env.new()
    iex> diagnostic = Feel.Diagnostic.new(:null_not_allowed, "x")
    iex> {:error, error} = Feel.Runtime.Env.run(env, fn env -> Feel.Runtime.Env.abort(env, :input, diagnostic) end)
    iex> {error.kind, error.code}
    {:input, :null_not_allowed}

# `check_list_length`

```elixir
@spec check_list_length(t(), non_neg_integer()) :: t()
```

Ends the run, in either mode, with an error of kind `:limit` if a list of `length` elements is longer than
`max_list_length`, and gives the environment back if it is not (D-154).

A list is checked when it is finished (`settle/3`), and a loop that makes one checks it as it makes it, with the
number of results so far, so that a loop that passes the limit ends at the first result over it and not when
it is done. A built-in function that makes a list out of lists, as `flatten` and `concatenate` do, counts the items as it
walks them. The diagnostics reported until then are in the error, as for any limit.

    iex> env = Feel.Runtime.Env.new(limits: Feel.Limits.new(max_list_length: 2))
    iex> ^env = Feel.Runtime.Env.check_list_length(env, 2)
    iex> {:error, error} = Feel.Runtime.Env.run(env, fn env -> {Feel.Runtime.Env.check_list_length(env, 3), env} end)
    iex> {error.kind, error.code}
    {:limit, :limit_exceeded}

# `check_string_length`

```elixir
@spec check_string_length(t(), non_neg_integer()) :: t()
```

Ends the run, in either mode, with an error of kind `:limit` if a string of `size` bytes is longer than
`max_string_length`, and gives the environment back if it is not.

`settle/3` checks a string when it is finished, and a built-in function whose result is longer than its arguments
checks the size it is going to make before it makes anything, so that a result that the limit refuses is not built.
It is the same function, so the error is the same (D-154 does it for a list). `size` need not be the exact size
of the string to come: a count that has passed the limit already (the bytes added up until then) is enough.

    iex> env = Feel.Runtime.Env.new(limits: Feel.Limits.new(max_string_length: 2))
    iex> ^env = Feel.Runtime.Env.check_string_length(env, 2)
    iex> {:error, error} = Feel.Runtime.Env.run(env, fn env -> {Feel.Runtime.Env.check_string_length(env, 3), env} end)
    iex> {error.kind, hd(error.diagnostics).details}
    {:limit, %{limit: :max_string_length}}

# `clock`

```elixir
@spec clock(t()) :: DateTime.t()
```

The moment `now()` and `today()` see: the `DateTime` that was given as the `:clock`, or, without one, the instant at which
the environment was built, as a `DateTime` in UTC with the precision of a microsecond (what `DateTime.utc_now/0` gives).
Every call on one environment gives the same moment (D-148).

    iex> env = Feel.Runtime.Env.new(clock: ~U[2026-10-13 12:00:00Z])
    iex> Feel.Runtime.Env.clock(env)
    ~U[2026-10-13 12:00:00Z]

    iex> env = Feel.Runtime.Env.new()
    iex> Feel.Runtime.Env.clock(env) == Feel.Runtime.Env.clock(env)
    true

# `count_iteration`

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

Counts one element taken by an iterator of `for`, `some` or `every` (D-86). A run in which they have taken more than
`max_iterations` ends, in either mode, with an error of kind `:limit`.

    iex> env = Feel.Runtime.Env.new(limits: Feel.Limits.new(max_iterations: 2))
    iex> env = env |> Feel.Runtime.Env.count_iteration() |> Feel.Runtime.Env.count_iteration()
    iex> env.iterations
    2
    iex> {:error, error} = Feel.Runtime.Env.run(env, fn env -> {Feel.Runtime.Env.count_iteration(env), env} end)
    iex> {error.kind, error.code}
    {:limit, :limit_exceeded}

# `declared`

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

Keeps the errors that were reported since `since` as the ones of the declarations of the names (D-103).

The checks of the bound values (`Feel.Runtime.BindingTypes`, `Feel.Runtime.AllowedValues`) run before the
expression, and report at the span of the first read of each name. An operation that reads the name has that span
inside its own without its operands having reported anything, and the error explains its `null` operand all the
same (`settle/4`), in whatever turn of a loop or call of a function it is. There is one for a name at most, so
looking through them costs the names and not the diagnostics of the run.

    iex> env = Feel.Runtime.Env.new()
    iex> {nil, checked} = Feel.Runtime.Env.settle(env, {:error, Feel.Diagnostic.new(:value_not_allowed, "x")}, {0, 3})
    iex> operand = Feel.Runtime.NullOperand.error("+")
    iex> {nil, kept} = Feel.Runtime.Env.settle(checked, operand, {0, 7}, checked)
    iex> Enum.map(Feel.Runtime.Env.diagnostics(kept), & &1.code)
    [:value_not_allowed, :null_operand]
    iex> declared = Feel.Runtime.Env.declared(checked, env)
    iex> {nil, explained} = Feel.Runtime.Env.settle(declared, operand, {0, 7}, declared)
    iex> Enum.map(Feel.Runtime.Env.diagnostics(explained), & &1.code)
    [:value_not_allowed]

# `diagnostics`

```elixir
@spec diagnostics(t()) :: [Feel.Diagnostic.t()]
```

Returns the diagnostics reported so far, oldest first.

    iex> env = Feel.Runtime.Env.new()
    iex> {nil, env} = Feel.Runtime.Env.settle(env, {:error, Feel.Diagnostic.new(:division_by_zero, "x")})
    iex> Enum.map(Feel.Runtime.Env.diagnostics(env), & &1.code)
    [:division_by_zero]

# `enter_call`

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

Enters a call of a function of FEEL (D-82): the depth of the run goes up by one, and so does the number of calls
it has made (D-154). A run that passes `max_call_depth` or `max_calls` ends in either mode, as one that passes
any other limit does.

    iex> env = Feel.Runtime.Env.new(limits: Feel.Limits.new(max_call_depth: 1))
    iex> env = Feel.Runtime.Env.enter_call(env)
    iex> {env.call_depth, env.calls}
    {1, 1}
    iex> {:error, error} = Feel.Runtime.Env.run(env, fn env -> {Feel.Runtime.Env.enter_call(env), env} end)
    iex> {error.kind, error.code}
    {:limit, :limit_exceeded}

    iex> env = Feel.Runtime.Env.new(limits: Feel.Limits.new(max_calls: 1))
    iex> env = env |> Feel.Runtime.Env.enter_call() |> Feel.Runtime.Env.leave_call(0)
    iex> {env.call_depth, env.calls}
    {0, 1}
    iex> {:error, error} = Feel.Runtime.Env.run(env, fn env -> {Feel.Runtime.Env.enter_call(env), env} end)
    iex> {error.kind, hd(error.diagnostics).details}
    {:limit, %{limit: :max_calls}}

# `leave_call`

```elixir
@spec leave_call(t(), non_neg_integer()) :: t()
```

Leaves a call: the depth is the one that the run was at before it (`depth`). The number of calls is not: they
have been made.

# `new`

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

Builds the environment of a run.

## Options

  * `:mode` - `:lenient` (default) or `:strict`.
  * `:dialect` - `:feel` (default), `:s_feel`, or `:b_feel`.
  * `:extensions` - a list of extensions or a `t:Feel.Extensions.t/0`, none by default.
  * `:clock` - a `DateTime`, or a function of no arguments that returns one. It
    defaults to the current time, and a function is called once, here.
  * `:limits` - a `t:Feel.Limits.t/0`. It defaults to `Feel.Limits.default/0`.
  * `:entropy` - a function from a number of bytes to that many bytes, for the random functions of an extension.
    It defaults to `:crypto.strong_rand_bytes/1`, and is called each time a function wants bytes (the clock is read
    once).
  * `:origin` - which text the run is of, for the host to find the span of a diagnostic in (D-201). It is `nil` by
    default. A function written in the expression keeps it, wherever the function is called.

Raises `ArgumentError` for an unknown option or a value of the wrong kind.

    iex> env = Feel.Runtime.Env.new(mode: :strict, clock: ~U[2026-10-13 12:00:00Z])
    iex> {env.mode, env.dialect, env.clock}
    {:strict, :feel, ~U[2026-10-13 12:00:00Z]}

# `quietly`

```elixir
@spec quietly(t(), (t() -&gt; {Feel.Value.t(), t()})) :: {Feel.Value.t(), t()}
```

Runs `fun` on the environment and keeps what it reports out of it (D-100).

`fun` takes the environment and returns `{value, environment}`, as the code of an expression does. In `fun` the
run is lenient, so an error does not end it, and what it reports is dropped: the diagnostics and the mode are the
ones of `env`. What it counted (the iterations and the depth of the calls) is kept, and so is the end of a run by a
limit or by `abort/3`, which is not a report.

    iex> env = Feel.Runtime.Env.new(mode: :strict)
    iex> {nil, quiet} = Feel.Runtime.Env.quietly(env, fn env ->
    ...>   {nil, Feel.Runtime.Env.report(env, Feel.Diagnostic.new(:invalid_property, "x"))}
    ...> end)
    iex> {quiet.mode, Feel.Runtime.Env.diagnostics(quiet)}
    {:strict, []}

# `random_bytes`

```elixir
@spec random_bytes(t(), non_neg_integer()) :: binary()
```

`count` random bytes, from the `:entropy` of the environment. A source that gives anything but that many bytes is a
mistake of its caller and raises `ArgumentError`.

    iex> env = Feel.Runtime.Env.new(entropy: fn count -> :binary.copy(<<7>>, count) end)
    iex> Feel.Runtime.Env.random_bytes(env, 3)
    <<7, 7, 7>>
    iex> byte_size(Feel.Runtime.Env.random_bytes(Feel.Runtime.Env.new(), 16))
    16

# `report`

```elixir
@spec report(t(), Feel.Diagnostic.t()) :: t()
```

Reports a diagnostic.

In lenient mode it is collected, and so is a warning or an info (D-46) in either mode. In strict mode
an error ends the run: this function does not return, and the run's `run/2` returns
`{:error, error}` of kind `:strict`, whose diagnostics start with this one and go on
with the ones reported before.

A run that has reported `max_diagnostics` already ends, in either mode, with an error of kind `:limit` and the
diagnostics it has (D-154).

    iex> env = Feel.Runtime.Env.new(limits: Feel.Limits.new(max_diagnostics: 1))
    iex> env = Feel.Runtime.Env.report(env, Feel.Diagnostic.new(:invalid_property, "x"))
    iex> env.diagnostic_count
    1
    iex> {:error, error} = Feel.Runtime.Env.run(env, fn env ->
    ...>   {nil, Feel.Runtime.Env.report(env, Feel.Diagnostic.new(:invalid_property, "y"))}
    ...> end)
    iex> {error.kind, hd(error.diagnostics).details, length(error.diagnostics)}
    {:limit, %{limit: :max_diagnostics}, 2}

# `run`

```elixir
@spec run(t(), (t() -&gt; {Feel.Value.t(), t()})) :: Feel.result(Feel.Value.t())
```

Runs `fun` on the environment and returns the result in the form `Feel.run/3` does.

`fun` takes the environment and returns `{value, environment}`. The result is
`{:ok, value, diagnostics}`, or `{:error, error}` if the run was ended by a strict
mode error or by a limit.

    iex> env = Feel.Runtime.Env.new()
    iex> Feel.Runtime.Env.run(env, fn env -> {1, env} end)
    {:ok, 1, []}

    iex> env = Feel.Runtime.Env.new(mode: :strict)
    iex> {:error, error} = Feel.Runtime.Env.run(env, fn env ->
    ...>   Feel.Runtime.Env.settle(env, {:error, Feel.Diagnostic.new(:type_mismatch, "x")})
    ...> end)
    iex> error.kind
    :strict

# `settle`

```elixir
@spec settle(t(), outcome(), Feel.Diagnostic.span() | nil, t() | nil) ::
  {Feel.Value.t(), t()}
```

Turns the outcome of an operation into a value and an environment.

`span` is the byte range of the source the operation came from; it is put on a
diagnostic that has none. A value that is too long for the limits ends the run, as
described in the module documentation. Construction checks apply to each string and list.

A `null` operand (`Feel.Runtime.NullOperand`) is not reported when an error has been reported inside the
operation already. `since` is the environment from before the operands of the operation were evaluated, and what
was reported after it is what they can have reported (D-103):

    iex> env = Feel.Runtime.Env.new()
    iex> since = env
    iex> {nil, env} = Feel.Runtime.Env.settle(env, {:error, Feel.Diagnostic.new(:division_by_zero, "x")}, {1, 6})
    iex> {nil, env} = Feel.Runtime.Env.settle(env, Feel.Runtime.NullOperand.error("+"), {0, 11}, since)
    iex> Enum.map(Feel.Runtime.Env.diagnostics(env), & &1.code)
    [:division_by_zero]

An error that was reported before the operands were evaluated, in an earlier turn of a loop or by an earlier call of
the same function, has the span of this operation around it too, and is not the reason for this `null`. Without
`since`, nothing is:

    iex> env = Feel.Runtime.Env.new()
    iex> {nil, env} = Feel.Runtime.Env.settle(env, {:error, Feel.Diagnostic.new(:division_by_zero, "x")}, {1, 6})
    iex> since = env
    iex> {nil, env} = Feel.Runtime.Env.settle(env, Feel.Runtime.NullOperand.error("+"), {0, 11}, since)
    iex> Enum.map(Feel.Runtime.Env.diagnostics(env), & &1.code)
    [:division_by_zero, :null_operand]

# `start`

```elixir
@spec start(t(), keyword()) :: t()
```

The environment of one run, from the template of its executable (D-148) and the run options `:mode`, `:clock`, `:limits`
`:entropy` and `:origin`, which mean what they mean for `new/1`, with the same errors. It is what `new/1` gives for the options of
the template and these, and does the work of checking only the options that are given: a run with none takes the clock
and nothing else.

Options of any other kind (a `:dialect`, an unknown one, one given twice) are left to `new/1`, which raises for the ones
that are wrong and lets the options of the executable win.

    iex> template = Feel.Runtime.Env.template(dialect: :s_feel)
    iex> env = Feel.Runtime.Env.start(template, mode: :strict, clock: ~U[2026-10-13 12:00:00Z])
    iex> {env.mode, env.dialect, env.clock}
    {:strict, :s_feel, ~U[2026-10-13 12:00:00Z]}
    iex> Feel.Runtime.Env.start(template, []).mode
    :lenient

# `template`

```elixir
@spec template(keyword()) :: t()
```

The environment that the runs of one executable start from (D-148): `new/1` with the options of the executable (its
`:dialect` and `:extensions`) and no clock, which `start/2` takes at each run. An executable makes it once, when it is
compiled, and so a run does not check and build the same options again. It has no clock so that two executables of the
same expression are equal.

    iex> template = Feel.Runtime.Env.template(dialect: :s_feel)
    iex> {template.dialect, template.mode, template.clock}
    {:s_feel, :lenient, nil}

# `within`

```elixir
@spec within(t(), term(), (t() -&gt; {Feel.Value.t(), t()})) :: {Feel.Value.t(), t()}
```

Runs `fun` with `origin` as the origin of the text, and gives the environment back with the origin it had (D-201).

What `fun` reports has `origin` (`Feel.Diagnostic`, `:origin`) unless a function it calls has one of its own. It is how
the body of a function that another expression made (`Feel.Value.Function.with_origin/2`) says whose text its spans are in.

    iex> env = Feel.Runtime.Env.new()
    iex> {nil, inner} = Feel.Runtime.Env.within(env, :member, fn env ->
    ...>   {nil, Feel.Runtime.Env.report(env, Feel.Diagnostic.new(:invalid_property, "x", span: {0, 1}))}
    ...> end)
    iex> {inner.origin, Enum.map(Feel.Runtime.Env.diagnostics(inner), & &1.origin)}
    {nil, [:member]}

---

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