Step 1 of 5 · Reading · ~3 min
Read
Production Concerns
Putting It All Together
Every piece you have written so far handled one construct in isolation. This is where they become a program: one function that takes a document and returns HTML, with each handler you built now living as a branch of a single dispatcher.
What the finished converter contains
| Phase | Handlers |
|---|---|
| Block, in dispatch order | fenced code, ATX heading, setext lookahead, thematic break, blockquote, list, paragraph |
| Inline, in application order | backslash escape, code span, image, link, strong, em, strikethrough, HTML escape |
| Render | join block HTML with newlines |
Notice that the block table is ordered and the inline table is ordered. Neither order is arbitrary — each one exists because an earlier rule would otherwise steal a line, or a later rule would otherwise walk into HTML that an earlier rule produced.
The branch on the right is the point of the whole architecture: code content
leaves the block phase and goes straight to the renderer, never passing through
inline parsing. That is what keeps *not italic* literal inside a fence.
Why does dispatch order decide correctness?
Take the two rules that both claim a row of dashes:
Same input, same rules, different order — a heading became a paragraph plus a horizontal rule. Fenced code has to be checked before everything, because a fence can contain lines that look like any other block; the setext lookahead has to beat the thematic break; and the paragraph handler always goes last, because a paragraph is defined as "whatever nothing else claimed".
What your parser still does not do
Being honest about scope is part of engineering:
- Link reference definitions need a pre-pass over the whole document.
- Tight versus loose lists need the list collected before any item renders.
- Lazy continuation lets a blockquote paragraph run past unprefixed lines.
- Nested containers — a list inside a quote inside a list — need recursion.
- Raw HTML blocks, backslash escapes and entity references have their own rules.
CommonMark ships 652 conformance examples in version 0.31.2 precisely because
this list is long. cmark is the reference implementation in C; markdown-it,
goldmark, pulldown-cmark and markdig are the widely used ports.
Use one of those in production. Write your own to understand parsing — which is what you just did — and then reach for the fuzz-tested library when the input comes from strangers.
Your exercise
Build the end-to-end converter.
The mistake the grader catches is skipping setext headings. Two hidden tests
feed Setext H1 over a row of = and Title over a row of -, and expect
<h1> and <h2>. Handle only ATX and those two come back as a paragraph plus
an <hr /> — the same failure the snippet above demonstrates. The lookahead
belongs before your thematic-break branch.
The second trap is inline parsing inside fences. A hidden test puts *em*,
**strong** and ~~strike~~ in a paragraph and a plain word in a code block;
if the code path shares the inline function, the block content gets rewritten
and the diff lands inside <pre>.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…