Skip to content

Preserve runs of compact assignment rows - #6

Merged
jserv merged 2 commits into
mainfrom
assignment-rows
Sep 21, 2026
Merged

jserv merged 2 commits into
mainfrom
assignment-rows

Conversation

@jserv

@jserv jserv commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

A comment that documents a mapping one row at a time, the ir->imm = lui immediate shape a decoder or a register table is written in, was packed into a paragraph like prose, which loses the mapping. Those rows now classify as a preformatted kind and go out verbatim. What decides a row is the left-hand side, which must be a single code-like token, so a sentence that merely carries an equals sign stays prose; the operator may be bare or compound, spaced or not, and alignment padding before it is layout rather than a disqualifier, since column-aligned tables are the common spelling. A run of two or more adjacent rows freezes whole and whatever follows is simply the next paragraph, so a table sitting directly above the sentence that explains it survives intact, while a lone count = zero when the queue drained is a sentence and still wraps.

Run membership is decided after every other check, over the lines nothing else claimed, so a run cannot reach through a code sample, a table, a fence or a verbatim block. Deciding it on how a line looks in the source instead let an indented ret = foo(a, b); pair with the sentence under it and freeze that sentence alone, which is the case the run rule exists to reflow and a common shape in C headers. Two rules this leans on needed work: art declines a mapping row, since = is in the art alphabet and a short row cleared the density threshold on the very characters that make it a row, except where the line is rule-decorated, or the packer builds a line ruled on both ends that the next pass strips; and the merge gate reads through a bookend, because strip_decorative_bookends runs between the two stages.

The first commit is independent and stands on its own against the base branch: line 0 of a /* <content> block was excluded from canonical re-emit and took raw replay, so it kept a decoration its siblings had lost and the pass never converged. That affects label and banner rows with no assignment row involved.

Verified against the pre-change binary over 900 system headers and Rust sources, about 550k lines, at column limits 40, 60, 80 and 100. Five files differ at 80 and each is a mapping table the old binary had run together: the adler32 derivation in adler2, the write defaults in brlapi.h, the size definitions in nettle/ccm.h, the permission rows in xenstore.h, and two lines of literal example code in webp/decode.h. Preserved rows match their source bytes including the alignment indent, no comment word moves in any file, and the set of files whose second pass differs is unchanged. A 4000-case fuzz over an operator-heavy vocabulary and a 1500-case fuzz mixing multibyte characters with every operator byte, both run against the base binary for comparison, found no new non-convergence and no panics. Run handling is linear, measured at 3000, 10000 and 20000 rows in one comment, and the branch costs about 0.5% more instructions on real headers. cargo test is 452 green, with cargo clippy --all-targets and cargo fmt --check clean, and commentflow --check on the changed files matches the base branch.

Left as it stands: a run of two or more overlong rows keeps sitting over the column limit, the same way a table row or an indented code sample does, because a real mapping row cannot be wrapped without destroying the mapping. That boundary is pinned by a test rather than fixed. Two pre-existing issues found during validation are untouched here: a /*- opener merge that does not converge, visible in two lz4 headers, and a quadratic prefix rescan in is_file_start_prefix that a one-line conjunct reorder would fix, measured at 5% of instructions on real headers and 53% on comment-dense input.

cubic-dev-ai[bot]

This comment was marked as resolved.

cubic-dev-ai[bot]

This comment was marked as resolved.

A label or banner row re-emits its body behind the canonical prefix
rather than replaying its raw source line, so a drifted marker and a
stripped decorative bookend land like every reflowed sibling. Line 0 of
a "/* <content>" block was excluded from that and took raw replay, which
is the source line as it arrived, from before strip_decorative_bookends
ran. The opener's line therefore kept a decoration its siblings had
already lost.

The result is a pass that never settles. Pass one emits the mixed shape,
pass two sees the bookend on a line that is no longer first, strips it,
and produces different bytes, so --check reports a diff forever. The
exclusion bought nothing: text is already the stripped body with the
opener removed, which is exactly what the raw-replay branch rebuilds by
hand for that line.
cubic-dev-ai[bot]

This comment was marked as resolved.

A comment that documents a mapping one row at a time, the shape a
decoder or a register table is written in, was packed into a paragraph
like prose, which loses the mapping. Those rows now classify as a
preformatted kind and go out verbatim.

What decides a row is the left-hand side, which must be a single
code-like token, so a sentence that merely carries an equals sign stays
prose. The operator may be bare or compound, spaced or not, and
alignment padding before it is layout rather than a disqualifier, since
column-aligned tables are the common spelling. Comparisons and arrows
are not assignments and do not open a row.

A run of two or more adjacent rows freezes whole and whatever follows is
simply the next paragraph, so a table sitting directly above the
sentence that explains it survives intact. One row is not a table: a
lone "count = zero when the queue drained" is a sentence, and wrapping
it is correct. Requiring a neighbour is also what keeps a lone overlong
sentence out of the frozen set, though a run of them may sit over the
column limit, the same way a table row or an indented code sample may,
because a mapping row cannot be wrapped without destroying the mapping.

Membership is decided after every other check, over the lines nothing
else claimed, so a run cannot reach through a code sample, a table, a
fence or a verbatim block. Deciding it on how a line looks in the source
instead let an indented "ret = foo(a, b);" pair with the sentence under
it and freeze that sentence alone, which is the case the run rule exists
to reflow.

Two rules this leans on needed work. Art declines a mapping row, because
the equals sign is a member of the art alphabet and a short row cleared
the density threshold on the very characters that make it a row; a
rule-decorated line is excepted, or the packer builds a line ruled on
both ends that the next pass strips. The merge gate reads through a
bookend, because strip_decorative_bookends runs between the two stages,
and answering on the raw line let a prose comment merge into a table
that then froze around it.

Two questions about a LineKind were remote matches! lists a new variant
could fall out of, answering "reflow it as prose", which is the damage
the enum exists to prevent. is_preformatted is an exhaustive method now,
so the next variant is a compile error until it is answered.

Verified against the base branch over 900 headers and Rust sources, at
four column limits. Five files differ, each a mapping table the old
binary had run together, and no comment word moves. The set of files
whose second pass differs is unchanged.
@jserv
jserv merged commit c11e22c into main Sep 21, 2026
23 checks passed
@jserv
jserv deleted the assignment-rows branch September 21, 2026 03:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant