# `Aether.Reader`
[🔗](https://github.com/joetjen/ichor/blob/v0.3.0/lib/aether/reader.ex#L1)

The pure-syntax half of Aether's front-end: turns `.aether` source (via
`Aether.Lexer`) into a concrete syntax tree that's a faithful record of
what was written -- every pragma, token, and rule definition, in file
order -- with no desugaring and no cross-definition semantics applied.

This is the same "Reader" role `Grammar.Native`'s generated `parse/1`
(or any of the ABNF/BNF/EBNF/PEG importers' own `parse/1`) plays for a
grammar's *input*: a bare recognizer producing a raw tree, nothing
more. `Aether.Eval` is the matching "Eval" half -- it's what turns this
CST into actual `Grammar.IR` (not to be confused with
`Ichor.Actions.evaluate/5`, which evaluates *parsed input* through a
grammar's own actions; this evaluates *grammar source* into IR).

Owns: the token/rule reference split (`ALL_CAPS` names a token,
`snake-case` names a rule), the full expression grammar (choice >
sequence > term > postfix > primary), `~` markers, quantifier syntax,
`@indent`/`@samecol`, and every check that only depends on local
syntax (a token body referencing a rule, an inline character class
outside a token, a name or pragma declared twice). Everything that
depends on the *whole* grammar -- predefined-token override/use
tracking, case-insensitivity resolution, char-class/regex desugaring,
inline-literal promotion, `@skip` splicing -- is `Aether.Eval`'s job,
not this module's.

# `cst`

```elixir
@type cst() ::
  {:seq, [{cst(), suppress_skip :: boolean()}]}
  | {:choice, [cst()]}
  | {:capture, atom(), cst()}
  | {:and_pred, cst()}
  | {:not_pred, cst()}
  | {:indent, cst(), :indent | :samecol}
  | {:quant, cst(),
     :star
     | :plus
     | :opt
     | {:bound, non_neg_integer(), non_neg_integer() | :infinity}, pos()}
  | {:literal, String.t(), Aether.Token.case_flag(), pos()}
  | {:char_class, boolean(), [Aether.Token.class_item()], pos()}
  | {:regex, String.t(), pos()}
  | {:dot, pos()}
  | {:ref, atom(), pos()}
  | {:native, String.t(), String.t(), [atom()], hint(), pos()}
```

A raw expression node, mirroring exactly what was written -- no
desugaring. Composite nodes (`:seq`/`:choice`/`:capture`/`:and_pred`/
`:not_pred`/`:indent`) carry no position of their own (matching
`Grammar.IR`'s own convention of falling back to a descendant's span);
leaf and quantifier nodes do.

# `hint`

```elixir
@type hint() :: %{nullable: boolean() | nil, leading: [atom()] | nil}
```

Author-supplied facts for a `@native(...)` node, standing in for what
`Grammar.Analysis` would otherwise compute structurally -- `nil` means
"not given, `Aether.Eval` applies its default." `:leading`, when given,
is itself a list of declared-dependency rule names (never arbitrary
rules outside that list -- `@native(...)`'s own argument list is the
only thing this node can reference).

# `pos`

```elixir
@type pos() :: {pos_integer(), pos_integer()}
```

A `{line, column}` source position.

# `read`

```elixir
@spec read(String.t(), String.t() | nil) ::
  {:ok, Aether.Reader.Grammar.t()} | {:error, Ichor.Error.t()}
```

Reads `source` into an `Aether.Reader.Grammar` CST.

---

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