Emitter 2a: the corpus runner, the canonical serializer and the CommonMark subset #9
Reference in New Issue
Block a user
Delete Branch "emitter-commonmark-subset"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
First real code:
adfToMarkdownemits the CommonMark subset, and the corpus stops being hand-traced assertions and becomes a test suite. 112 tests, all 17commonmark-subset/pairs emitting byte for byte, all 44 corpus JSON files gated against the library's own canonical serializer. Floors in thetestscript sonpm testand the gate are one path: 100% lines, 100% functions, 93% branches.The runner cannot go vacuously green: a new corpus directory, an orphaned fixture, an emptied directory and a non-byte-equal emission each fail a distinct test.
What the review flow changed
An architecture pass and four stability rounds ran against this branch. The architecture pass blocked on four findings, all fixed: one
commonmark-grammar.tsnow owns every recognizer both directions need — the line-claim predicate had been implemented twice and had already diverged;opensConstructwas split; the type guard no longer admits sparse arrays it claims areAdfNode[].Twelve §2 losslessness breaks were found and closed. The ones worth naming, because each emitted successfully and read back as different ADF:
emovera_andemoveraplus a literal_both spelled_a__. The escaper asked whether a literal character opened a construct, never whether it merged with the syntax emitted beside it.**/_/~~delimiters could open or close where they landed.un+strong("-real")+isticspelledun**-real**istic, which CommonMark reads as plain text — the**is followed by punctuation and preceded by a letter, so it is not left-flanking. Every mark was lost. Delimiters are now judged by the run they sit in, against cmark'sscan_delimsrules.ruleinside a bullet spelled- ---, a thematic break, destroying the list and all its siblings.\rand U+0000 passing through raw, and four character classes that had drifted from the CommonMark definitions they implement.isAdfDocumentandisJsonValuenow walk iteratively, and blocks, marks and attribute values are held to 500 levels, so the published "nothing throws" promise holds against a deep document.The error surface
ConvertErroris{ code, message, path }— the path names the node, from the document root.ConvertErrorCodeis a closed list a consumer may switch on exhaustively; adding a member is breaking, and the list is complete at0.1.0(AGENTS.md §8).Open, and recorded rather than guessed
A nested list that cannot interrupt the block above it is refused, not spelled. Whether the canonical form separates it with a blank line, always or only where tight would not survive, is a format decision (§8) waiting on the maintainer; the answer turns that refusal into an emission. Recorded against milestone 2b, along with the
separationBetweenseam it lands in.The emitter validates delimiter flanking exactly but does not model CommonMark's matching — the multiple-of-3 rule and the way a run splits across openers. No reachable violation was found by hand; the corpus property test at 2e is what settles it. Recorded there.