astero.emit¶
The document tree, the renderer, and bracketing from a precedence table.
astero.emit ¶
Emission: documents, and parenthesization derived from declared precedence.
Every compiler in the corpus builds target text by concatenating strings, and every one of them hand-rolls the question of when a subexpression needs brackets. That question has a known answer given a precedence table, so the table is declared and the brackets are derived.
Two pieces.
A document is a tree, not a string: text, concatenation, nesting, and a line break that a group may or may not take. Layout becomes the renderer's job, and a span rides on a document, so a source map is a projection of the emitted document rather than a structure threaded alongside the emitter.
A precedence table gives each production a binding power and an associativity. A child is bracketed when its power is lower than the position it sits in, which is the whole rule. Nothing is written per production.
Doc
dataclass
¶
A layout-independent rendering, with an optional source span.
Line
dataclass
¶
Bases: Doc
A hard break, followed by the indentation in force where it appears.
In Python indentation is semantic, so a break in a statement body is a correctness question rather than a cosmetic one. A soft break that a group may flatten to fit a width is a separate concept and is not needed yet.
Level
dataclass
¶
A binding power and how it associates.
render ¶
render(doc: Doc, indent: int = 0) -> str
Text for a document. A Line breaks and re-indents to its Nest depth.
spans ¶
spans(
doc: Doc, offset: int = 0, indent: int = 0
) -> Iterator[tuple[int, int, tuple[int, int]]]
(start, end, span) for every document carrying one.
A source map is this list, and start and end index render(doc).
indent is not decoration. A Line renders as a break plus the
indentation in force, so everything after one inside a Nest is wider
than the same document rendered at column zero. Measuring the parts at
zero put every offset after the first break too far left, which a
caller would have seen as a source map that drifts down the file. The
emitter threads no source map, so nothing here noticed.
joined_by ¶
Like joined, with a document separator rather than a string.
needs_parens ¶
Whether a subexpression at inner needs brackets inside outer.
The whole parenthesization rule, in three lines, given a table. A child binding less tightly always needs them; a child binding equally tightly needs them on the side the operator does not associate towards.