# Cooper v0.1.0 - Table of Contents

> Parses and loads CASC config files -- a hierarchical, extensible config language with imports, variables, interpolation, loops, and consumer-registered resolvers/tags -- into native Elixir terms, secrets redacted by default.

## Pages

- [Cooper](readme.md)
- [Cooper Tutorial](tutorial-1.md)
- [Examples](examples.md)
- [Cheatsheet](cheatsheet.md)
- [Changelog](changelog.md)
- [Contributing to Cooper](contribution.md)
- [LICENSE](license.md)

- CASC
  - [CASC Tutorial](tutorial-2.md)
  - [CASC Configuration Format (CASC)](casc.md)
  - [CASC Examples](casc_examples.md)
  - [CASC Cheatsheet](casc_cheatsheet.md)

## Modules

- Public API
  - [Cooper](Cooper.md): Parses and loads CASC config files (see `CASC.md`) into native Elixir
terms: maps (string keys), atoms, real tuples (never lists --
CASC.md §6.11 is explicit that this is a correctness requirement, not
an implementation detail), and `Cooper.Secret`-wrapped values for any
`*key`-prefixed secret.
  - [Cooper.IPv4](Cooper.IPv4.md): A parsed IPv4 address, with an optional CIDR prefix (CASC.md §6.7 --
`127.0.0.1` or `127.0.0.1/32`). `new/2` is the only way to build one,
and it's the same validation `Cooper.Actions` runs on a bare literal
at parse time -- an out-of-range octet or CIDR prefix is a load-time
`Ichor.Error`, never a crash or a silently-accepted garbage value.

  - [Cooper.IPv6](Cooper.IPv6.md): A parsed IPv6 address, with an optional CIDR prefix (CASC.md §6.7 --
`::1` or `::1/128`). `new/2` is the only way to build one, and it's
the same validation `Cooper.Actions` runs on a bare literal at parse
time -- a malformed address or an out-of-range CIDR prefix is a
load-time `Ichor.Error`, never a crash or a silently-accepted garbage
value.
  - [Cooper.Secret](Cooper.Secret.md): Wraps a secret leaf value (CASC.md §4.3's `*key`-prefixed keys) in the
final result `Cooper.load_file/2`/`Cooper.load_string/2` return, so it
never leaks by accident: `inspect/1` (via `IO.inspect/2`, pattern-match
failures, logging, ...) and `to_string/1` both redact regardless of the
wrapped value. Reach the real value deliberately via `reveal/1` (or the
`:value` field directly) -- the wrapping is only ever a display-time
concern, never a barrier to the developer's own deliberate use of the
real value; there would be no point config-loading a secret otherwise.

- Unresolved AST (seen only if you call a pipeline stage directly)
  - [Cooper.Block](Cooper.Block.md): Internal marker wrapping a `{ ... }` block's already-flattened entries,
distinguishing "this statement's right-hand side was a block" from "was
a scalar value" (`Cooper.Actions`' own `handle_rule/3` clause for
`:kv_statement` branches on it). A struct rather than a tagged tuple
deliberately --
CASC tuples (§6.11) are real Elixir tuples built from user data, and a
plain `{:block_ops, list}` tag could theoretically collide with one; a
struct never can.

  - [Cooper.Interp.Text](Cooper.Interp.Text.md): A double-quoted string's content, once it's been found to contain at
least one interpolation reference -- an ordered list of literal string
runs and `Cooper.Ref.*` nodes. `Cooper.Resolver` resolves each segment
and joins the results into the final string. A double-quoted string with *no*
references at all is represented as a plain Elixir string instead
(`Cooper.Actions` never wraps pure literal text in this struct) --
keeps the common case cheap and keeps existing scalar-string
assumptions elsewhere intact.

  - [Cooper.Merge.Layered](Cooper.Merge.Layered.md): A path whose value is a lazy base (a `for`-loop's `from <template>`,
CASC.md §5.5, resolved by `Cooper.Resolver` against the *final* merged
tree) with literal statements layered on top as overrides --
`Cooper.Merge` builds this whenever a later op writes underneath a
path currently holding a `Cooper.Ref.Config`. `overrides` mirrors the
assembled tree's own shape (a plain nested map, itself possibly
containing more `Layered` values at deeper paths); `Cooper.Resolver`
resolves `base`, deep-merges `overrides` on top, and that's this
path's final value.

  - [Cooper.Op](Cooper.Op.md): One flattened leaf assignment: every assignment or block statement --
regardless of surface form, dotted path, or nested block -- resolves
recursively into a list of these before merge ever runs, so the merge
engine (`Cooper.Merge`) only ever deep-merges paths, never
re-discovers which parts of two differently-shaped literals mean the
same key.

  - [Cooper.Ref.Config](Cooper.Ref.Config.md): An unresolved `%{path}` config reference (CASC.md §7.3). Lazy --
resolved by `Cooper.Resolver` only after `Cooper.Merge` has run,
against the fully merged final tree; cycle detection happens there
too.

  - [Cooper.Ref.Env](Cooper.Ref.Env.md): An unresolved `${NAME}` environment reference (CASC.md §7.2). Eager,
same timing as `Cooper.Ref.Var` -- resolved by `Cooper.Resolver`,
always to a string unless wrapped in a tagged value (`!int(...)` etc).

  - [Cooper.Ref.Resolver](Cooper.Ref.Resolver.md): An unresolved `!{resolver:payload}` extensible-resolver call (CASC.md
§7.4). `payload` is carried verbatim (never parsed further) -- giving
it meaning is entirely the consumer-registered resolver's job, wired
up via `Cooper.Resolver` and the `:resolvers` option (see `Cooper`).
An unregistered resolver name is a hard error at resolve time, never
silently passed through.

  - [Cooper.Ref.Tagged](Cooper.Ref.Tagged.md): An unresolved `!Name(arg)` tagged value (CASC.md §7.5). The grammar
only recognizes "a tag plus one parenthesized argument" -- `arg` is
already a fully evaluated ordinary value (recursively -- it may
itself be a nested ref); giving `name` meaning (built-in `int`/
`float`/`bool`/`duration`/`bytes`, or a consumer-registered tag) is
`Cooper.Resolver`'s job, via the `:tags` option (see `Cooper`).

  - [Cooper.Ref.Var](Cooper.Ref.Var.md): An unresolved `@{name}` variable reference (CASC.md §7.1). Eager --
`Cooper.Resolver` resolves these against the per-file variable
environment, independent of the final merged tree.

  - [Cooper.VarDecl](Cooper.VarDecl.md): A parsed `@name = value` / `@*name = value` variable declaration
(CASC.md §5.2). Resolution against a variable environment, and
public/private visibility across imports, is `Cooper.Loader`/
`Cooper.Resolver` territory -- this struct only carries what the
grammar can determine locally.

- Pipeline
  - [Cooper.Actions](Cooper.Actions.md): `Ichor.Actions` for `casc.aether`. Turns the raw parse into
  - [Cooper.Grammar](Cooper.Grammar.md): Orchestrates `casc.aether` parsing against `Cooper.NativeGrammar`
(`Grammar.Native`, compiled ahead of time by `mix ichor.gen` -- see
its own moduledoc). `Grammar.VM` -- the interpreted bytecode backend
that `ichor` proper still ships -- is no longer reachable from
anything under `lib/` at all: `ichor` is an `only: [:dev, :test],
runtime: false` dependency now (the `ichor`/`ichor_runtime` split),
so nothing a `mix release` build ships is allowed to call into it.
`Cooper.Test.VMParity` (`test/support/`, compiled only under
`MIX_ENV=test`) keeps the old `Grammar.VM` path alive purely for
`bench/native_vs_vm.exs` and `test/cooper/backend_parity_test.exs`.

  - [Cooper.InterpActions](Cooper.InterpActions.md): `Ichor.Actions` for `casc_interp.aether` -- parses a double-quoted
string's already escape-processed content into an ordered list of
literal string runs and `Cooper.Ref.*` nodes.
  - [Cooper.InterpGrammar](Cooper.InterpGrammar.md): Loads `casc_interp.aether`. Used only for a double-quoted string's
content (CASC.md §7) -- see that grammar file's own moduledoc-style
comment for why bare interpolated values in the main grammar don't go
through here.
  - [Cooper.Loader](Cooper.Loader.md): Resolves and loads a CASC.md §5.1 `import "..."` statement, called
directly from `Cooper.Actions`' own `handle_rule/3` clause for
`:import_statement` (not a separate, later pass) -- an import has to
be resolved inline, at the point it's parsed, so that variables it
brings in are visible
to whatever *follows* it in the importing file, `for` loops included,
exactly the way an ordinary `@name = ...` declaration already is.
  - [Cooper.Loop](Cooper.Loop.md): Expands a parsed `for` statement (CASC.md §5.5) into the ops it
generates.
  - [Cooper.Merge](Cooper.Merge.md): Folds the flattened op/var-decl/clear-scope entries `Cooper.Actions`
(and `Cooper.Loader`, for imports) produce into (almost) the final
tree -- CASC.md §8/§5.7. "Almost": leaf values may still be unresolved
`Cooper.Ref.*` nodes or `Cooper.Merge.Layered` values (a lazy
`for`-loop base with overrides on top) -- `Cooper.Resolver` walks
this next.
  - [Cooper.NativeGrammar](Cooper.NativeGrammar.md): `casc.aether`, compiled to direct Elixir function calls -- ~2x
throughput per Ichor's own docs, now that the grammar has stopped
changing weekly.
  - [Cooper.NativeInterpGrammar](Cooper.NativeInterpGrammar.md): `casc_interp.aether`, compiled the same way `Cooper.NativeGrammar` is
(see its own moduledoc for the full ahead-of-time-generation
rationale). This sub-grammar is invoked dynamically -- once per
double-quoted string literal encountered during parsing, not once
overall -- but that's an argument *for* native codegen here, not
against it: under `Grammar.VM` (`Cooper.Test.VMParity.run_interp_vm/1`,
`test/support` only), every one of those calls re-parses
`casc_interp.aether`'s own source text from scratch; here it's
compiled exactly once, ahead of Cooper's own build, regardless of how
many string literals a given file has.
  - [Cooper.Resolver](Cooper.Resolver.md): Resolves everything `Cooper.Merge.assemble/1` left unresolved --
`Cooper.Ref.Var` (`@{}`, eager), `Cooper.Ref.Env` (`${}`, eager),
`Cooper.Ref.Config` (`%{}`, lazy against the *final* tree),
`Cooper.Ref.Resolver` (`!{resolver:payload}`, dispatch), `Cooper.Ref.Tagged`
(`!Name(arg)`, dispatch), and `Cooper.Merge.Layered` (a `for` loop's
lazy `from` base with overrides on top, see `Cooper.Loop`).

- Support
  - [Cooper.CIDR](Cooper.CIDR.md): Shared bitwise CIDR math for `Cooper.IPv4`/`Cooper.IPv6` -- both
address families need the identical operations (prefix -> mask,
address <-> integer, network/broadcast/host-range math), differing
only in bit width (32 vs 128) and part width (8-bit octets vs 16-bit
hextets), so this holds one implementation instead of two
near-copies. Not meant to be called directly -- use `Cooper.IPv4`/
`Cooper.IPv6`'s own functions, which already know their own bit width.

  - [Cooper.Display](Cooper.Display.md): Stringifies a resolved value for embedding into a string-interpolation
segment -- shared by `Cooper.Loop` (a loop-bound value substituted into
an interpolated destination path or body string) and `Cooper.Resolver`
(an ordinary `@{}`/`${}`/`%{}`/`!{}`/`!Name()` result embedded the same
way, CASC.md §7).

  - [Cooper.Literals](Cooper.Literals.md): Duration (CASC.md §6.8) and byte-size (§6.9) text parsing, shared
between `Cooper.Actions` (the bare `500ms`/`512MiB` literal forms) and
`Cooper.Resolver` (the equivalent `!duration("5m")`/`!bytes("512MiB")`
tagged-value forms, §7.5 -- "reachable ... for computed/interpolated
values"). Same rules, same errors, one implementation.

  - [Cooper.Native.ResolverRef](Cooper.Native.ResolverRef.md): `@native("Cooper.Native.ResolverRef", "scan")` implementation for
`RESOLVER_REF_RAW` (`casc.aether`/`casc_interp.aether`): scans a
`!{name:payload}` resolver reference (CASC.md §7.4) with genuinely
unbounded brace nesting in `payload`, tracking depth as it goes.
  - [Cooper.RefCommon](Cooper.RefCommon.md): Shared across `Cooper.Actions` and `Cooper.InterpActions`: the
`:ref_suffix` (`:default`/`:+alt`/`:?"msg"`, CASC.md §7.2's "one rule,
not three near-identical ones", reused across `@{}`/`${}`/`%{}` in
both grammars) and `:env_bracket` (`${NAME[i]}` vs `${NAME[]}`)
handling, plus the small eval helpers every custom `handle_rule` in
either module needs.

