The backend that walks the resolved form of an expression.
It generates no code. Its executable is the IR itself, and run/3 evaluates it
node by node, calling Feel.Runtime for every operation. Nothing is loaded into the
VM and no atoms are created, so it is the backend to use for expressions that come
from sources that are not trusted. It is also the reference that the BEAM backend is
tested against.
Each operation returns an outcome (Feel.Runtime.Env.outcome/0), and the
interpreter passes it to Feel.Runtime.Env.settle/3 with the span of the node, so a
diagnostic points at the text it came from and strict mode ends the run there.
Bindings
A :var node is looked up with Feel.Runtime.Bindings.lookup/4 (D-37), the same
function the generated code of the BEAM backend calls, so both find the same value.
Operands
Both operands of a binary operator are always evaluated, and and or included:
the specification gives them no order, and a false decides a conjunction whatever
the other operand is, an error included (D-53). So an error in the operand that does
not matter is still reported, and in strict mode it ends the run.
A conditional is the exception, as the specification has it: it evaluates the condition and
then only the branch it takes, the then branch for a condition that is true and the else
branch for any other value. So nothing in the other branch is reported, and in strict mode it
does not end the run.
Lists and contexts
The elements of a list are evaluated in order. The entries of a context are evaluated in the order the
node has them, which is the order they depend on each other in, and the value of each is bound to its
key for the ones after it: a :local node reads it. The bindings of the run hold them under {:local, name}, which no name of the bindings can be, and the context's own copy of the bindings is what the
entries after see, so a key that an inner context has again shadows the outer one only inside it.
Loops
for, some and every are run by Feel.Runtime.Iteration, which takes a function for each domain and one for the
body: the walk of the nodes, with the iterators bound as {:local, name} in the bindings (D-86). A domain with more
than 16 iterators before it is given and binds only the ones its code names (Feel.IR.local_names/1), where binding
all of them for each domain made a loop of n iterators n * n / 2 bindings (D-86).
Nodes
run/3 evaluates every node that the analyzer produces (see Feel.IR, which also says which
nodes of its set are not produced yet). A node that it has no clause for raises ArgumentError:
that is a mistake of the analyzer and not of the expression, which is why it is not a diagnostic.
The walk and the monitor
The walk of the nodes is in Feel.Backend.Interpreter.Walk, one text that is two modules: Feel.Backend.Interpreter.Plain
for run/3, and Feel.Backend.Interpreter.Monitored for run_monitored/4, which tells a function the value of every node
(Feel.Paranoid, D-159). A run does not ask at each node whether there is a monitor: the question cost 3 to 14% of a run of a
few operations on names.
Summary
Functions
Wraps the IR in an executable, with the dialect and the extensions it was prepared with (D-49, D-81). The
interpreter has nothing to compile, so the other options are only checked. The profile :explain is not
implemented yet and raises ArgumentError (Feel.Backend.validate_profile!/1).
Evaluates an IR on a set of bindings and an environment, as run/3 does, and returns {value, env} without catching
the end of a run (Feel.Runtime.Env.run/2 is what does).
Evaluates an executable on a set of bindings.
Evaluates an executable and tells monitor, a function of a node and its value, the value of every node as it is made
(Feel.Paranoid). The run is the run of run/3.
Types
@type monitor() :: (Feel.IR.t(), Feel.Value.t() -> term())
What a monitored run tells of each node: the node, and the value it made.
@type t() :: %Feel.Backend.Interpreter{ binding_names: MapSet.t(Feel.Name.t()) | nil, dialect: Feel.Dialect.t(), env: Feel.Runtime.Env.t() | nil, extensions: Feel.Extensions.t(), ir: Feel.Backend.ir() }
Functions
@spec compile(Feel.Backend.ir(), [Feel.Backend.compile_option()]) :: {:ok, t()}
Wraps the IR in an executable, with the dialect and the extensions it was prepared with (D-49, D-81). The
interpreter has nothing to compile, so the other options are only checked. The profile :explain is not
implemented yet and raises ArgumentError (Feel.Backend.validate_profile!/1).
iex> {:ok, executable} = Feel.Backend.Interpreter.compile(:an_ir, [])
iex> executable.ir
:an_ir
@spec evaluate(Feel.IR.t(), Feel.bindings(), Feel.Runtime.Env.t()) :: {Feel.Value.t(), Feel.Runtime.Env.t()}
Evaluates an IR on a set of bindings and an environment, as run/3 does, and returns {value, env} without catching
the end of a run (Feel.Runtime.Env.run/2 is what does).
It is the walk itself, for what runs the interpreter in the middle of a run: the guarded fast path of the BEAM backend
(Feel.Backend.Beam.Scalar) runs an expression this way from the environment it was given when it finds that it
cannot answer, through Feel.Runtime.Interpret (D-164).
iex> ir = {:arithmetic, %{span: {0, 5}}, :add, {:const, %{span: {0, 1}}, Decimal.new(1)}, {:var, %{span: {4, 5}}, "x"}}
iex> {value, _env} = Feel.Backend.Interpreter.evaluate(ir, %{"x" => Decimal.new(2)}, Feel.Runtime.Env.new())
iex> value
Decimal.new(3)
@spec run(t(), Feel.bindings(), [Feel.run_option()]) :: Feel.result(Feel.Value.t())
Evaluates an executable on a set of bindings.
Takes the run options of Feel.run/3 and raises ArgumentError for any other.
iex> ir = {:const, %{span: {0, 1}, type: :number}, Decimal.new(7)}
iex> {:ok, executable} = Feel.Backend.Interpreter.compile(ir, [])
iex> Feel.Backend.Interpreter.run(executable, %{}, [])
{:ok, Decimal.new(7), []}
iex> ir = {:var, %{span: {0, 1}, type: :any}, "x"}
iex> {:ok, executable} = Feel.Backend.Interpreter.compile(ir, [])
iex> Feel.Backend.Interpreter.run(executable, %{"x" => "a"}, [])
{:ok, "a", []}
@spec run_monitored(t(), Feel.bindings(), [Feel.run_option()], monitor()) :: Feel.result(Feel.Value.t())
Evaluates an executable and tells monitor, a function of a node and its value, the value of every node as it is made
(Feel.Paranoid). The run is the run of run/3.
iex> ir = {:const, %{span: {0, 1}, type: :number}, Decimal.new(7)}
iex> {:ok, executable} = Feel.Backend.Interpreter.compile(ir, [])
iex> test = self()
iex> Feel.Backend.Interpreter.run_monitored(executable, %{}, [], fn _node, value -> send(test, {:value, value}) end)
{:ok, Decimal.new(7), []}
iex> receive do {:value, value} -> value end
Decimal.new(7)