The text that a unit is prepared from, and where each part of it came from.
FEEL reports where it found a problem as a byte range of a text (Feel.Diagnostic). A unit of a model is made of many texts (the
literal expression of a decision, the input entries of a rule, an output entry) and of syntax that stands for a boxed
expression and has no text. So the unit is prepared from a virtual source, the texts one after another, with the trees
of their parses moved to where they are (Feel.AST.shift/2), and a source map from a range of it to the part of the model
it is from.
Every part takes at least one byte (a newline follows its text), so the glue of a boxed expression, which has no text, has a place of its own too: a node of it is given the zero-width range at that place and a diagnostic on it is found in that part.
An origin is {element, path}: element is the id of the element of the model (or its name, if it has none) and path the
place in it, a list such as [:rule, 3, :input_entry, 2]. A part also has the box it is in (Dmn.Compiler.Box, nil for a
part that is in none), which box/2 gives for a range, so every node of a tree can be asked which boxed expression it came
from, whether it was parsed from a text or built from the syntax of a box.
Summary
Functions
Appends a text that came from origin. Returns where it starts, and the source.
The boxed expression that a range of the text came from, by where it starts: nil for a range outside the source and for a
part that is in no box.
The text, and the parts in a form that can be searched.
Appends a part that has no text, for the syntax that stands for a boxed expression. Returns the zero-width range that its nodes are given.
The origin of a range of the text, by where it starts, or nil for a range outside it.
An empty source.
Types
A finished source: the text, and the parts in order, as a tuple that locate/2 searches.
@type t() :: %Dmn.Compiler.Source{ parts: [ {non_neg_integer(), non_neg_integer(), origin(), Dmn.Compiler.Box.t() | nil} ], size: non_neg_integer(), text: iodata() }
Functions
@spec add(t(), String.t(), origin(), Dmn.Compiler.Box.t() | nil) :: {non_neg_integer(), t()}
Appends a text that came from origin. Returns where it starts, and the source.
iex> {first, source} = Dmn.Compiler.Source.add(Dmn.Compiler.Source.new(), "a + 1", {"d1", [:expression]})
iex> {second, source} = Dmn.Compiler.Source.add(source, "b", {"d2", [:expression]})
iex> {first, second}
{0, 6}
iex> Dmn.Compiler.Source.finish(source).text
"a + 1\nb\n"
@spec box(finished(), Feel.Diagnostic.span() | nil) :: Dmn.Compiler.Box.t() | nil
The boxed expression that a range of the text came from, by where it starts: nil for a range outside the source and for a
part that is in no box.
iex> box = %Dmn.Compiler.Box{kind: :filter, id: "f1", boolean_part: :match}
iex> {span, source} = Dmn.Compiler.Source.glue(Dmn.Compiler.Source.new(), {"d1", [:expression]}, box)
iex> finished = Dmn.Compiler.Source.finish(source)
iex> Dmn.Compiler.Source.box(finished, span).kind
:filter
iex> Dmn.Compiler.Source.box(finished, nil)
nil
@spec find(finished(), Feel.Diagnostic.span() | nil) :: {String.t(), [term()], non_neg_integer(), Dmn.Compiler.Box.t() | nil} | nil
Where a range of the text came from, by where it starts: the element, the place in it, how far into the text of that part
it starts, and the box (nil for a part in none). nil for a range outside the text. One search, for what needs more than
one of them; locate/2 and box/2 are its parts.
iex> box = %Dmn.Compiler.Box{kind: :list}
iex> {span, source} = Dmn.Compiler.Source.glue(Dmn.Compiler.Source.new(), {"d1", [:expression]}, box)
iex> {element, path, offset, found} = Dmn.Compiler.Source.find(Dmn.Compiler.Source.finish(source), span)
iex> {element, path, offset, found.kind}
{"d1", [:expression], 0, :list}
The text, and the parts in a form that can be searched.
@spec glue(t(), origin(), Dmn.Compiler.Box.t() | nil) :: {Feel.Diagnostic.span(), t()}
Appends a part that has no text, for the syntax that stands for a boxed expression. Returns the zero-width range that its nodes are given.
iex> {span, _source} = Dmn.Compiler.Source.glue(Dmn.Compiler.Source.new(), {"t1", [:table]})
iex> span
{0, 0}
@spec locate(finished(), Feel.Diagnostic.span() | nil) :: {String.t(), [term()], non_neg_integer()} | nil
The origin of a range of the text, by where it starts, or nil for a range outside it.
iex> {_, source} = Dmn.Compiler.Source.add(Dmn.Compiler.Source.new(), "a + 1", {"d1", [:expression]})
iex> {_, source} = Dmn.Compiler.Source.add(source, "b", {"d2", [:expression]})
iex> finished = Dmn.Compiler.Source.finish(source)
iex> {Dmn.Compiler.Source.locate(finished, {2, 3}), Dmn.Compiler.Source.locate(finished, {6, 7})}
{{"d1", [:expression], 2}, {"d2", [:expression], 0}}
@spec new() :: t()
An empty source.