# `Dmn.Compiler.Source`
[🔗](https://github.com/FeeJai/BDM/blob/dmn-v0.1.0/apps/dmn/lib/dmn/compiler/source.ex#L1)

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.

# `finished`

```elixir
@type finished() :: %{text: String.t(), parts: tuple()}
```

A finished source: the text, and the parts in order, as a tuple that `locate/2` searches.

# `origin`

```elixir
@type origin() :: {String.t(), [term()]}
```

# `t`

```elixir
@type t() :: %Dmn.Compiler.Source{
  parts: [
    {non_neg_integer(), non_neg_integer(), origin(), Dmn.Compiler.Box.t() | nil}
  ],
  size: non_neg_integer(),
  text: iodata()
}
```

# `add`

```elixir
@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"

# `box`

```elixir
@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

# `find`

```elixir
@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}

# `finish`

```elixir
@spec finish(t()) :: finished()
```

The text, and the parts in a form that can be searched.

# `glue`

```elixir
@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}

# `locate`

```elixir
@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}}

# `new`

```elixir
@spec new() :: t()
```

An empty source.

---

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