Formatter.Render
Render a Logical Printing Tree to its final formatted source string.
renderRoot walks the RootBox's top-level children, renders each one
through the Box renderer (Formatter.Render.MakeRenderBox), and joins the
results with newlines. Box is the only backend: every construct goes through
Formatter.Render.Box's Line/Box IR, and one the renderer does not handle
is an Err that fails the format rather than falling back to anything.
This module is also the doorway through the stage barrier. It is the only
one on the render side that names LPNode: it calls Formatter.RenderTree.lower
on the way in, and everything under Formatter/Render/ sees the position-free
RenderNode instead. That is what makes "no source rows are read at render
time" a compile error rather than a convention -- see Formatter.RenderTree.
Serialise the Box each top-level child renders to as a JSON array (one
entry per declaration), for the --box debug flag — the Box analogue of the
--lpt LPT dump. An Err from any child (an unhandled construct) aborts.
Render every top-level child of the RootBox and join them with newlines.
The same render, one string per top-level child instead of one string for the file.
renderRoot is these joined with newlines. They are needed apart by
Formatter.Audit.DecisionTrace, which has to say which declaration's output
moved between two formats: a decision that flipped inside a declaration whose
output is unchanged caused nothing, and reporting it beside the ones that did
would bury them.
renderRootChildren for a caller that has already lowered the tree.
Formatter.Audit.DecisionTrace is the one such caller, and it needs both halves
at once: this render, and the render tree itself, which it walks to read the
decisions back. Going through renderRootChildren made it lower the whole
tree twice per formatting pass -- four times per --decisions run, which formats
twice. Lowering is O(n) and allocates a node per node, so that is not free, and
the second copy answers a question the first already answered.
renderRoot and lptToBoxJson keep taking the LPNode: they lower once and
have no use for the result afterwards, which is the normal case.