Pin the code-span and strikethrough grammars, break mark runs at a carry
CI / gate (push) Successful in 4s
CI / gate (push) Successful in 4s
This commit is contained in:
+37
-28
@@ -1,11 +1,11 @@
|
||||
# The markdown flavour
|
||||
|
||||
The grammar of the extended markdown `adfToMarkdown` emits and `markdownToAdf` parses. Plain
|
||||
CommonMark is a subset with three carve-outs: literal text that matches directive syntax below,
|
||||
opens a pipe table or runs `~~` is claimed by the flavour (escape the `:`, `|` or `~` to keep
|
||||
it literal) — and one gap: a CommonMark image fits only as its own title-less paragraph —
|
||||
mid-text and titled images are named errors. The emitted form is contract (AGENTS.md §8).
|
||||
Per-node syntaxes build on this grammar in the sections below.
|
||||
CommonMark is a subset with three carve-outs: literal text that matches directive syntax below or
|
||||
opens a pipe table is claimed by the flavour, and a matched `~~` pair spells `strike` (escape the
|
||||
`:`, `|` or `~` to keep it literal) — and one gap: a CommonMark image fits only as its own
|
||||
title-less paragraph — mid-text and titled images are named errors. The emitted form is contract
|
||||
(AGENTS.md §8). Per-node syntaxes build on this grammar in the sections below.
|
||||
|
||||
## Canonical form
|
||||
|
||||
@@ -13,7 +13,9 @@ Per-node syntaxes build on this grammar in the sections below.
|
||||
normalizes to it through the round-trip.
|
||||
|
||||
- Emphasis `_em_`, strong `**strong**`, strike `~~strike~~`; `*` replaces `_` only where `_`
|
||||
cannot parse (intra-word).
|
||||
cannot parse (intra-word). Strike is GFM strikethrough narrowed to exactly two tildes — a
|
||||
single tilde or a run of three or more is literal text — and block structure resolves before
|
||||
inline, so a `~~~` line opens a CommonMark tilde code fence.
|
||||
- Bullet lists `- `; ordered lists incrementing `1.` `2.` `3.`, the first number taken from the
|
||||
node's `order` attribute. Continuation lines align with the first character after the marker
|
||||
(two spaces for `- `, three for `1. `); blank lines inside an item are empty lines. Lists are
|
||||
@@ -22,6 +24,9 @@ normalizes to it through the round-trip.
|
||||
- ATX headings (`#` … `######`); setext input normalizes to ATX.
|
||||
- Code fences ``` with the node's language as info string, the fence lengthened past any backtick
|
||||
run in the content; indented-code input normalizes to fences.
|
||||
- Code spans: a backtick string one longer than the longest backtick run in the text, padded with
|
||||
one space on each side where the text begins or ends with a backtick, or begins and ends with a
|
||||
space without being all spaces.
|
||||
- Thematic break `---`.
|
||||
- Hard break: backslash at end of line (survives editors that trim trailing spaces). Where
|
||||
CommonMark admits no spelling — the end of a block, inside a heading — it is `:hardBreak{}`.
|
||||
@@ -37,18 +42,19 @@ normalizes to it through the round-trip.
|
||||
## Directives
|
||||
|
||||
One grammar for everything CommonMark lacks. A directive name is `[a-z][A-Za-z0-9]*` — the ADF
|
||||
node and mark names. Recognition is syntactic and name-set-independent: anything matching the
|
||||
forms below parses as a directive regardless of whether the name is known, and an unknown name is
|
||||
an error result naming it — so output an old emitter escaped stays escaped, and erroring input
|
||||
gaining meaning later is MINOR, never a reparse (§8). The name `adf` is reserved for the opaque
|
||||
carry, as both directive name and fence info string.
|
||||
node and mark names the sections below spell as directives. Recognition is syntactic and
|
||||
name-set-independent: anything matching the forms below parses as a directive regardless of
|
||||
whether the name is known, and an unknown name is an error result naming it — so output an old
|
||||
emitter escaped stays escaped, and erroring input gaining meaning later is MINOR, never a reparse
|
||||
(§8). The name `adf` is reserved for the opaque carry, as both directive name and fence info
|
||||
string.
|
||||
|
||||
**Inline**: `:name[content]{attrs}`, on one line — an inline directive never spans lines.
|
||||
`[content]` is inline markdown; brackets inside balance as in CommonMark link text, `\]` for a
|
||||
literal bracket. Each section below says whether content and attrs are required. `:` opens a
|
||||
directive only when the name is followed immediately by `[` or `{`, and `{attrs}` must follow
|
||||
`]` (or the name) with no gap — anything else (`10:30`, `:smile:`, a stray `{…}` in text) is
|
||||
literal text.
|
||||
literal bracket. A space at either edge of `[content]` is part of it. Each section below says
|
||||
whether content and attrs are required. `:` opens a directive only when the name is followed
|
||||
immediately by `[` or `{`, and `{attrs}` must follow `]` (or the name) with no gap — anything
|
||||
else (`10:30`, `:smile:`, a stray `{…}` in text) is literal text.
|
||||
|
||||
**Container block**:
|
||||
|
||||
@@ -306,8 +312,9 @@ attributes. The other three share: `extensionKey` (string), `extensionType` (str
|
||||
|
||||
Attributes and the carry fallback read as in the block sections, the carry in its inline form. Of
|
||||
the six nodes below, those with a `text` attribute spell it in the content slot as plain text:
|
||||
content parsing to anything but one unmarked text node is a named error, absent content is the
|
||||
absent attribute, and a `text` key in `{attrs}` is a named error. The rest take no content.
|
||||
`[]` is the empty string, absent content is the absent attribute, non-empty content parsing to
|
||||
anything but one unmarked text node is a named error, and so is a `text` key in `{attrs}`. The
|
||||
rest take no content.
|
||||
|
||||
- `date` — `localId` (string), `timestamp` (string, epoch milliseconds).
|
||||
- `emoji` — `id` (string), `localId` (string), `shortName` (string, `:name:`), `text` (string).
|
||||
@@ -329,10 +336,11 @@ Shipped :emoji[🎉]{shortName=":tada:"} on :date{timestamp=1756080000000}.
|
||||
|
||||
**Whitespace CommonMark cannot hold.** A newline inside a text node, and a space or tab where
|
||||
CommonMark strips or refuses one — a block's inline content edges, either side of a line break,
|
||||
a mark spelling's inner edges, a pipe cell's edges — is spelled `:text{text="…"}`, the reserved
|
||||
key carrying the node's text, escaped by the attribute grammar and never literal: pipe cells trim
|
||||
and pad. The emitter wraps the whitespace run alone and leaves the rest plain text;
|
||||
`markdownToAdf` merges adjacent text nodes carrying identical marks (AGENTS.md §2).
|
||||
an em, strong or strike spelling's inner edges, a pipe cell's edges — is spelled
|
||||
`:text{text="…"}`, the reserved key carrying the node's text, escaped by the attribute grammar
|
||||
and never literal: pipe cells trim and pad. The emitter wraps the whitespace run alone and leaves
|
||||
the rest plain text; `markdownToAdf` merges adjacent text nodes carrying identical marks
|
||||
(AGENTS.md §2).
|
||||
|
||||
```
|
||||
:text{text=" "}Two leading spaces held, and one text node split:text{text="\n"}over two lines.
|
||||
@@ -342,8 +350,8 @@ and pad. The emitter wraps the whitespace run alone and leaves the rest plain te
|
||||
|
||||
An inline node's marks and a text node's ride the spelling wrapped around them, never the block
|
||||
sections' reserved `marks` key. `code`, `em`, `link`, `strike` and `strong` keep their markdown
|
||||
spellings, which hold no attributes beyond `link`'s `href` and `title`. The other four are inline
|
||||
directives, content required:
|
||||
spellings, which hold no attributes beyond `link`'s `href` and `title`, and are not directive
|
||||
names: `:em[x]` is a named error. The other four are inline directives, content required:
|
||||
|
||||
- `border` — `color` (string, `#rrggbb` or `#rrggbbaa`), `size` (number, 1–3).
|
||||
- `subsup` — `type` (`sub` `sup`).
|
||||
@@ -354,12 +362,13 @@ A spelling adds its mark to every inline node it wraps, and nesting is the marks
|
||||
outermost first: `_:underline[x]_` gives marks `[em, underline]`, `:underline[_x_]` the reverse.
|
||||
`adfToMarkdown` nests in the order the array holds rather than sorting it — §2's equality
|
||||
restores the array, not a set — and opens each spelling once over the longest run of adjacent
|
||||
inline nodes carrying that mark at that depth.
|
||||
inline nodes carrying that mark at that depth. A run breaks at every node the emitter carries, so
|
||||
no emitted carry sits inside a mark spelling.
|
||||
|
||||
A text node whose marks no nesting spells — a mark type not listed here, an attribute no
|
||||
spelling holds, or an order putting a code span outside another mark — rides the inline carry
|
||||
whole. An opaque carry inside a mark spelling is a named error: the carry restores its node
|
||||
exactly, marks included (AGENTS.md §3).
|
||||
An inline node whose marks no nesting spells — a mark type not listed here, an attribute no
|
||||
spelling holds, an order putting a code span outside another mark, or `code` on text holding a
|
||||
newline — rides the inline carry whole. An opaque carry inside a mark spelling is a named error
|
||||
in input: the carry restores its node exactly, marks included (AGENTS.md §3).
|
||||
|
||||
```
|
||||
:textColor[**Overdue**]{color="#ae2e24"}, H:subsup[2]{type=sub}O, :underline[signed].
|
||||
|
||||
@@ -58,5 +58,5 @@ syntax, the rest rides the opaque carry (§3) until it does too.
|
||||
| Marks | `border` `code` `em` `link` `strike` `strong` `subsup` `textColor` `underline` |
|
||||
|
||||
Plain markdown covers `blockquote`, `bulletList`, `codeBlock`, `heading`, `orderedList`,
|
||||
`paragraph`, `rule`, `listItem`, `hardBreak`, `text`, and the `code`, `em`, `link`, `strike` and
|
||||
`strong` marks. Everything else is what the flavour is for.
|
||||
`paragraph`, `rule`, `listItem`, `hardBreak`, `text`, and the `code`, `em`, `link` and `strong`
|
||||
marks; `strike` is the flavour's `~~` carve-out. Everything else is what the flavour is for.
|
||||
|
||||
Reference in New Issue
Block a user