Cooper.Loop (Cooper v0.1.0)

Copy Markdown

Expands a parsed for statement (CASC.md §5.5) into the ops it generates.

This sits at a genuine architectural seam: loop expansion runs before merge (Cooper.Merge) and general reference resolution (Cooper.Resolver), yet it structurally needs to know each element binding's actual list to know how many iterations to generate. The resolution here: a binding's iterable is resolved eagerly, but only against the current file's own local variable environment built so far (ordinary @name = [...] declarations threaded through Cooper.Actions' ctx as they're parsed) -- never against something that can only exist after merge. A %{...} config reference as an iterable is refused outright for exactly that reason (not a currently-missing feature, a structural impossibility at this stage).

Once bindings are resolved, iteration proceeds like an ordinary bottom-up eval: for each iteration, a local overlay (@idx/@domain/ @port/...) is substituted into the loop body's ops and the destination path -- and only those loop-bound names; any other reference (@{outer_var}, %{...}, !{...}, !Name(...)) is left untouched for Cooper.Resolver's later resolution pass, matching CASC.md's own "loop-bound variables ... scoped to the body" framing.

from <template> (CASC.md §5.5) stays a lazy Cooper.Ref.Config base at each generated destination, exactly like Cooper.Merge handles any other lazy config-ref base -- loop expansion never reads the template's value itself.

Summary

Functions

bindings are as parsed by Cooper.Actions' own handle_rule/3 clause for :binding; from_template is nil or a key-path segment list; dest_segments is key_path's already-evaluated segment list (a segment may be a plain string or an unresolved Cooper.Interp.Text, per an interpolated destination like "domain-@{idx}"); body is the loop's %Cooper.Block{}; outer_vars is the enclosing file's variable environment so far (name => {value, public?}, matching Cooper.Actions' own ctx.vars shape -- public? is unused here, only Cooper.Loader cares about it).

Types

binding()

@type binding() :: {:index, String.t()} | {:element, String.t(), term()}

Functions

expand(bindings, from_template, dest_segments, dest_secret?, block, outer_vars)

@spec expand(
  [binding()],
  [String.t()] | nil,
  [term()],
  boolean(),
  Cooper.Block.t(),
  map()
) ::
  {:ok, list()} | {:error, Ichor.Error.t()}

bindings are as parsed by Cooper.Actions' own handle_rule/3 clause for :binding; from_template is nil or a key-path segment list; dest_segments is key_path's already-evaluated segment list (a segment may be a plain string or an unresolved Cooper.Interp.Text, per an interpolated destination like "domain-@{idx}"); body is the loop's %Cooper.Block{}; outer_vars is the enclosing file's variable environment so far (name => {value, public?}, matching Cooper.Actions' own ctx.vars shape -- public? is unused here, only Cooper.Loader cares about it).