Appearance
6. Block Syntax
6.1 Catalogue
The following block kinds are defined by core MdFlow v1.0:
| Kind | Source example | AST node |
|---|---|---|
| Paragraph | consecutive non-blank lines | Paragraph |
| Heading | # Title through ###### Title | Heading |
| Thematic break | ---, ***, ___ on a line | ThematicBreak |
| Fenced code block | ``` ... ``` or ~~~ ... ~~~ | CodeBlock |
| Blockquote | lines beginning with > | BlockQuote |
| Ordered list | 1. item, 1) item | List (ordered: true) |
| Unordered list | - item, * item, + item | List (ordered: false) |
| Table | pipe-delimited rows with alignment row | Table |
| Custom block | whitelisted md-* tag at block level | CustomBlock |
Block recognition MUST be attempted in priority order (§9.3). No block kind outside this catalogue is recognized by core; extensions MAY add kinds.
6.2 Paragraph
Priority. 300 (lowest specific block).
A paragraph is a maximal run of consecutive non-blank lines that does not match any higher-priority block. Trailing whitespace of each line is preserved for inline processing only; leading whitespace is per-line controlled by the surrounding container (e.g., list continuation).
A paragraph terminates at:
- a blank line,
- the opening line of any higher-priority block,
- end-of-input.
Paragraph contents are parsed as inlines (§7). Cross-reference: CommonMark §4.8.
6.2.1 Streaming behavior
A paragraph token is pending until a terminator is seen. In Class S mode, DOM for a paragraph MAY be emitted incrementally as inline content becomes determinable.
6.3 ATX heading
Priority. 100.
An ATX heading is a line beginning with 1–6 U+0023 (#) followed by U+0020 or U+0009 or end-of-line, followed by optional content, optionally ending with a trailing run of U+0023 preceded by whitespace.
Grammar sketch:
heading = ('#'{1,6}) (space+ content trailing-hashes?)? line-end
trailing-hashes = space+ '#'+The heading's level is the count of opening #. Content is parsed as inlines. A Heading node is produced.
Seven or more # is NOT a heading; the line becomes a paragraph.
Cross-reference: CommonMark §4.2. Setext headings are not supported; see Appendix B.
6.4 Thematic break
Priority. 100.
A thematic break is a line consisting of 3 or more U+002D (-), U+002A (*), or U+005F (_) characters, optionally interspersed with U+0020, after optional indent of 0–3 spaces. Produces ThematicBreak.
Cross-reference: CommonMark §4.1.
6.5 Fenced code block
Priority. 100.
A fenced code block opens with a line consisting of 3 or more U+0060 (`) or U+007E (~), optionally followed by an info string (any characters up to end-of-line, trimmed of surrounding whitespace). It closes with a line consisting of at least as many of the same fence character, optionally followed by whitespace.
Within the block, content is preserved verbatim up to but not including the closing fence line. CodeBlock.lang is the first whitespace-delimited token of the info string (may be empty). CodeBlock.content is the verbatim inner content with line endings normalized to LF.
6.5.1 Streaming behavior
A fenced code block token is pending from the opening fence until either the closing fence is seen or flush() is called. On flush() with no closing fence, the block is finalized with content as-is; no synthetic closing fence is inserted.
Cross-reference: CommonMark §4.5.
6.6 Blockquote
Priority. 100.
A blockquote is introduced by a line beginning with U+003E (>), optionally preceded by 0–3 spaces of indent, optionally followed by U+0020. The line's remainder (after > and optional single space) is the quoted content.
A blockquote MAY contain nested blocks, including nested blockquotes. Termination: a blank line not inside a nested paragraph, or a higher- priority block opener.
Produces BlockQuote whose children are recursively parsed.
Cross-reference: CommonMark §5.1.
6.7 List
Priority. 100.
A list is a sequence of one or more list items sharing a marker style:
- Unordered markers:
-,*,+. - Ordered markers: 1–9 decimal digits followed by
.or).
A list is tight if no blank lines separate its items, else loose. In a loose list, each item's block content is wrapped in Paragraph; in a tight list, the single paragraph per item is unwrapped to inline children.
Ordered list start is the first item's integer; subsequent item numbers are not validated (producer convention only).
Lazy continuation (continuation lines lacking the list-item indentation) is supported; continuation is parsed as part of the item's first paragraph.
Cross-reference: CommonMark §5.2, §5.3.
6.8 Table
Priority. 100.
A table is a three-part block:
- A header row: one line of pipe-delimited cells.
- An alignment row: one line of pipe-delimited cells where each cell matches
:? -{3,} :?(left colon = left-align, right colon = right-align, both = center, neither = default). - Zero or more body rows: pipe-delimited cells. Rows with fewer cells than the header are padded with empty cells; rows with more cells truncate extras.
Cell contents are parsed as inlines. Produces Table with align array length equal to header cell count, header row, and rows array.
6.8.1 Streaming behavior
A table token is pending as long as the next non-blank line could be a body row. Rows are sub-tokens; a complete row is finalized once its line ending is consumed.
Cross-reference: GitHub Flavored Markdown §4.10. Not in CommonMark core.
6.9 Custom block
Priority. 50.
A custom block is a <md-*> element at block-level (starting at column 0 after optional indent of 0–3 spaces). The element's tag MUST be in the block-context whitelist (§8.2).
Parsing rules for custom blocks are specified in §8.
6.10 Block recognition ordering
When multiple block patterns could match the current line, the implementation MUST attempt plugins in descending priority order (§9.3). The first successful match wins. Priorities for core blocks are listed above.
6.11 Inline-constraint inheritance
Each block kind declares which inline kinds are admissible in its content via allowedChildren. For example, Heading excludes LineBreak.hard (hard breaks are paragraph-only). The full matrix is specified per block in the implementation's type definitions and MUST be honored by the inline parser.