# `Feel.Backend.Beam.Scalar`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/backend/beam/scalar.ex#L1)

The guarded fast path of the BEAM backend, for the expressions that only do scalar work on declared names (D-164).

The general emitter calls shared runtime operations. This specialization removes
dispatch and repeated kind checks where declared scalar types establish the required
assumptions. Numeric and boolean operations no longer carry a step counter. String
construction still checks individual value-size limits.

## The subset

An expression is in it when its nodes are all of these, and there are at most 32 of them:

  * constants: a number, a string, `true` or `false`;
  * a name that is read from the bindings, whose kind is `number`, `string` or `boolean` (the type that the scope
    declares for it, or, if the expression was not given declarations, the one the analyzer found);
  * `+ - * /` of two numbers, `+` of two strings, and `-x` of a number;
  * `=` and `!=` of two numbers, two strings or two booleans, and `<`, `<=`, `>` and `>=` of two numbers;
  * `and`, `or` and `not(x)` of booleans, and `between` of three numbers;
  * `in`, with tests that are such expressions of booleans, which is what a range or a comparison written as a unary
    test becomes (`age in [18..65)`);
  * `if then else` of a boolean, where it is the value of the expression or the value of a branch of one that is, so
    that what follows a branch is the end of the run (`if a then 1 else if b then 2 else 3`); an `if` that is an operand
    is left to the emitter.

Everything else (contexts, lists, loops, functions, calls of built-in functions other than `not`, paths, dates and
durations, the unary tests themselves, allowed values, a declared result) is left to the emitter, and so is an
expression that has no name to read, which the optimizer has folded.

## What it does

It reads the names it needs from the bindings in one pattern, with a guard for each that says it is a number (a
`Decimal` that is finite, of at most 34 digits and an exponent from -6176 to 6111: a guard cannot count the digits of a
coefficient, so a number with a larger exponent, `1E+6144`, takes the clause that does not match and the fallback;
D-214), a string or a boolean, and a string that is declared is asked whether it is
valid UTF-8 (`Feel.Runtime.Kind.string?/1`), as the check of the declared types asks. A value that is not one, a name
that has no binding, or a binding that is spelled differently, is a clause that does not match, and so is a `null`
where the name is read by an operation that reports it (below). The declared names that the
expression does not read are checked as well, since the declared types are checked for all of them. With the values
in hand, the operations are done in a straight line without an environment and without an outcome: a `case` on
`'Elixir.Feel.Runtime.Number':multiply(A, B)` for an operation that gives a number, a comparison of the order that
`Feel.Runtime.Number.compare/2` gives, the operators of the language for strings and booleans. The largest constructed
string is checked against `max_string_length` at the end of each path, including intermediate
strings when the final result is boolean. Successful paths return the original environment.

    run(Bindings, E0) ->
        case Bindings of
            #{<<"price">> := #{'__struct__' := 'Elixir.Decimal', coef := C1, exp := X1} = P1,
              <<"quantity">> := #{'__struct__' := 'Elixir.Decimal', coef := C2, exp := X2} = P2,
              <<"tax">> := #{'__struct__' := 'Elixir.Decimal', coef := C3, exp := X3} = P3}
                when is_integer(C1), C1 < 10^34, is_integer(X1), X1 >= -6176, X1 =< 6111,
                     ...the same for C2, X2 and C3, X3 ->
                case 'Elixir.Feel.Runtime.Number':multiply(P1, P2) of
                    {ok, V1} ->
                        case 'Elixir.Feel.Runtime.Number':add(Decimal1, P3) of
                            {ok, V2} ->
                                case 'Elixir.Feel.Runtime.Number':multiply(V1, V2) of
                                    {ok, V3} -> {V3, E0};
                                    _ -> fallback(Bindings, E0)
                                end;
                            _ -> fallback(Bindings, E0)
                        end;
                    _ -> fallback(Bindings, E0)
                end;
            _ -> fallback(Bindings, E0)
        end.

    fallback(Bindings, E0) ->
        'Elixir.Feel.Runtime.Interpret':run(IR, Bindings, E0).

## A `null`

Most operations report a `null` operand (`Feel.Runtime.NullOperand`, D-103), and the path leaves those to the
interpreter. Some answer for it and report nothing: `<` and `>` are `null`, `=` and `!=` are a boolean (`null = null`
is `true`, a number is not equal to `null`), `and`, `or` and `not` are the three-valued logic (`false and null` is
`false`), and the condition of an `if` that is not `true` takes the `else`. The arithmetic, `<=`, `>=`, `between`,
negation, the join of strings and the subject of an `in` report it. Every value that the path makes knows the names
that make it `null` (none: it is a value of its kind), and an operation that answers for one has code for it, which a
`case` of `nil` is: the same value and diagnostics as the interpreter gives.

A name may be `null` in the guard that reads it only if every operation it is an operand of answers for it. The first
that does not says which names it was given, they are made values (`strict`), and the code is made again: there is one
more strict name at each attempt, and an attempt is a walk of at most 32 nodes. A name declared `!` is strict from the
start, since a `null` of it ends the run. A name that is not strict is read by a guard that says `null` or a value of
its kind, which costs a little more than the pattern of a value, and a declared string that is not strict is asked to
be valid only if it is not `null`.

## What it does not do

It does not report anything, and it does not end a run. Anything that the interpreter would report or end the run for,
the path takes as a sign that it is not the one to answer: a `null` that is reported, a name that has no value, a value that has to be
converted to the declared type (a list of one number, a date for a date and time) or cannot be, an operation that gives
an error (an overflow, a division by zero, a number of more than 34 digits), and a constructed string longer than its limit.
It has changed nothing at that point, since it only makes values
until it returns, so the function `fallback/2` runs the whole expression from the environment it was given with the
interpreter's walk (`Feel.Runtime.Interpret.run/3`), whose answer is what the interpreter would have given: the
diagnostics, the place where a limit ends the run, and the strict mode's error. The path is exact for the runs it
answers because each of its steps is one the runtime takes, with the arguments the runtime would give it, and for the
others because it does not answer.

Only the taken branch of an `if` constructs values, as in the interpreter. Each branch checks
its own largest string, so an untaken branch cannot cause a size failure.
The dialect of the expression is not read: nothing in the runtime
reads it for an operation that gives a value, and the operations that would (the ones that give an error) are not the
path's.

The IR that `fallback/2` runs is a literal of the module, with the metadata of each node cut down to the span (and the
error and the signature of a call, and `boxed` of a boxed `if`), which is all that the interpreter reads of the nodes
of the subset: a module that
has the whole IR is 100 to 200 µs longer to compile, and one that has it as a binary takes 1 to 3 µs to read it for each
run that is not answered.

## The compile

The function is nested one `case` deep for each operation that gives a number, with the names read at the start, so
every name is alive until it is used, and the Erlang compiler takes more than linear time for a function of many
of those (D-113): for arithmetic alone it takes 1.2 to 1.5 times as long as for the emitter's code for 4 to 32 nodes
(a chain of `+` of 16 names is the worst), for comparisons 0.7 to 0.9 times, and for an `if` chain or a mix of
operations about as long. The subset is therefore bounded, and an expression of more nodes than that keeps the code of
the emitter, which `Feel.Backend.Beam.Chunks` cuts.

# `emit`

```elixir
@spec emit(Feel.IR.t()) :: {:ok, [:erl_parse.abstract_form()]} | :error
```

Returns the forms of `run/2` and `fallback/2` for an IR in the subset, or `:error` for one that is not.

    iex> ir = {:conform_bindings, %{span: {0, 5}}, :enforce, [{"x", {0, 1}, :number}],
    ...>   {:arithmetic, %{span: {0, 5}}, :add, {:var, %{span: {0, 1}, type: [:number, :null]}, "x"},
    ...>     {:const, %{span: {4, 5}, type: [:number]}, Decimal.new(1)}}}
    iex> {:ok, [{:function, _, :run, 2, _}, {:function, _, :fallback, 2, _}]} = Feel.Backend.Beam.Scalar.emit(ir)
    iex> Feel.Backend.Beam.Scalar.emit({:const, %{span: {0, 1}, type: [:number]}, Decimal.new(1)})
    :error

---

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