Dmn.Compiler.Source (dmn v0.1.0)

Copy Markdown View Source

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

Types

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

t()

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.

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.

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

finished()

@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()

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

t()

@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

add(source, text, origin, box \\ nil)

@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(finished, span)

@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(arg1, arg2)

@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(source)

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

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

glue(source, origin, box \\ nil)

@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(finished, span)

@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()

@spec new() :: t()

An empty source.