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

A TPTP file as read: its statements, its comments and its diagnostics.

## Source retention

Every leaf's `text` is a sub-binary of `source`, so the source must outlive the
tree and is retained here. Reading a 4 MB problem therefore costs one 4 MB binary
and a tree of offsets rather than a copy of every symbol. `Tptp.detach/1` returns
a copy owning its own binaries, for consumers retaining a small number of
statements from a large file.

## Comments

The BNF permits a comment between any two tokens and does not treat comments as
white space, so they cannot be placed in the tree without appearing among the
children of arbitrary nodes. They are held as an ordered list of spans, which is
the form the format-preserving printer requires in order to reattach them by
position.

## Computed properties

`line_index/1` and `digest/1` are functions rather than fields. Both are linear in
the size of the source and both are required infrequently — the first to render a
position, the second to key a cache. Callers needing either repeatedly should
retain the result.

# `t`

```elixir
@type t() :: %Tptp.File{
  bnf_version: binary(),
  comments: [Tptp.Lexer.comment()],
  diagnostics: [Tptp.Diagnostic.t()],
  id: Tptp.Span.file_id(),
  path: Path.t() | nil,
  source: binary(),
  statements: [Tptp.Statement.t()]
}
```

One parsed TPTP file: its source, its statements, its comments and everything said about them.

# `any_errors?`

```elixir
@spec any_errors?(t()) :: boolean()
```

Whether anything error-severity was found.

    iex> {:ok, file, _diagnostics} = Tptp.from_string("fof(a,axiom,p q).")
    iex> Tptp.File.any_errors?(file)
    true

# `digest`

```elixir
@spec digest(t()) :: binary()
```

A SHA-256 of the file's bytes, for keying a cache across runs.

Pair it with `bnf_version` — the same bytes read against a different BNF can
produce a different tree, so a cache keyed on content alone will eventually hand
back a CST the current code cannot read.

# `format_diagnostics`

```elixir
@spec format_diagnostics(t()) :: [binary()]
```

Every diagnostic, rendered one per line against a single line index.

    iex> {:ok, file, _diagnostics} = Tptp.from_string("wibble(a).")
    iex> Tptp.File.format_diagnostics(file)
    ["1:1: error: `wibble` does not start a TPTP statement [TPTP0201]"]

# `formulae`

```elixir
@spec formulae(t()) :: [Tptp.Statement.Annotated.t()]
```

The annotated formulae, `include` directives excluded.

# `includes`

```elixir
@spec includes(t()) :: [Tptp.Statement.Include.t()]
```

The `include` directives this file names, in source order.

Derived rather than stored, because a second list of the same structs would be a
second thing to keep in step.

    iex> {:ok, file, []} = Tptp.from_string("fof(a,axiom,p). include('b.ax').")
    iex> file |> Tptp.File.includes() |> Enum.map(&Tptp.Statement.Include.path/1)
    ["b.ax"]

# `line_index`

```elixir
@spec line_index(t()) :: Tptp.Span.line_index()
```

A line-start index for turning offsets into line and column numbers.

O(bytes) to build and independent of the tree, so hold it if you are resolving
more than a handful of positions. `format_diagnostics/1` builds one and reuses it
for the whole list, which is the common case.

# `node_at`

```elixir
@spec node_at(t(), non_neg_integer()) :: Tptp.Node.t() | nil
```

The innermost CST node under `offset`, or `nil`.

    iex> {:ok, file, []} = Tptp.from_string("fof(a,axiom,p(bcd)).")
    iex> Tptp.File.node_at(file, 15).text
    "bcd"

# `statement_at`

```elixir
@spec statement_at(t(), non_neg_integer()) :: Tptp.Statement.t() | nil
```

The statement containing `offset`, or `nil`.

The first half of the editor's incremental path: find the statement under the
cursor, reparse just that one, splice it back. Linear in statements, which is
what a sorted list costs; a consumer editing a 3 million statement file wants an
interval tree of its own, built from these spans.

---

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