Aether.Reader (Ichor v0.2.0)

Copy Markdown View Source

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.

Summary

Types

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.

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).

A {line, column} source position.

Types

cst()

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

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

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

A {line, column} source position.

Functions

read(source, file \\ nil)

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

Reads source into an Aether.Reader.Grammar CST.