Feel.Runtime.UnaryTests (feel v0.1.0)

Copy Markdown View Source

What it means for a value to satisfy unary tests (DMN 1.6, sections 7.3.2 and 10.3.2.10; notes 08 §4.6 and §4.8).

The analyzer lowers each test to an expression about the input ?: < 5 becomes ? < 5, [1..5] becomes ? >= 1 and ? <= 5, and a value such as "a" becomes ? = "a" (D-40). Two things are left for the run, and are here:

  • matches/3: a test that is an expression whose value is only known at run time. Its value decides what the test means: a list is a set of alternatives, a range is containment, a boolean that is made from ? is the answer, and anything else is equality with the input.
  • satisfied/2: the tests together. They are alternatives, so a true decides and a null is neither true nor false; the result is satisfied only when it is true, or for not(...) only when it is false.

Neither function gives null. A test that gives null (a null input against < 5, or a number against "a") is not satisfied, and a negated list of tests with such a result is not satisfied either (U2 in the notes).

Summary

Functions

The three-valued OR of the values of some tests: a true decides, and anything that is not a false leaves it open. It is what in gives for its tests (D-61), where satisfied/2 turns the open case into false.

Returns whether the input matches the value of a test that is an expression.

Returns whether the value of the subject of in is a member of the value of one of its tests (D-61).

Returns whether unary tests are satisfied, given the value of each test.

Functions

any(values)

@spec any([boolean() | nil]) :: boolean() | nil

The three-valued OR of the values of some tests: a true decides, and anything that is not a false leaves it open. It is what in gives for its tests (D-61), where satisfied/2 turns the open case into false.

iex> Feel.Runtime.UnaryTests.any([false, true, nil])
true
iex> Feel.Runtime.UnaryTests.any([false, nil])
nil
iex> Feel.Runtime.UnaryTests.any([false, false])
false
iex> Feel.Runtime.UnaryTests.any([])
false

matches(input, value, arg3)

Returns whether the input matches the value of a test that is an expression.

uses_input? says whether the expression refers to ?.

  • An expression that uses ? and is a boolean (or null) is the answer itself.
  • A list matches when the input equals one of its elements.
  • A range matches when the input lies within it.
  • Any other value matches when the input equals it: a null too, if the expression does not use ?.

The value decides and the type of the expression never does (D-209): an expression of ? that is null is null, which is not satisfied, as null < 5 is, and a null that does not come from ? (a name that is null) is equal to a null input. The analysis lowers a test to ? = expression only where that is the same.

Examples

iex> Feel.Runtime.UnaryTests.matches(Decimal.new(2), true, true)
{:ok, true}
iex> Feel.Runtime.UnaryTests.matches(Decimal.new(2), [Decimal.new(1), Decimal.new(2)], false)
{:ok, true}
iex> range = %Feel.Value.Range{start: Decimal.new(1), end: Decimal.new(5), start_included: true, end_included: false}
iex> Feel.Runtime.UnaryTests.matches(Decimal.new(5), range, false)
{:ok, false}
iex> Feel.Runtime.UnaryTests.matches(nil, nil, false)
{:ok, true}
iex> Feel.Runtime.UnaryTests.matches(nil, nil, true)
{:ok, nil}
iex> Feel.Runtime.UnaryTests.matches(Decimal.new(2), "a", false) |> elem(0)
:error

member(input, value, uses_input?)

Returns whether the value of the subject of in is a member of the value of one of its tests (D-61).

It is matches/3, except for a list, which Table 56 reads as list contains: the subject is in it when it equals an element, or lies within an element that is a range, and an element that it cannot be compared with is not equal to it, where matches/3 makes that an error.

iex> list = [Decimal.new(1), "a", Decimal.new(3)]
iex> Feel.Runtime.UnaryTests.member(Decimal.new(3), list, false)
{:ok, true}
iex> Feel.Runtime.UnaryTests.member(Decimal.new(2), list, false)
{:ok, false}
iex> range = %Feel.Value.Range{start: Decimal.new(5), end: Decimal.new(9), start_included: true, end_included: true}
iex> Feel.Runtime.UnaryTests.member(Decimal.new(6), [Decimal.new(1), range], false)
{:ok, true}
iex> Feel.Runtime.UnaryTests.member(true, [false, Decimal.new(2)], false)
{:ok, false}

A subject that is found at the front stops the search:

iex> list = [Decimal.new(1), "a", Decimal.new(3)]
iex> Feel.Runtime.UnaryTests.member(Decimal.new(1), list, false)
{:ok, true}

satisfied(atom, values)

@spec satisfied(:any | :positive | :negated, [boolean() | nil]) :: boolean()

Returns whether unary tests are satisfied, given the value of each test.

kind is :any for -, :positive for a list of tests, and :negated for not(...). Each value is true, false or null.

iex> Feel.Runtime.UnaryTests.satisfied(:positive, [false, true])
true
iex> Feel.Runtime.UnaryTests.satisfied(:positive, [false, nil])
false
iex> Feel.Runtime.UnaryTests.satisfied(:negated, [false, false])
true
iex> Feel.Runtime.UnaryTests.satisfied(:negated, [false, nil])
false
iex> Feel.Runtime.UnaryTests.satisfied(:any, [])
true