xo-umbrella2/xo-pyfacet/examples/flywheel_pretty.py

122 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""Render an AllocFlywheel through a PrettySink, from python.
Shows the whole loop: python owns the arena configuration, the flywheel and
the sink, and drives a c++ render between two objects it holds. Nothing here
is static or global -- everything is reclaimed by python's own refcounting
when the last reference drops.
Run (from the umbrella build):
.build/xo-python xo-pyfacet/examples/flywheel_pretty.py
"""
import xo.arena as mm
import xo.indentlog2 as il
import xo.facet as f
def configure():
"""Establish this process's subsystem contexts, bottom-up.
Importing a module registers its types and nothing else; the context is
built here, from capacities chosen by the caller -- the same choice a c++
main() makes.
Each configure() returns its context and the next one takes it, mirroring
the c++ ctor FacetAppcx(cfg, const Indentlog2Appcx &). The chain is
therefore visible as data flow: nothing reaches back for a context it was
not handed.
"""
il_cx = il.configure(il.Indentlog2Config(il.PpConfig.plain(), 64 * 1024))
return f.configure(f.FacetConfig(1024, 1024), il_cx)
def make_flywheel(facet_cx):
"""An AllocFlywheel: one primary arena plus two root sets.
make_app() is a named factory, mirroring c++ -- an AllocFlywheel is
heap-allocated and cannot be constructed any other way. It is refcounted
(it inherits Displayable -> Refcount), so python holds it through rp<>.
The appcx is passed explicitly rather than taken from the module: a
flywheel's dependence on configure() should be visible where the flywheel
is made, not implied.
"""
return f.AllocFlywheel.make_app(
facet_cx,
mm.ArenaConfig(name="flywheel-storage", size=256 * 1024),
mm.ArenaConfig(name="flywheel-strong", size=64 * 1024),
mm.ArenaConfig(name="flywheel-weak", size=64 * 1024))
def render_to_string(obj, margin=60):
"""Render obj through a fresh accumulating sink; return the text.
make2str builds a sink with no drain destination, so output() hands back
everything written so far. It is a copy -- the c++ output() returns a
string_view that is only valid until the next write.
"""
sink = il.PrettySink.make2str(il.PpConfig.scratch_plain(margin))
obj.pretty(sink)
return sink.output()
def render_to_stdout(obj, margin=60):
"""Render obj to stdout. complete() ends the record and drains it."""
sink = il.PrettySink.make2cout(il.PpConfig.scratch_plain(margin))
obj.pretty(sink)
sink.complete()
def main():
facet_cx = configure()
fw = make_flywheel(facet_cx)
print("1. render to a string")
print(" ", repr(render_to_string(fw)))
print("2. render to stdout")
print(" ", end="", flush=True)
render_to_stdout(fw)
print("3. configuration is inspectable")
cfg = il.PpConfig.scratch_plain(40)
print(" cfg =", cfg)
print(" layout margin =", cfg.layout.soft_right_margin)
print(" logbuf arena =", cfg.logbuf.logbuf_config)
print("4. configs are values -- with_* copies, it does not mutate")
wide = cfg.with_soft_right_margin(120)
print(" original =", cfg.layout.soft_right_margin)
print(" derived =", wide.layout.soft_right_margin)
print("5. the sink can be aimed at a named arena of your choosing")
named = (il.PpConfig.scratch_plain(60)
.with_logbuf_config(mm.ArenaConfig(name="from-python",
size=128 * 1024)))
sink = il.PrettySink.make2str(named)
fw.pretty(sink)
print(" arena name =", named.logbuf.logbuf_config.name)
print(" output =", repr(sink.output()))
print("6. sinks are independent, and reusable across renders")
a = il.PrettySink.make2str(il.PpConfig.scratch_plain(60))
b = il.PrettySink.make2str(il.PpConfig.scratch_plain(60))
fw.pretty(a)
fw.pretty(a) # accumulates
fw.pretty(b)
print(" a (rendered twice) =", repr(a.output()))
print(" b (rendered once) =", repr(b.output()))
print("7. lifetimes are python's")
del fw # flywheel released here; sinks are unaffected
import gc
gc.collect()
print(" flywheel released; a still holds", repr(a.output()))
if __name__ == "__main__":
main()