# `Feel.Builtin.Arguments`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/builtin/arguments.ex#L1)

What the built-in functions share in dealing with their arguments (DMN 1.6, section 10.3.4, D-79).

A function is called with the values of its arguments and gives an outcome
(`t:Feel.Runtime.Env.outcome/0`). An argument outside the domain of the function makes the result `null`
(section 10.3.4: "Whenever a parameter is outside its domain, the result of the built-in is null"), and that
is an error that the run reports, `:argument_out_of_domain`; this module makes the diagnostic. A `null`
that is only missing data is `:missing`, as for an operator.

## The implicit conversions of an argument (section 10.3.2.9.4)

  * **From a singleton list.** A list of one element is its element where a list is not what the
    parameter takes: `contains(["foobar"], "of")` is `contains("foobar", "of")`. `singleton/1` is that.
  * **From a number to an integer.** Where a function wants an integer (a position, a length, a scale, a
    year), the fraction is cut off, toward zero, before the domain is checked. `integer/1` is that.
  * **From a value to a singleton list.** Where a function wants a list, a value that is not one is the list
    of it, and `null` is not a value to convert. `list/1` is that.

The functions apply them, and say nothing. The run reports them as an `:info`, from the signatures and not from the
functions (`conversions/2`, `Feel.Builtin.call_at/5`, D-46): a list of one element that a parameter takes as its
element, and a value that it takes as a list of it; and, for a parameter that is flagged `integer: true`
(`Feel.Builtin.Function.parameter/3`), the fractional part of a number that is cut (D-46, "from decimal to integer"
in the specification's list).

# `conversion`

```elixir
@type conversion() ::
  {non_neg_integer(), String.t(), Feel.Type.t(),
   [Feel.Type.Conversion.step(), ...]}
```

A conversion of an argument: its place (from 0), the name of the parameter, the type the parameter took it as,
and the steps (`t:Feel.Type.Conversion.step/0`).

# `described`

```elixir
@type described() ::
  Feel.Value.t()
  | Feel.Type.t()
  | [described()]
  | %{optional(String.t()) =&gt; described()}
```

A value as a diagnostic keeps it: the value, with each function in it as its type.

# `arithmetic`

```elixir
@spec arithmetic(Feel.Runtime.Number.result(), String.t(), [Feel.Value.t()]) ::
  {:ok, Decimal.t()} | {:error, Feel.Diagnostic.t()}
```

The outcome for a result that a number cannot hold: `null` with the error of the function, which has
the arguments it was given. A number that is fine is the value.

    iex> Feel.Builtin.Arguments.arithmetic({:ok, Decimal.new(1)}, "sum", [])
    {:ok, Decimal.new(1)}
    iex> {:error, diagnostic} = Feel.Builtin.Arguments.arithmetic({:error, :overflow}, "sum", [1])
    iex> diagnostic.message
    "sum: the result is out of range"

# `compared`

```elixir
@spec compared(Feel.Builtin.Function.signature(), [Feel.Value.t()]) :: [
  Feel.Value.t()
]
```

The values that a call compares, for the message of an error: the items of a signature that says its arguments are
items (`max(c1, ..., cn)`, `max(list)`: for a list alone, its elements), and the arguments for any other. It is what
`out_of_domain_among/3` names, so that the function, the check of a call by name (`Feel.Builtin.call/4`) and the
analysis say the same words (D-155).

    iex> {:ok, max} = Feel.Builtin.function("max")
    iex> [_of_list, of_items] = max.signatures
    iex> Feel.Builtin.Arguments.compared(of_items, [["a", Decimal.new(1)]])
    ["a", Decimal.new(1)]
    iex> Feel.Builtin.Arguments.compared(of_items, ["a", Decimal.new(1)])
    ["a", Decimal.new(1)]
    iex> {:ok, before} = Feel.Builtin.function("before")
    iex> Feel.Builtin.Arguments.compared(hd(before.signatures), [["a"], "b"])
    [["a"], "b"]

# `conversion_info`

```elixir
@spec conversion_info(
  String.t(),
  String.t(),
  Feel.Type.t(),
  Feel.Value.t(),
  [Feel.Type.Conversion.step(), ...],
  Feel.Diagnostic.span() | nil
) :: Feel.Diagnostic.t()
```

The `:info` for a conversion of an argument (D-46): the argument, the function, the type the parameter
took it as and the steps. The cut of a fraction is a conversion to an integer, which no type is.

    iex> info = Feel.Builtin.Arguments.conversion_info("contains", "string", :string, ["foobar"], [:from_singleton], {0, 5})
    iex> {info.severity, info.code, info.message}
    {:info, :implicit_conversion, ~s(the argument "string" of contains is converted to string: a list with one element is replaced by that element)}

# `conversions`

```elixir
@spec conversions([Feel.Builtin.Function.signature()], [Feel.Value.t()]) :: [
  conversion()
]
```

The implicit conversions (section 10.3.2.9.4, D-46) by which the signatures take the values: for each argument that
a parameter takes only converted, where it is in the call, the name of the parameter, the type it took it as and the
steps. A list of one element given where a parameter takes the element is `:from_singleton`, a value given where it
takes a list is `:to_singleton`.

Of the signatures with that many parameters, if one takes the values as they are the call is not converted, and
otherwise the first that takes them converted says. If none takes them the call is `[]` too: that is the error of the
call (`mismatch?/2`) and not a conversion. A signature of an extension takes what the extension takes, so it converts
nothing.

    iex> {:ok, contains} = Feel.Builtin.function("contains")
    iex> Feel.Builtin.Arguments.conversions(contains.signatures, [["foobar"], "of"])
    [{0, "string", :string, [:from_singleton]}]
    iex> Feel.Builtin.Arguments.conversions(contains.signatures, ["foobar", "of"])
    []
    iex> {:ok, count} = Feel.Builtin.function("count")
    iex> Feel.Builtin.Arguments.conversions(count.signatures, [Decimal.new(5)])
    [{0, "list", {:list, :any}, [:to_singleton]}]
    iex> Feel.Builtin.Arguments.conversions(contains.signatures, [Decimal.new(1), "of"])
    []

# `describe`

```elixir
@spec describe(Feel.Value.t()) :: described()
```

A value as a diagnostic keeps it. A function value holds an Elixir function, which two backends do not share, and a
built-in function holds its signatures, so a diagnostic has the type of a function (`Feel.Value.Function.type/1`).

    iex> {:ok, abs} = Feel.Builtin.value("abs")
    iex> Feel.Builtin.Arguments.describe([1, abs])
    [1, {:function, [:any], :any}]

# `integer`

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

The integer that a number stands for where an integer is wanted: the number with its fraction cut off.
A number too large to be a position or a count of anything is clamped, so it is out of range all the same
and costs nothing. Anything that is not a number is `:error`.

    iex> Feel.Builtin.Arguments.integer(Decimal.new("2.9"))
    {:ok, 2}
    iex> Feel.Builtin.Arguments.integer(Decimal.new("-2.9"))
    {:ok, -2}
    iex> Feel.Builtin.Arguments.integer("2")
    :error

# `kind_name`

```elixir
@spec kind_name(Feel.Value.t()) :: String.t()
```

The name of the kind of a value, as the message of an error says it.

# `list`

```elixir
@spec list(Feel.Value.t()) :: {:ok, [Feel.Value.t()]} | :error
```

A list where a list is wanted: a list is itself, any other value is the list of that one value
(the conversion to a singleton list, section 10.3.2.9.4), and `null` is no list, so `:error`.

    iex> Feel.Builtin.Arguments.list([1, 2])
    {:ok, [1, 2]}
    iex> Feel.Builtin.Arguments.list("a")
    {:ok, ["a"]}
    iex> Feel.Builtin.Arguments.list(nil)
    :error

# `mismatch?`

```elixir
@spec mismatch?(Feel.Builtin.Function.signature(), [Feel.Value.t()]) :: boolean()
```

Whether the values are arguments that the signature does **not** take (D-56): one of them is of a kind that its
parameter does not take, with the conversions above allowed. It is the check that `Feel.Analysis.Calls.mismatch/3`
makes on the types of the arguments, made on the values, for a call that the analyzer could not decide.

A function is given the values and not the signature that a call by name chose, so `context put(key: ["y", "a"])`
would be read as `keys`. A call that has a signature (`Feel.Builtin.call/4`) is checked against it first, and a
value that it does not take makes the result `null`, as it does for a call of the other signature.

`null` and a value of a kind that is none of FEEL's are never a mismatch, as a parameter that takes `any`
takes everything. A list of one element is its element, a value is the list of it, and the elements of a
list are looked at until one is taken. A signature of an extension (`extension: true`) takes what the extension takes.
Values that the signature says are of one kind (`same_kind`, D-155) and are of two, or one that has no order, are a
mismatch as well.

    iex> {:ok, put} = Feel.Builtin.function("context put")
    iex> [key, keys] = put.signatures
    iex> context = %{"x" => Decimal.new(1)}
    iex> Feel.Builtin.Arguments.mismatch?(key, [context, ["y", "a"], Decimal.new(2)])
    true
    iex> Feel.Builtin.Arguments.mismatch?(key, [context, ["y"], Decimal.new(2)])
    false
    iex> Feel.Builtin.Arguments.mismatch?(keys, [context, ["y", "a"], Decimal.new(2)])
    false
    iex> Feel.Builtin.Arguments.mismatch?(key, [context, nil, Decimal.new(2)])
    false
    iex> {:ok, before} = Feel.Builtin.function("before")
    iex> [points | _] = before.signatures
    iex> Feel.Builtin.Arguments.mismatch?(points, ["a", Decimal.new(2)])
    true
    iex> Feel.Builtin.Arguments.mismatch?(points, [["a"], "b"])
    false

# `number?`

```elixir
@spec number?(Feel.Value.t()) :: boolean()
```

Returns `true` for a FEEL number.

# `out_of_domain`

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

The outcome for an argument that is outside the domain of a function: `null`, with an error that
says which kinds of value the function was given.

    iex> {:error, diagnostic} = Feel.Builtin.Arguments.out_of_domain("abs", ["x"])
    iex> {diagnostic.code, diagnostic.message}
    {:argument_out_of_domain, "abs is not defined for string"}

# `out_of_domain`

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

The same, with the reason, for an argument of the right kind that the function cannot take.

    iex> {:error, diagnostic} = Feel.Builtin.Arguments.out_of_domain("modulo", [1, 0], "the divisor is zero")
    iex> diagnostic.message
    "modulo: the divisor is zero"

# `out_of_domain_among`

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

As `out_of_domain/2`, with the kinds of the values that were compared in the message, and the arguments in the details:
the items of `max(["a", 1])` are `"a"` and `1` (`compared/2`), and the message says `string, number` and not `list`.

    iex> {:error, diagnostic} = Feel.Builtin.Arguments.out_of_domain_among("max", [["a", Decimal.new(1)]], ["a", Decimal.new(1)])
    iex> {diagnostic.message, diagnostic.details.arguments}
    {"max is not defined for string, number", [["a", Decimal.new(1)]]}

# `singleton`

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

A list of one element is the element; anything else is as it is.

    iex> Feel.Builtin.Arguments.singleton(["foobar"])
    "foobar"
    iex> Feel.Builtin.Arguments.singleton(["a", "b"])
    ["a", "b"]
    iex> Feel.Builtin.Arguments.singleton("a")
    "a"

# `unwrapping`

```elixir
@spec unwrapping((Feel.Runtime.Env.t(), [Feel.Value.t()] -&gt; result)) ::
  (Feel.Runtime.Env.t(), [Feel.Value.t()] -&gt;
     result)
when result: var
```

The call of a function all of whose parameters take a scalar, made to take a list of one element as its element
(section 10.3.2.9.4, "from singleton list"; D-56): `date(["2020-01-02"])` is `date("2020-01-02")`. The signatures
already say so (`conversions/2`, and the check of D-56), and the run reports it (D-46).

    iex> twice = fn _env, [n] -> {:ok, Decimal.mult(n, 2)} end
    iex> Feel.Builtin.Arguments.unwrapping(twice).(nil, [[Decimal.new(2)]])
    {:ok, Decimal.new(4)}
    iex> Feel.Builtin.Arguments.unwrapping(twice).(nil, [Decimal.new(2)])
    {:ok, Decimal.new(4)}

---

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