The module backend: Rupa.IR in, the AST of a module out.
Same IR as Rupa.Closure, so a feature is written once and both backends get it. What
changes is what the work costs. A closure reads its constants out of an environment and
reaches the next node through an indirect call. Generated code has the constants as
literals, calls the next node directly, and tests a scalar field's type and bounds inline —
so the common case, a field that is present and valid, allocates nothing at all and the
{:ok, value} wrapper never gets built.
Three things follow from generating at runtime rather than at build time:
- a compiled
patternregex is embedded as a literal, because the OTP that compiled it is the OTP about to run it — the version skew that would make that unsafe in a release cannot happen here - recursion needs no table: a ref is a direct call to the function generated for that definition, so the one lookup the closure backend does goes away
- the module is not part of your release.
Rupa.compile/2says what that means.
The generated module is a handle, not an API. Everything reaches it through Rupa.decode/3,
and ctx here is just the mode — :halt or :collect.
Summary
Types
Which of the three walks generated a function.
Functions
What a compiler diagnostic about generated code is talking about, in words.
Builds the module AST for a staged program, and the index of which walk wrote what.
Types
Functions
What a compiler diagnostic about generated code is talking about, in words.
Which name a message is about is not worth a grammar: the generated names are d0, e7,
jref_node and nothing an ordinary sentence would contain, so the first identifier the index
knows is the one being discussed. A message naming none of them is described in general
rather than guessed at.
iex> {:ok, program} = Rupa.Stage.run(%{name: Rupa.T.string()})
iex> {_ast, index} = Rupa.Codegen.module(MyApp.Codecs.Named, program, %{})
iex> Rupa.Codegen.attribute("this clause of defp e1/2 is never used", index)
"the encoder it generated"
iex> Rupa.Codegen.attribute("something about nothing in particular", %{})
"the codec it generated"
@spec module(module(), Rupa.IR.program(), term()) :: {Macro.t(), %{required(String.t()) => direction()}}
Builds the module AST for a staged program, and the index of which walk wrote what.
The schema is embedded so the module can say what it was built from, and hashed so a repeat
compile under the same name is a lookup rather than a recompile. The index is how
Rupa.compile/2 turns a compiler diagnostic about d17/2 back into a sentence: it names
every function this module generated, so a message can be told from an ordinary word.
Rupa.compile/2 with as: is the way in; this only builds the AST, and compiling it is the
caller's job:
schema = %{name: Rupa.T.string()}
{:ok, program} = Rupa.Stage.run(schema)
{ast, index} = Rupa.Codegen.module(MyApp.Codecs.User, program, schema)
index["d0"]
#=> :decode