Parses a type expression (DMN 1.6, grammar rule 52) from tokens.
type = qualified name
| "range" "<" type ">"
| "list" "<" type ">"
| "context" "<" name ":" type { "," name ":" type } ">"
| "function" "<" [ type { "," type } ] ">" "->" type
| type "?"The result is a Feel.AST type node, for example
{:list_type, meta, {:type_name, meta, ["number"]}}. It does not look names up:
Feel.Type.from_ast/2 turns the node into a Feel.Type.t/0.
list, range, and context are type constructors only when a < follows;
otherwise they are ordinary names. A context member name is a name or a string
literal.
Which tokens form a type name
A type name can have several words: date and time, years and months duration,
or a name from a model. A type is also followed by other tokens: x instance of date and time and y is not a type named date and time and y. The parser is
therefore given the names of the types that exist, and takes the longest run of
name tokens that is one of them. If none is, it takes the first token and the
. and name that follow it, if any, since import.Employee is one qualified
name (grammar rule 18), so that the analyzer can report the unknown name.
The run of name tokens after a type can be as long as the expression (x instance of number and x instance of number and … is all name tokens), so it is
not taken out of the tokens and tried at every length: a name is made one token
at a time and the scan ends when it is not the start of any name that exists
(D-177).
The parser stops after one type and returns the tokens that are left.
Nesting
A type nests: list<list<number>>, the type of each member of a context<>, the parameters of a function<> and the type
after its arrow. Each is one level deeper than the type around it (the result after an arrow too, since
function<number> -> function<number> -> … nests with no bracket), and parse/3 refuses a type that goes deeper than
max_depth with the code :nesting_too_deep and the message the parser gives for an expression (D-154). A type is a part of
an expression, so depth says how deep the expression around it is already. With no max_depth there is no limit, which is
how Feel.Type.parse/2 reads the types that a host declares.
A trailing !
A ! marks the type it follows as not null, a {:non_null_type, meta, type} node
(D-48): number!, list<number!>, list<number>!. It is not part of FEEL; it is
an extension, which a type in an expression has as the declarations of a Feel.Scope
do (D-48). A ! that starts != is that operator, so number != x is read as usual
and number!=x is too.
Summary
Types
An option of parse/3: :depth, how deeply the type is nested already (0), and :max_depth, how deeply expressions may nest
(:infinity).
The result: the type, and the tokens after it.
Functions
Parses one type at the start of tokens. The options are :depth and :max_depth (see "Nesting"), and :composite, whether a
union and Any except are read (false).
Types
@opaque names()
The names of the types that exist, ready to be read from (see names/1).
@type option() :: {:depth, non_neg_integer()} | {:max_depth, non_neg_integer() | :infinity} | {:composite, boolean()}
An option of parse/3: :depth, how deeply the type is nested already (0), and :max_depth, how deeply expressions may nest
(:infinity).
@type result() :: {:ok, Feel.AST.type_expression(), [Feel.Syntax.Token.t()]} | {:error, [Feel.Diagnostic.t(), ...]}
The result: the type, and the tokens after it.
Functions
@spec names(MapSet.t(Feel.Name.t())) :: names()
Prepares the canonical names of the types that exist (see Feel.Name) for
parse/2, which takes them as they are too.
A caller that parses many types with the same names (every instance of of an
expression) prepares them once: parse/2 looks a name up in them by what it
starts with, to read it no further than it can go, however long the expression
after it is.
iex> names = Feel.Syntax.TypeParser.names(MapSet.new(["date and time"]))
iex> {:ok, tokens} = Feel.Syntax.Lexer.tokenize("date and time and x")
iex> {:ok, {:type_name, _, words}, [_and, _x]} = Feel.Syntax.TypeParser.parse(tokens, names)
iex> words
["date", "and", "time"]
@spec parse([Feel.Syntax.Token.t()], MapSet.t(Feel.Name.t()) | names(), [option()]) :: result()
Parses one type at the start of tokens. The options are :depth and :max_depth (see "Nesting"), and :composite, whether a
union and Any except are read (false).
known_names is the set of canonical names of the types that exist (see
Feel.Name), or names/1 of it, used to decide where a multi-word type name
ends.
iex> {:ok, tokens} = Feel.Syntax.Lexer.tokenize("list<date and time>")
iex> {:ok, type, []} = Feel.Syntax.TypeParser.parse(tokens, MapSet.new(["date and time"]))
iex> type
{:list_type, %{span: {0, 19}}, {:type_name, %{span: {5, 18}}, ["date", "and", "time"]}}