Skip to content

Inline spans

Emphasis flanking, mentions and tags, smart typography, inline literals and escaping.

Generated from resources/examples/edge-cases.md, resources/examples/core.md and resources/examples/extensions.md - edit the cases there, not here. Each case links the conformance fixture it produces.

Mention ignores email addresses

1 conformance fixture

@ starts a mention only at a word boundary, so an email address is left untouched.

carve
Write me@example.com or ping @markus.
html
<p>Write me@example.com or ping <span class="mention"><strong>@markus</strong></span>.</p>

Tag requires a word boundary

2 conformance fixtures

# starts a tag only at a word boundary; foo#bar is literal text.

carve
A #tag here, but not in foo#bar.
html
<p>A <span class="tag"><strong>#tag</strong></span> here, but not in foo#bar.</p>

A tag name may be all digits, so #123 is a tag (not literal) — Issue #123 tags the number. Only a leading word boundary is required, not a leading letter.

carve
Issue #123 and #v2 here.
html
<p>Issue <span class="tag"><strong>#123</strong></span> and <span class="tag"><strong>#v2</strong></span> here.</p>

Smart typography escapes and code

1 conformance fixture

A backslash keeps the literal sequence; code spans and blocks are never transformed.

carve
Escaped \-> and \... stay; code `a -- b ...` stays.
html
<p>Escaped -&gt; and ... stay; code <code>a -- b ...</code> stays.</p>

Empty delimiters

1 conformance fixture

A delimiter pair with no content is literal text, not emphasis.

carve
** and // and ^^
html
<p>** and // and ^^</p>

Escape coverage

2 conformance fixtures

A backslash escapes any ASCII punctuation character to its literal form. This pins the full ascii_punctuation matrix (&, :, ;, ? included); <, >, & are then HTML-escaped in the output.

carve
\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~ done
html
<p>!"#$%&amp;'()*+,-./:;&lt;=&gt;?@[\]^_`{|}~ done</p>

A backslash before a non-ASCII character or a letter is literal; \\ is a single backslash.

carve
\a and \« and a\\b
html
<p>\a and \« and a\b</p>

Emphasis edge cases

4 conformance fixtures

Two emphasis spans of the same kind sit side by side without merging.

carve
*a* and *b*
html
<p><strong>a</strong> and <strong>b</strong></p>

A code span inside emphasis is preserved.

carve
*a `x` b*
html
<p><strong>a <code>x</code> b</strong></p>

Different-kind delimiters sit adjacent without interfering.

carve
~old~ =new=
html
<p><s>old</s> <mark>new</mark></p>

Trailing punctuation after a closer is literal.

carve
*a, b*!
html
<p><strong>a, b</strong>!</p>

Doubled emphasis delimiters

1 conformance fixture

A bare single-character emphasis delimiter immediately adjacent to the same delimiter does not open a span, so a doubled delimiter is literal text. This "no nesting of same type" rule is uniform across all seven single-character delimiters: **, ~~, ^^, ==, and ,, stay literal exactly like // and __.

carve
**a** ~~b~~ ^^c^^
html
<p>**a** ~~b~~ ^^c^^</p>

Two-char delimiter runs

1 conformance fixture

Every bare delimiter is single-char. A doubled (or longer) run of any delimiter is literal by the same-delimiter-adjacency rule, so ==x== and ~~y~~ are doubled = / ~ and render literal, while the single-char =z= and ~w~ mark.

carve
==x== ~~y~~ =z= ~w~
html
<p>==x== ~~y~~ <mark>z</mark> <s>w</s></p>

Mention and tag name boundaries

1 conformance fixture

A mention or tag name runs over letters, digits, _, -, and interior dots (a dot followed by another name character, as in @john.doe or #release-1.0). A dot at the end of the run is sentence punctuation, not part of the name; other punctuation ends the name and stays literal (an apostrophe becomes a typographic quote).

carve
Ping @john-doe, @john_doe and @john.doe about #release-1.0 today.

Reach @john. That is @john's idea, @john!
html
<p>Ping <span class="mention"><strong>@john-doe</strong></span>, <span class="mention"><strong>@john_doe</strong></span> and <span class="mention"><strong>@john.doe</strong></span> about <span class="tag"><strong>#release-1.0</strong></span> today.</p>
<p>Reach <span class="mention"><strong>@john</strong></span>. That is <span class="mention"><strong>@john</strong></span>’s idea, <span class="mention"><strong>@john</strong></span>!</p>
1 conformance fixture

A *[ at an emphasis-opening position is a bold span whose content begins with a link - only a line-start *[ followed by term]: is an abbreviation definition.

carve
See *[the docs](url) for more* info.
html
<p>See <strong><a href="url">the docs</a> for more</strong> info.</p>

Literal less-than in prose

1 conformance fixture

A < that is neither an autolink, a crossref, nor a smart-typography arrow stays literal text (HTML-escaped on output).

carve
Check if (x < 5) holds, and 3<4 too.
html
<p>Check if (x &lt; 5) holds, and 3&lt;4 too.</p>

Editorial markup takes a trailing attribute

1 conformance fixture

An addition {+...+} or deletion {-...-} is an ordinary inline node, so a trailing {...} attribute block attaches to its <ins> / <del>, exactly like a span, code span, link, or emphasis (PART 9 §22 / §15). The markers are single-character: the doubled form {++a++} is not special — the outer + are the delimiters and +a+ is literal content, so it yields <ins>+a+</ins> (as the example below shows).

carve
{++a++}{.a}
html
<p><ins class="a">+a+</ins></p>

Emphasis opener slash-adjacency

4 conformance fixtures

A / immediately before a bare delimiter suppresses an italic / or underline _ opener there: / never opens after / (same-delimiter adjacency) and _ never opens after / (the extra cross-delimiter guard, path protection). So the underscore in a_/_a_ stays literal.

carve
a_/_a_
html
<p>a_/_a_</p>

An underline opener directly after a slash is literal on its own, too.

carve
a/_y_
html
<p>a/_y_</p>

A path-like /a/ opens italic; the following _b_ does not open, because its opening _ sits immediately after the closing /.

carve
/a/_b_
html
<p><em>a</em>_b_</p>

The guard is specific to / and _: the other delimiters *, ~, = DO open after a /, so a preceding slash does not suppress them.

carve
a/~y~ a/=y=
html
<p>a/<s>y</s> a/<mark>y</mark></p>

Bold-italic delimiter needs content

4 conformance fixtures

A bold-italic run /*...*/ collapses to <strong><em>...</em></strong> only when it wraps content. With nothing between the delimiters, the inner ** is literal and only the outer /.../ italic applies -- so /**/ is an emphasized **, not empty bold-italic.

carve
/**/
html
<p><em>**</em></p>

A single space is content for the outer italic but not for the bold pair, so the * * stays literal inside one <em>.

carve
/* */
html
<p><em>* *</em></p>

With real content, the full bold-italic collapse still applies.

carve
/*x*/
html
<p><strong><em>x</em></strong></p>

The bold-italic pair /*...*/ has no word-boundary condition on its outer /: the combined two-character opener wins over the bare /-then-* parse even when a word character sits directly before /* or directly after the closing */, so it opens and closes intraword.

carve
a/*y*/b
html
<p>a<strong><em>y</em></strong>b</p>

Emphasis span closes before a following delimiter

1 conformance fixture

A completed emphasis span closes at its valid closer regardless of what follows, per the §9 close-first rule: a valid closer closes the nearest matching open entry. So _z_ closes into <u>z</u> even when more bare delimiters come right after it. The trailing /y/ stays literal, because a / opener is suppressed immediately after the closing _ (the slash-adjacency guard above).

carve
_z_/y/
html
<p><u>z</u>/y/</p>

Unclaimed openers stay literal

1 conformance fixture

Two forms that were proposed and then not adopted have no meaning in Carve, and both are pinned here so no engine can quietly start claiming them.

[>content] was the proposed sidenote form. It was dismissed, not deferred: a margin note is footnote content positioned by CSS, so it needs no syntax. The [> opener is unclaimed and the whole thing is literal text.

{:name:} was a proposed braced symbol form for intraword use. The brace is not part of any construct: it merely satisfies the symbol boundary guard, and the braces themselves stay literal. The name inside is a normal symbol, so with no symbols map configured it falls back to the literal :name: and the line renders exactly as written. See dismissed syntax.

carve
[>foo]

{:tada:}
html
<p>[&gt;foo]</p>
<p>{:tada:}</p>

Inline literal

3 conformance fixtures

A ! prefix on a verbatim span is an inline literal (PART 9 §27): the content is captured verbatim like a code span, but it renders as ordinary prose — no <code> wrapper — and is HTML-escaped and emitted by every renderer. It mirrors the $-math prefix, and exists so notation that collides with the bare emphasis delimiters (phonemic transcription /kaet/, glob patterns, paths) can be written without escaping each character.

carve
The word cat is !`/kaet/` in IPA.
html
<p>The word cat is /kaet/ in IPA.</p>

A trailing {…} is the ordinary inline attribute block, so an attributed literal renders a <span> carrying it. The content is HTML-escaped, and no inline construct inside it is parsed.

carve
!`/kaet/`{.ipa} and !`a<b>` and !`*not bold*`
html
<p><span class="ipa">/kaet/</span> and a&lt;b&gt; and *not bold*</p>

! still opens an image before [, and stays literal text everywhere else. A literal ! immediately before a backtick span is written \!.

carve
\!`x` is a bang before code.
html
<p>!<code>x</code> is a bang before code.</p>

All-space verbatim content

3 conformance fixtures

The single-space strip on a verbatim span (PART 3 code_span) drops one leading and one trailing space, but not when the content consists entirely of space characters - those spans keep every space. Without this guard a formatter round-trip loses the content, since a span stripped to empty has no writable source spelling.

carve
A single space ` ` and two spaces `  ` are preserved.
html
<p>A single space <code> </code> and two spaces <code>  </code> are preserved.</p>

Ordinary content still strips one space from each side, so the guard is narrow.

carve
But ` a ` strips one space from each side.
html
<p>But <code>a</code> strips one space from each side.</p>

The sigil-prefixed verbatim forms - the inline literal (section 27) and math (section 18) - share the same strip rule, so they keep all-space content too.

carve
Literal !`  ` and math $`  ` keep their spaces.
html
<p>Literal    and math <span class="math inline">\(  \)</span> keep their spaces.</p>

Adjacent slash and underscore emphasis nest

1 conformance fixture

/ and _ open immediately after each other when the preceding delimiter is a true opener, so adjacent pairs nest (they only stay literal as path protection when the preceding delimiter is a closer, e.g. /a/_b_).

carve
/_x_/ and _/x/_
html
<p><em><u>x</u></em> and <u><em>x</em></u></p>

Quote flanking after an escaped character

1 conformance fixture

A backslash-escaped character still flanks as the character it is, so a quote that follows it decides direction from the literal. \{ is an opening bracket and opens the quote; \< and \* are not, so the quote closes - the same decision the unescaped character produces. Escaping changes what the character is, not what it flanks like.

Worth pinning because the escape is consumed before the quote is resolved, so an implementation that loses the literal at that point silently flips the direction.

carve
\{"quoted"\} and \<"q"\> and \*'q'\*

A \{ before a quote still opens it: \{"open"

Unescaped for contrast: {"open"}
html
<p>{“quoted”} and &lt;”q”&gt; and *’q’*</p>
<p>A { before a quote still opens it: {“open”</p>
<p>Unescaped for contrast: {“open”}</p>

A combined bold-italic span may cross a line

1 conformance fixture

/*…*/ is one construct (bold_italic), and like any inline span it folds across the lines of its paragraph. The nesting is the same either way: strong outside emphasis. Nothing pinned the multi-line form, and the oracle inverted it there - pairing the two delimiters separately gives emphasis outside strong.

carve
/*multi
line*/
html
<p><strong><em>multi
line</em></strong></p>

A tag inside a literal brace run is still a tag

1 conformance fixture

A trailing {…} on a heading is not an attribute list - Carve is djot-strict, so the braces are inline content (§15 A7). Their CONTENTS are inline content too, which is where #word is a tag (§19). Both rules apply at once: the braces stay as typed and the tag inside them renders.

carve
# H {#id .cls}
html
<section id="H-id-cls">
  <h1>H {<span class="tag"><strong>#id</strong></span> .cls}</h1>
</section>

A math span's base class keeps the class slot in place

2 conformance fixtures

math inline is a mandatory base class, so it is prepended INSIDE the class slot and the slot stays at the first-appearance position of a class in the author's order (PART 10 §1). An id written before any class is still serialized first.

markup-carve/carve#1168 pinned this for the generic ext-NAME fallback. The math span carries a base class the same way and was missed, because no case put an id before a class on it (markup-carve/carve#1164).

carve
$`E=mc^2`{#i .c k=v}
html
<p><span id="i" class="math inline c" k="v">\(E=mc^2\)</span></p>

With no authored class there is no slot to keep, so the base class leads.

carve
$`E=mc^2`{#i k=v}
html
<p><span class="math inline" id="i" k="v">\(E=mc^2\)</span></p>

A marker glued to a name opens nothing

3 conformance fixtures

The boundary rule has a right half. A # or @ written directly against the end of a tag or mention name is preceded by a WORD character - the last character of that name - so it cannot open a second one, exactly as a#b cannot open one (PART 9 §7).

The word production absorbs a glued marker between two word characters, which is that rule stated structurally, but a run that STARTS with a marker never enters word at all - so this shape was unreachable until markup-carve/carve-js#1029 made {#i#j} literal text rather than an attribute block, and the executable spec read it as two tags (markup-carve/carve#1156).

carve
#i#j
html
<p><span class="tag"><strong>#i</strong></span>#j</p>

A mention takes the same guard.

carve
@a@b
html
<p><span class="mention"><strong>@a</strong></span>@b</p>

Separated by a space, both open normally - that is the control the guard must not break.

carve
#a #b
html
<p><span class="tag"><strong>#a</strong></span> <span class="tag"><strong>#b</strong></span></p>

An angle bracket is escaped only where it opens markup

1 conformance fixture

PART 11 §8a M1e. On the Markdown target a < is escaped with a backslash when the next character is an ASCII letter, /, ! or ? - the four things that can open raw HTML - and left alone otherwise. A > takes nothing: mid-line it is inert, and at the start of a line it is a block quote marker M1 already covers.

Escaping the < alone is sufficient, because a tag that cannot open cannot be closed. Every engine used to rewrite both brackets to entities, unconditionally and with no clause behind it (markup-carve/carve#1148); an entity is not an escape, since it replaces the character rather than protecting it.

carve
a < b and a <b> c and x > y
html
<p>a &lt; b and a &lt;b&gt; c and x &gt; y</p>

An escaped hash keeps its escape at a container's content position

2 conformance fixtures

PART 11 §8b M2b is decided on the EMITTED LINE, so a container prefix is passed over before the position is read. A hash at the start of a block quote's or a list item's content opens an ATX heading exactly as one at column 0 does, and its escape is kept there. All three engines dropped it, and a round trip through the Markdown target turned the author's text into a heading (markup-carve/carve#1330).

The narrowing itself does not move, which is the half a correction here is most likely to lose. A hash the prefix does not put at the content position is still emitted bare, and so is one that stands there but opens no heading, since M2b's reading is CommonMark's and a run closed by a letter is not a heading. This pair carries both directions.

carve
> \# heading
>
> C\# is a language

- \# heading
- \#tag rest
html
<blockquote>
  <p># heading</p>
  <p>C# is a language</p>
</blockquote>
<ul>
  <li># heading</li>
  <li>#tag rest</li>
</ul>

Nesting needs no rule of its own: the prefix is whatever the writer emitted, > > included. Neither does lazy continuation, which is a parser concept - this writer re-prefixes every line of a container, so the last line below is emitted with its > and read at the content position like any other.

carve
> > \# deep

> a
\# heading
html
<blockquote>
  <blockquote><p># deep</p></blockquote>
</blockquote>
<blockquote><p>a
# heading</p></blockquote>

Emphasis

8 conformance fixtures

The word-boundary rule applies to every bare delimiter (/ * _ ~ = — all single-char). No bare delimiter emphasizes intraword: foo*bar*baz, foo~bar~baz, snake_case, a/b/c, x = 5, key=value all stay literal. For deliberate intraword emphasis, use the forced {X … X} family (below). Superscript and subscript have no bare delimiter at all — they exist only in the braced forms {^…^} / {,…,} (see below). For any bare delimiter:

  • an opener is recognized only if it is not followed by whitespace and is preceded by the start of the line/block, whitespace, or a punctuation character (not by an alphanumeric, _, or the same delimiter) — so a/b/c, foo_bar_baz, snake_case, and //a/ stay literal, while (/x/) and a./b/ open after punctuation;
  • a closer is recognized only if it is not preceded by whitespace and not followed by an alphanumeric character — so x /a/b y stays literal.

Highlight is the single-char = delimiter; the uniform word boundary keeps x = 5, key=value, a=b literal. Every bare delimiter is single-char, so a doubled delimiter (==x==) is literal by the same-delimiter-adjacency rule, just like **x** or //x//. This is stricter than Djot, whose _/* rule is purely whitespace-flanking. ^ and , are not bare delimiters — a comma or caret in prose is always literal text (1,2,3, a,b,c, x, y, z, 2 ^ 3), and superscript/subscript are written with the braced forms only. The boundary rule still allows /usr/local/<em>usr/local</em>: the opening / sits at line start and the inner same-type / characters are literal content (Carve does not nest same-type emphasis). The normative rule lives in resources/grammar.ebnf PART 9 §9 and §22.

carve
foo*bar*baz and a/b/c stay literal.
html
<p>foo*bar*baz and a/b/c stay literal.</p>

An opener without a matching closer is left as a literal character.

carve
/foo bar
html
<p>/foo bar</p>

Inner slashes inside a /…/ span are literal content — a path-like span still parses as emphasis.

carve
/usr/local/
html
<p><em>usr/local</em></p>

Whitespace immediately after an opener (or before a closer) blocks emphasis — the delimiter renders literally.

carve
/ not emphasis /
html
<p>/ not emphasis /</p>

An opener may follow punctuation, not only whitespace or the line start.

carve
(/x/) and a./b/
html
<p>(<em>x</em>) and a.<em>b</em></p>

A closer is rejected when followed by an alphanumeric character, so an interrupted path stays literal.

carve
x /a/b y
html
<p>x /a/b y</p>

No bare delimiter produces intraword emphasis — * behaves like / and _.

carve
foo_bar_baz and snake_case stay literal
html
<p>foo_bar_baz and snake_case stay literal</p>

An emphasis opener (any bare delimiter) immediately preceded by the same delimiter or by a literal _ is not valid (this does not affect different-delimiter combinations like /*bold italic*/, and a _ that itself opens an underline span does not block a following opener).

carve
//a/ and snake_/case/
html
<p>//a/ and snake_/case/</p>

Inline extensions

5 conformance fixtures

The extension name is an identifier, which permits a leading _, so _ is a valid extension name (grammar extension_name).

carve
:_[x]
html
<p><span class="ext-_">x</span></p>

An identifier must start with a letter or _, so a digit-first name is not a valid extension. :1[x] stays literal text; :a1[x] (a digit after the first letter) is a valid extension and renders as a generic span.

carve
:1[x]
:a1[x]
html
<p>:1[x]
<span class="ext-a1">x</span></p>

The extension_content runs up to the first ] (extension_content = {character - ']'}); a nested ] therefore closes the extension early and the remainder is literal text.

carve
:foo[a [b] c]
html
<p><span class="ext-foo">a [b</span> c]</p>

The four names core does not reserve - samp, var, cite, dfn - are the SemanticSpan extension's, so a core processor leaves them as ordinary attributes.

carve
[x]{samp} [y]{dfn="a term"}
html
<p><span samp="">x</span> <span dfn="a term">y</span></p>

An explicit abbr value takes precedence over automatic abbreviation definitions, avoiding invalid nested <abbr> markup.

carve
*[HTML]: Hyper Text Markup Language

[HTML]{abbr="Custom"}
html
<p><abbr title="Custom">HTML</abbr></p>

Smart typography dashes and quotes

8 conformance fixtures

A quote opens (left/opening quote) when it follows start-of-content, whitespace (incl. NBSP), or one of the opening/operator characters ( [ { = : - /; otherwise it closes. So a quote right after =, :, -, /, or an opening paren still opens the first quote (grammar smart_quote).

carve
a="b"
:"q"
-"q"
/"q"
("q")
html
<p>a=“b”
:“q”
-“q”
/“q”
(“q”)</p>

When a quote does not follow one of those opening contexts it closes instead — so a quote right after a closing bracket (} ) ]), after sentence punctuation (. ,), or mid-word always becomes a right/closing quote. An empty "" opens both marks (the second " follows a ", which is not an opening context, yet there is nothing to its right to close against, so it too renders as an opening quote).

carve
}"q"
)"q"
]"q"
."q"
,"q"
a"b
""
html
<p>}”q”
)”q”
]”q”
.”q”
,”q”
a”b
””</p>

The same opening set applies to the single quote '. After (, [, =, :, -, or / a single quote opens (); the matching ' then closes ().

carve
('q')
['q']
='q'
:'q'
-'q'
/'q'
html
<p>(‘q’)
[‘q’]
=‘q’
:‘q’
-‘q’
/‘q’</p>

A single quote after { opens too — shown on its own line because a trailing {…} would otherwise be read as an attribute block.

carve
{'q'}
html
<p>{‘q’}</p>

A single quote before a digit is an apostrophe (decade elision), so a digit pair becomes apostrophes on both sides; a quote before a letter in an open context opens.

carve
the '70s and '24' and 'word'
html
<p>the ’70s and ’24’ and ‘word’</p>

A run of four or more hyphens is allocated into em/en dashes (all em if divisible by 3, all en if by 2, otherwise max em-dashes with an en remainder) — matching djot.

carve
a---- b----- c------
html
<p>a–– b—– c——</p>

Longer runs follow the same allocation with no leftover hyphen: seven is one em plus two en, eight is four en, ten is five en, eleven is three em plus one en, and thirteen is three em plus two en.

carve
a------- b-------- c---------- d----------- e-------------
html
<p>a—–– b–––– c––––– d———– e———––</p>

The open/close decision reads the character before the quote. A bare emphasis delimiter is not that character - the quote sees the start of the emphasis CONTENT - and nothing at all before a quote opens it. A quote directly after another one follows whichever half that one resolved to, so a nested pair opens while an empty pair stays closed.

carve
*'q'*

"hello"

"'nested'"

a*'q'*
html
<p><strong>‘q’</strong></p>
<p>“hello”</p>
<p>“‘nested’”</p>
<p>a*’q’*</p>

Inline code

1 conformance fixture

An unclosed run is opaque: an emphasis delimiter or link tail after it is verbatim content, so the surrounding construct never closes.

carve
*a ` b*
html
<p>*a <code> b*</code></p>

Non-breaking space

1 conformance fixture

The non-breaking space U+00A0 serializes as &nbsp; in text and code-span output. (In a heading id it is kept as the raw byte instead - ids are not entity-encoded; see Heading IDs.)

carve
`a b`
html
<p><code>a&nbsp;b</code></p>

Superscript and subscript

1 conformance fixture

A ^ or , outside the braced forms is always literal text — even where a bare delimiter's word-boundary rule would have matched:

carve
typo ,oops, happens and 10^6^ things and x ^2^ y
html
<p>typo ,oops, happens and 10^6^ things and x ^2^ y</p>

Symbols

2 conformance fixtures

Boundary and name-shape cases — all of these stay plain text. The colon is glued to a word character in the first two, and _ cannot open a name in the third:

carve
a:b:c and 10:30: meeting, :_x: too.
html
<p>a:b:c and 10:30: meeting, :_x: too.</p>

A symbol is recognized before smart typography, so a name made of typographic punctuation is a symbol rather than a substitution: :+-: is the symbol +-, not a ± between colons. The typographic forms still apply wherever no symbol opens — a +- b is a ± b, and word:+-: has no boundary, so its +- is substituted:

carve
Vote :+1: or :-1:. Tolerance :+-: is a symbol, but a +- b and word:+-: are not.
html
<p>Vote :+1: or :-1:. Tolerance :+-: is a symbol, but a ± b and word:±: are not.</p>

Released under the MIT License.