Source-to-source transforms
Everything so far has instrumented code in order to run it. But the same
machinery can hand you the rewritten source without executing anything —
which is what a linter, a formatter, a source-map generator, or a compile-to-plain
Python tool needs. pyccolo.transform() desugars, and
pyccolo.untransform() resugars.
Desugar and resugar
Under a tracer that defines syntax augmentation, pyc.transform returns the
instrumented / desugared source as a string, and pyc.untransform reverses an
augmentation — turning valid Python back into the augmented surface syntax. Using
the shipped optional-chaining example, a?.b?.c desugars to plain a.b.c
and back:
import pyccolo as pyc
from pyccolo.examples.optional_chaining import ScriptOptionalChainer as OC
with OC:
assert pyc.transform("y = a?.b?.c") == "y = a.b.c"
tree = pyc.parse("y = a?.b?.c", instrument=False)
assert pyc.untransform(tree) == "y = a?.b?.c"
pyc.parse(code, instrument=False) gives you the augmented syntax parsed into
an AST without adding instrumentation, which is the right input to
untransform.
Analysis-only (pure) transforms
Some rewrites keep bookkeeping state as a side effect of running — fine when you
are about to execute the result, wrong when you only want to inspect it. Pass
pure=True to mark a transform as analysis-only, so cooperating rewrites know
not to disturb execution-relevant state:
with OC:
assert pyc.transform("y = a?.b", pure=True) == "y = a.b"
A rewrite can consult pyccolo.is_pure_transform() to branch on this. It is
thread- and async-safe, backed by a ContextVar, so a pure
transform on one thread won’t perturb a live tracer on another.
Source maps
For tooling that must map positions between the original and transformed source —
underlining an error at the right column, say — both transform and
untransform accept a positions=[(line, col), ...] argument and return the
corresponding positions in the transformed (or untransformed) coordinate space
alongside the source:
with OC:
transformed, mapped = pyc.transform(
"y = a?.b?.c", positions=[(1, 4)]
)
# `mapped` holds the (line, col) of that point in `transformed`
The precise signatures — including what mapped contains — are in
Source-to-source API.