Cooper.Grammar (Cooper v0.1.0)

Copy Markdown

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.

Summary

Functions

Parses and evaluates source_text against casc.aether via Cooper.Actions, returning whatever Cooper.Merge.assemble/1 (Phase 1-3's placeholder) or, later, the real merge engine produces.

Like run/2, but reads path itself and seeds :root/:file (CASC.md §5.1's "resolves relative to the current file" and correct import-cycle detection) from it automatically -- the ordinary way to load a real CASC file with imports, rather than assembling those opts by hand.

Parses, merges, but deliberately does not resolve source_text -- returns the assembled tree (Cooper.Merge.assemble/1's output, secret leaves already wrapped in Cooper.Secret but otherwise still possibly containing unresolved Cooper.Ref.*/Cooper.Merge.Layered values) and the file's own resolved variable environment (name => value, already public/private-filtered across any imports by Cooper.Loader) -- exactly what Cooper.Resolver.resolve/2 needs. Kept separate from run/2 (which only merges) rather than folding resolution into it, since resolution needs :env/:resolvers/:tags options run/2 doesn't take -- Cooper.load_string/2 is what finally wires the two together end to end.

Functions

run(source_text, opts \\ [])

@spec run(
  String.t(),
  keyword()
) :: {:ok, term()} | {:error, Ichor.Error.t() | [Ichor.Error.t()]}

Parses and evaluates source_text against casc.aether via Cooper.Actions, returning whatever Cooper.Merge.assemble/1 (Phase 1-3's placeholder) or, later, the real merge engine produces.

opts:

  • :env -- %{String.t() => String.t()}, defaults to System.get_env/0. Threaded into ctx here (not just at Cooper.Resolver's later, separate resolve step) because CASC.md §7.2's ${?NAME} -- "skip the whole statement if unset/empty" -- has to decide at parse time, in Cooper.Actions, whether the statement it guards gets evaluated at all.
  • :root -- filesystem root a bare (non-scheme://) import in source_text resolves relative to (CASC.md §5.1). Defaults to File.cwd!/0; pass it explicitly for anything import-related, since the default is only reasonable for a real invocation of the library, not a test fixture.
  • :import_schemes -- %{scheme => (rest :: String.t() -> {:ok, String.t()} | {:error, term()})}, one function per registered scheme:// import loader (CASC.md §9.3). The same map as Cooper's own :import_schemes option.
  • :file -- the entry source's own absolute path, if it has one. Seeds Cooper.Loader's import-cycle-detection set with it, so a cycle that loops all the way back to the entry file itself is caught on the first repeat, not one hop later (an import that never reaches the entry file again doesn't need this at all). run_file/2 sets this for you.

run_file(path, opts \\ [])

@spec run_file(
  String.t(),
  keyword()
) :: {:ok, term()} | {:error, Ichor.Error.t() | [Ichor.Error.t()]}

Like run/2, but reads path itself and seeds :root/:file (CASC.md §5.1's "resolves relative to the current file" and correct import-cycle detection) from it automatically -- the ordinary way to load a real CASC file with imports, rather than assembling those opts by hand.

run_tree(source_text, opts \\ [])

@spec run_tree(
  String.t(),
  keyword()
) :: {:ok, map(), map()} | {:error, Ichor.Error.t() | [Ichor.Error.t()]}

Parses, merges, but deliberately does not resolve source_text -- returns the assembled tree (Cooper.Merge.assemble/1's output, secret leaves already wrapped in Cooper.Secret but otherwise still possibly containing unresolved Cooper.Ref.*/Cooper.Merge.Layered values) and the file's own resolved variable environment (name => value, already public/private-filtered across any imports by Cooper.Loader) -- exactly what Cooper.Resolver.resolve/2 needs. Kept separate from run/2 (which only merges) rather than folding resolution into it, since resolution needs :env/:resolvers/:tags options run/2 doesn't take -- Cooper.load_string/2 is what finally wires the two together end to end.