| title | Printing Stage |
|---|---|
| audience | developers, maintainers, contributors |
| prerequisites | contributor architecture guide, formed source representations |
| related | ../architecture.md, index.md, codegen.md, pipeline.md, parsers.md |
| status | maintained |
| publication | reviewed |
prik/printers/ is the representation-to-text boundary. C and Fortran
printers serialize backend nodes; the semantic .pyi printer serializes
semantic IR. Printers own formatting, escaping, indentation, declaration
order, and safe line wrapping. They do not invoke generators, choose filenames,
complete policy, or compile output.
prik/printers/
├── __init__.py
├── c.py
├── fortran.py
└── pyi.py
formed C or Fortran node tree -> matching source printer -> native text
SemanticModule graph -> PyiPrinter -> editable .pyi
| Module | Main entrypoints and contents | Change it when |
|---|---|---|
prik/printers/__init__.py |
Re-exports CSourcePrinter, FortranSourcePrinter, PyiPrinter, and emit_module(). |
The supported printer import surface changes. |
prik/printers/c.py |
CSourcePrinter serializes C translation units, headers, declarations, functions, tables, and statements. |
C syntax layout, escaping, or formatting changes. |
prik/printers/fortran.py |
FortranSourcePrinter serializes bridge modules, interfaces, declarations, procedures, and free-form wrapped statements. |
Fortran source layout or line-wrapping changes. |
prik/printers/pyi.py |
PyiPrinter, emit_module(), and _PyiEmissionContext serialize semantic modules and scope imports, aliases, namespaces, and defaults for one emission. |
Editable contract spelling or emission-context behavior changes. |
The fact that code generation calls a printer at the end of wrapper rendering
does not make printing part of codegen ownership. pipeline/wrapper.py
coordinates both distinct stages.
CSourcePrinter.doprint() is the public entrypoint. It freezes a supplied
StageRecord, dispatches it through the C node visitor, and returns text. A
CModule is rendered in compiler order: macro definitions, includes,
declarations, then functions. A CHeader adds its guard around includes and
prototypes.
The remaining visitors add C punctuation, indentation, signatures, and string escaping to node-selected values. CPython method tables and module-property support are serialized from their C nodes; no semantic model or wrapper plan is consulted.
FortranSourcePrinter.doprint() likewise freezes a supplied node and renders
it through the Fortran visitor. A module is emitted in Fortran specification
and body order: uses, type definitions, interfaces, declarations, then
procedures and standalone procedures.
After rendering, the printer wraps overlong free-form lines at safe whitespace or comma boundaries. It can continue string literals without changing their value, never splits comments or doubled-quote escapes, and rejects an unsplittable line that remains above the 132-column compiler-safe limit.
PyiPrinter.emit() creates a fresh _PyiEmissionContext for every call. The
context records contract imports, aliases, public-name reservations, source
array defaults, and nested namespaces without mutating a reusable printer or
the semantic IR.
For a module, the printer first renders public classes, prototypes, variables,
functions, and overload sets into body sections. As visitors use contract
symbols, the shared context records imports; final import sections are then
placed before the body. Visitors preserve semantic native identity,
projections, storage, imports, and contract annotations, but do not complete
wrapper policy. emit_module() is the normal one-module convenience entrypoint
and still creates a fresh context.
c.py constructs one small C module containing a wrap_ping function and
passes that already formed node tree to CSourcePrinter.
python3 prik/printers/c.pyRendered C binding source:
#include <Python.h>
static PyObject * wrap_ping(PyObject * self) {
Py_INCREF(Py_None);
return Py_None;
}
The include, C signature, indentation, and semicolons are printer work. The example contains no semantic model or wrapper plan for the printer to inspect.
fortran.py constructs one bridge module with explicit iso_c_binding and
native-module uses, then prints its one bridge function.
python3 prik/printers/fortran.pyRendered Fortran bridge source:
module bind_c_printer_demo_wrapper
use iso_c_binding, only: c_double
use printer_demo, only: native_double_value => DOUBLE_VALUE
implicit none
contains
function bind_c_double_value(value) result(result) bind(c, name="DOUBLE_VALUE")
real(c_double), value :: value
real(c_double) :: result
result = native_double_value(value)
end function bind_c_double_value
end module bind_c_printer_demo_wrapper
The result preserves the declared use order and native alias, then applies Fortran declaration, procedure, and indentation syntax to the supplied nodes.
pyi.py constructs one semantic double_value function with Float64 types
and a native DOUBLE_VALUE identity, then emits one contract module.
python3 prik/printers/pyi.pySemantic module: printer_demo
from prik.contracts import Float64, bind
@bind("DOUBLE_VALUE")
def double_value(
value: Float64
) -> Float64: ...
The native examples prove that punctuation and layout are added to already
formed nodes. The .pyi import and @bind line show that required contract
imports and native identity are derived from semantic IR without attaching
wrapper policy.
| Evidence | What it establishes |
|---|---|
| Native source printers | C and Fortran serialization, rejection of wrapper plans, line wrapping, literal preservation, and unsplittable-line diagnostics. |
Semantic .pyi conversion smoke |
Emitted contract fixtures can be parsed and converted through the normal semantic-.pyi route. |
.pyi imports and packages |
Isolated emission state, imports, aliases, packages, name collisions, and opaque dependencies. |
- Change formatting or serialization in the matching printer.
- If information is missing from a native node, add it in generation or the plan rather than consulting semantic IR from the printer.
- Change filenames or multi-source artifact order in the pipeline.
- Native source printers accept backend nodes, not semantic models.
- The
.pyiprinter accepts semantic IR, not wrapper plans. - Each
.pyiemission owns fresh context, so one failure cannot leak imports or reserved names into the next emission.
Native printers report unsupported node types and, for Fortran, a line that
cannot be safely wrapped. The .pyi printer reports unsupported semantic
models or invalid contract-emission facts. Printers delegate missing node facts
to codegen/ and missing semantic facts to earlier stages; they delegate file
names and writing to pipeline/. Start with the first invalid node or semantic
record, not the rendered text that exposes it.