# `Tptp.Span`
[🔗](https://github.com/jcschuster/tptp/blob/v0.1.3/lib/tptp/span.ex#L1)

A byte range within a file.

Spans record byte offsets rather than line and column pairs. Tracking a line and
column in the scanner costs a branch and two increments per byte for information
most tokens never require; a line index is constructed once per file instead, and
only where a diagnostic is rendered.

The `file` field is required by `include` resolution. A declaration may originate
in an axiom file and be used in the problem file, and a diagnostic concerning it
must refer to both, so a span is not interpretable without the file it indexes.

Nodes do not store spans. `%Tptp.Node{}` carries an offset and a length, and a
span is constructed on demand, the file identifier being a property of the file
rather than of each of its nodes.

# `file_id`

```elixir
@type file_id() :: non_neg_integer()
```

Which file a span is in.

An integer rather than a path, because `include` makes spans cross files and
every statement, diagnostic and symbol has to carry one.

# `line_index`

```elixir
@opaque line_index()
```

Line-start byte offsets, one per line, as a tuple for constant-time indexing.

# `t`

```elixir
@type t() :: %Tptp.Span{
  file: file_id(),
  length: non_neg_integer(),
  offset: non_neg_integer()
}
```

A byte range in one file.

# `ending`

```elixir
@spec ending(t()) :: non_neg_integer()
```

The byte just past the end of the span.

# `line_column`

```elixir
@spec line_column(line_index(), non_neg_integer()) :: {pos_integer(), pos_integer()}
```

Resolve a byte offset to a one-based line and column, by binary search.

    iex> index = Tptp.Span.line_index("fof(a,axiom,p).\nfof(b,axiom,q).\n")
    iex> Tptp.Span.line_column(index, 0)
    {1, 1}
    iex> Tptp.Span.line_column(index, 16)
    {2, 1}
    iex> Tptp.Span.line_column(index, 20)
    {2, 5}

# `line_count`

```elixir
@spec line_count(line_index()) :: pos_integer()
```

The number of lines the index covers.

# `line_index`

```elixir
@spec line_index(binary()) :: line_index()
```

Build the line index for a file.

One pass, using `:binary.matches/2`, which is a NIF. Build it lazily: a corpus
run that reports no diagnostics should never pay for it.

# `new`

```elixir
@spec new(file_id(), non_neg_integer(), non_neg_integer()) :: t()
```

A span over `length` bytes starting at `offset` in file `file`.

# `offset`

```elixir
@spec offset(line_index(), integer(), integer()) :: {:ok, non_neg_integer()} | :error
```

Resolve a one-based line and column to a byte offset: the inverse of
`line_column/2`.

The column counts bytes, as `line_column/2` does. A line's last column is its
line feed. `:error` is returned for a line the index does not have, a column
below one, or a column past the line feed. The index does not record where the
source ends, so a column on the last line is not bounded above; check the
result against `byte_size/1` of the source where that matters.

    iex> index = Tptp.Span.line_index("fof(a,axiom,p).\nfof(b,axiom,q).\n")
    iex> Tptp.Span.offset(index, 2, 5)
    {:ok, 20}
    iex> Tptp.Span.offset(index, 1, 16)
    {:ok, 15}
    iex> Tptp.Span.offset(index, 1, 17)
    :error
    iex> Tptp.Span.offset(index, 4, 1)
    :error

# `text`

```elixir
@spec text(t(), binary()) :: binary()
```

The bytes the span covers.

The result is a sub-binary: four words and no copy, holding a reference into
`source`. That is what you want while the file is alive anyway, and a leak when
it is not — a thirty-byte functor name can retain a ten-megabyte file. Use
`:binary.copy/1` at the boundary where a name outlives its file.

# `union`

```elixir
@spec union(t(), t()) :: t()
```

The smallest span covering both, which must be in the same file.

---

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