Compare commits
3 Commits
5fe2e6ec42
...
ad1f3e996b
| Author | SHA1 | Date | |
|---|---|---|---|
| ad1f3e996b | |||
| b6ba440410 | |||
| 7d20f3e895 |
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"text": "`` ``",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"marks": [
|
||||||
|
{
|
||||||
|
"type": "code"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"text": "a",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "paragraph"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"marks": [
|
||||||
|
{
|
||||||
|
"type": "underline"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"text": "``",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "``",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"marks": [
|
||||||
|
{
|
||||||
|
"type": "code"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"text": "a",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "paragraph"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"text": "``\\`",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"marks": [
|
||||||
|
{
|
||||||
|
"type": "code"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"text": "`",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "`",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "paragraph"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "doc",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
\`\` \`\``a`
|
||||||
|
|
||||||
|
:underline[\`\`]\`\``a`
|
||||||
|
|
||||||
|
\`\`\\\``` ` ``\`
|
||||||
+4
-2
@@ -54,8 +54,10 @@ normalizes to it through the round-trip.
|
|||||||
- Entity references in input decode to their characters; output backslash-escapes only where text
|
- Entity references in input decode to their characters; output backslash-escapes only where text
|
||||||
would otherwise parse as syntax, scanning the assembled line rather than each text node: escape
|
would otherwise parse as syntax, scanning the assembled line rather than each text node: escape
|
||||||
the leading delimiter of a construct that would otherwise open, re-scan from there, and repeat.
|
the leading delimiter of a construct that would otherwise open, re-scan from there, and repeat.
|
||||||
A backtick run escapes whole, and a lone backtick escapes wherever an escaped one follows it in
|
A backtick run escapes whole, and a bare one escapes wherever a later backtick string of its
|
||||||
the same inline content: CommonMark reads no escape inside a code span, so `` \` `` closes one.
|
length forms around an escape in the same inline content — an escaped backtick alone, one joined
|
||||||
|
to the bare run after it, or a bare run an escape splits off: CommonMark reads no escape inside a
|
||||||
|
code span, so such a string still closes one.
|
||||||
Where a paragraph opens with what reads as a link reference definition, which resolves before
|
Where a paragraph opens with what reads as a link reference definition, which resolves before
|
||||||
any inline construct binds (a `]` inside a code span or `{attrs}` counts), an opening text `[`
|
any inline construct binds (a `]` inside a code span or `{attrs}` counts), an opening text `[`
|
||||||
escapes and an opening link's nodes ride the carry.
|
escapes and an opening link's nodes ride the carry.
|
||||||
|
|||||||
@@ -427,6 +427,6 @@ test('generated markdown refuses, or what it parses to refuses to emit, or its s
|
|||||||
)
|
)
|
||||||
if (!parameters.gate) return
|
if (!parameters.gate) return
|
||||||
assert.ok(fixpoints >= fixpointFloor, `${fixpoints} of ${gateRuns} runs reached the fixpoint, under the floor of ${fixpointFloor}`)
|
assert.ok(fixpoints >= fixpointFloor, `${fixpoints} of ${gateRuns} runs reached the fixpoint, under the floor of ${fixpointFloor}`)
|
||||||
assert.ok(directiveShaped >= directiveShapedFloor, `${directiveShaped} runs reaching the fixpoint held a node or mark only a directive or the carry spells, under the floor of ${directiveShapedFloor}`)
|
assert.ok(directiveShaped >= directiveShapedFloor, `${directiveShaped} runs reaching the fixpoint held a node or mark outside CommonMark's own types, under the floor of ${directiveShapedFloor}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -103,17 +103,33 @@ function escapedIndexes(scan: string, escapings: readonly InlineEscaping[], cont
|
|||||||
escaped.add(index)
|
escaped.add(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
escapeLoneBackticks(scan, escapings, escaped)
|
escapeClosedRuns(scan, escapings, escaped)
|
||||||
return escaped
|
return escaped
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommonMark reads no escape inside a code span, so an escaped backtick still closes one a bare lone backtick before it opens.
|
// CommonMark reads no escape inside a code span, so a backtick string an escape forms or splits off still closes one an earlier bare run opens.
|
||||||
function escapeLoneBackticks(scan: string, escapings: readonly InlineEscaping[], escaped: Set<number>): void {
|
function escapeClosedRuns(scan: string, escapings: readonly InlineEscaping[], escaped: Set<number>): void {
|
||||||
let escapedAfter = false
|
const formed = new Set<number>()
|
||||||
for (let index = scan.length - 1; index >= 0; index -= 1) {
|
let end = scan.length - 1
|
||||||
if (scan.charAt(index) !== '`') continue
|
while (end >= 0) {
|
||||||
if (escapedAfter && escapings[index] !== 'none' && scan.charAt(index - 1) !== '`' && scan.charAt(index + 1) !== '`') escaped.add(index)
|
if (scan.charAt(end) !== '`') {
|
||||||
if (escaped.has(index)) escapedAfter = true
|
end -= 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let start = end
|
||||||
|
while (scan.charAt(start - 1) === '`') start -= 1
|
||||||
|
let segmentEnd = end
|
||||||
|
for (let index = end; index > start; index -= 1) {
|
||||||
|
if (!escaped.has(index)) continue
|
||||||
|
formed.add(segmentEnd - index + 1)
|
||||||
|
segmentEnd = index - 1
|
||||||
|
}
|
||||||
|
if (segmentEnd !== end || escaped.has(start)) formed.add(segmentEnd - start + 1)
|
||||||
|
else if (escapings[start] !== 'none' && formed.has(end - start + 1)) {
|
||||||
|
for (let index = start; index <= end; index += 1) escaped.add(index)
|
||||||
|
formed.add(1)
|
||||||
|
}
|
||||||
|
end = start - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ const marks = fc.uniqueArray(mark, { maxLength: 3, selector: (held) => held.type
|
|||||||
|
|
||||||
const textNode = fc.record({ marks, text }).map((held): AdfNode => ({ ...held, type: 'text' }))
|
const textNode = fc.record({ marks, text }).map((held): AdfNode => ({ ...held, type: 'text' }))
|
||||||
|
|
||||||
|
const backtickRunNode = fc
|
||||||
|
.record({ marks: fc.oneof(fc.constant<AdfMark[]>([]), fc.constant<AdfMark[]>([{ type: 'code' }]), marks), text: fc.string({ maxLength: 6, minLength: 1, unit: fc.constantFrom('`', '``', ' ', 'a') }) })
|
||||||
|
.map((held): AdfNode => ({ ...held, type: 'text' }))
|
||||||
|
|
||||||
const autolinkTextNode = fc
|
const autolinkTextNode = fc
|
||||||
.record({ href: fc.tuple(fc.constantFrom('ab:', 'http://'), textOf(0)).map(([scheme, rest]) => `${scheme}${rest}`), marks })
|
.record({ href: fc.tuple(fc.constantFrom('ab:', 'http://'), textOf(0)).map(([scheme, rest]) => `${scheme}${rest}`), marks })
|
||||||
.map(({ href, marks: held }): AdfNode => ({ marks: [...held.filter((outer) => outer.type !== 'link'), { attrs: { href }, type: 'link' }], text: href, type: 'text' }))
|
.map(({ href, marks: held }): AdfNode => ({ marks: [...held.filter((outer) => outer.type !== 'link'), { attrs: { href }, type: 'link' }], text: href, type: 'text' }))
|
||||||
@@ -119,7 +123,7 @@ const positions = fc.letrec<Positions>((tie) => {
|
|||||||
const leafBlocks = blockNodes.filter((entry) => entry.leaf).map((entry) => entry.node)
|
const leafBlocks = blockNodes.filter((entry) => entry.leaf).map((entry) => entry.node)
|
||||||
const containerBlocks = blockNodes.filter((entry) => !entry.leaf).map((entry) => entry.node)
|
const containerBlocks = blockNodes.filter((entry) => !entry.leaf).map((entry) => entry.node)
|
||||||
const misplacedWeight = 7
|
const misplacedWeight = 7
|
||||||
const paragraph = inlineContent.map((content): AdfNode => ({ content, type: 'paragraph' }))
|
const paragraph = fc.oneof({ arbitrary: inlineContent, weight: 3 }, { arbitrary: fc.array(backtickRunNode, { maxLength: 4, minLength: 2 }), weight: 1 }).map((content): AdfNode => ({ content, type: 'paragraph' }))
|
||||||
const cell = (type: string) => paragraph.map((held): AdfNode => ({ content: [held], type }))
|
const cell = (type: string) => paragraph.map((held): AdfNode => ({ content: [held], type }))
|
||||||
const listItems = fc.array(
|
const listItems = fc.array(
|
||||||
blockContent.map((content): AdfNode => ({ content, type: 'listItem' })),
|
blockContent.map((content): AdfNode => ({ content, type: 'listItem' })),
|
||||||
@@ -150,6 +154,7 @@ const positions = fc.letrec<Positions>((tie) => {
|
|||||||
{ depthIdentifier, depthSize: 'small', maxDepth: 4 },
|
{ depthIdentifier, depthSize: 'small', maxDepth: 4 },
|
||||||
{ arbitrary: textNode, weight: 12 },
|
{ arbitrary: textNode, weight: 12 },
|
||||||
{ arbitrary: autolinkTextNode, weight: 2 },
|
{ arbitrary: autolinkTextNode, weight: 2 },
|
||||||
|
{ arbitrary: backtickRunNode, weight: 3 },
|
||||||
{ arbitrary: fc.oneof(...inlineNodes), weight: 7 },
|
{ arbitrary: fc.oneof(...inlineNodes), weight: 7 },
|
||||||
{ arbitrary: fc.oneof(...blockNodes.map((entry) => entry.node), unknownNode), weight: 2 },
|
{ arbitrary: fc.oneof(...blockNodes.map((entry) => entry.node), unknownNode), weight: 2 },
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -64,10 +64,12 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c
|
|||||||
10c's properties as its third user. Under the gate seed the property asserts floors on the
|
10c's properties as its third user. Under the gate seed the property asserts floors on the
|
||||||
runs reaching the fixpoint and on the directive-shaped ones. It lands when hunts of several
|
runs reaching the fixpoint and on the directive-shaped ones. It lands when hunts of several
|
||||||
hundred thousand runs per engine pass clean, since the breaks hit once per ~150,000 runs,
|
hundred thousand runs per engine pass clean, since the breaks hit once per ~150,000 runs,
|
||||||
past a 10,000-run bar. Two breaks, both shipped in `0.1.0`, are fixed inside it. An escaped
|
past a 10,000-run bar. Two breaks, both shipped in `0.1.0`, are fixed inside it. A backtick
|
||||||
backtick closed an earlier lone backtick's code span, since CommonMark reads no escape
|
string an escape formed closed an earlier bare run's code span, since CommonMark reads no
|
||||||
inside one: a backtick run now escapes whole, and a lone backtick escapes wherever an
|
escape inside one: an escaped backtick alone or, as the review found, one joined to the bare
|
||||||
escaped one follows it in the same inline content. A paragraph's opening read as a link
|
run after it. A backtick run now escapes whole, and a bare run escapes wherever a later
|
||||||
|
string of its length forms around an escape in the same inline content: the generalized pass
|
||||||
|
the maintainer chose (2026-09-15). A paragraph's opening read as a link
|
||||||
reference definition across a `]` the emitter spelled: the emitter now escapes the opening
|
reference definition across a `]` the emitter spelled: the emitter now escapes the opening
|
||||||
`[` exactly when the parser's own definition reader accepts the paragraph, and a link
|
`[` exactly when the parser's own definition reader accepts the paragraph, and a link
|
||||||
opening it rides the carry until 13b.
|
opening it rides the carry until 13b.
|
||||||
|
|||||||
Reference in New Issue
Block a user