From f151ea5c097e4738a4eac0a4646301f1dc950659 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Fri, 30 Oct 2026 01:37:16 +0100 Subject: [PATCH] =?UTF-8?q?Answer=20stability=20round=201:=201=20?= =?UTF-8?q?=F0=9F=94=B4,=202=20=F0=9F=9F=A1=20(0=20misread);=20hold=20a=20?= =?UTF-8?q?pair=20whose=20opener=20may=20capture=20a=20held=20pair's=20del?= =?UTF-8?q?imiter,=20draw=20repeated=20units,=20pin=20the=20carry=20guards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/conformance/property-harness.ts | 6 +++++- src/markdown/emit/inline-line.test.ts | 2 ++ src/markdown/emit/line-escaping.ts | 15 +++++++++++++-- src/markdown/portable/inline-reduction.test.ts | 3 +++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/conformance/property-harness.ts b/src/conformance/property-harness.ts index 07808cb..fe67091 100644 --- a/src/conformance/property-harness.ts +++ b/src/conformance/property-harness.ts @@ -217,7 +217,11 @@ const lineLeaf = fc.oneof( { arbitrary: fc.record({ attrs: fc.constantFrom({ id: 'a' }, { id: 'a', odd: 'b' }), marks: lineMarks }).map((held): AdfNode => ({ ...held, type: 'mention' })), weight: 1 }, ) export const lineContainer = fc.constantFrom('heading', 'paragraph', 'table-cell') -export const markedLine = fc.array(lineLeaf, { maxLength: 10, minLength: 1 }) +// A unit repeated is the line where one misread run re-pairs every copy after it. +const repeatedUnit = fc + .tuple(fc.array(lineLeaf, { maxLength: 2 }), fc.array(lineLeaf, { maxLength: 3, minLength: 1 }), fc.integer({ max: 5, min: 2 }), fc.array(lineLeaf, { maxLength: 2 })) + .map(([head, unit, repeats, tail]) => [...head, ...Array.from({ length: repeats }, () => unit.map((node) => ({ ...node }))).flat(), ...tail]) +export const markedLine = fc.oneof(fc.array(lineLeaf, { maxLength: 10, minLength: 1 }), repeatedUnit) export const adfDocument = fc.oneof( { arbitrary: fc.array(positions.block, { depthIdentifier, maxLength: 4, minLength: 1 }).map((content): AdfDocument => ({ content, type: 'doc', version: 1 })), weight: 30 }, diff --git a/src/markdown/emit/inline-line.test.ts b/src/markdown/emit/inline-line.test.ts index 1272a77..16b8893 100644 --- a/src/markdown/emit/inline-line.test.ts +++ b/src/markdown/emit/inline-line.test.ts @@ -19,6 +19,8 @@ test('spells a line alike taking every fallback a pass finds or the first alone' [[{ marks: [{ type: 'em' }], text: 'a', type: 'text' }, { marks: [{ type: 'em' }, { type: 'strike' }, { type: 'strong' }], text: 'a', type: 'text' }, { text: 'a', type: 'text' }], 'paragraph'], [[1, 2, 3].flatMap(() => [{ text: 'un', type: 'text' }, { marks: [{ type: 'em' }, { type: 'strong' }], text: '-r', type: 'text' }]), 'paragraph'], [[{ text: 'un', type: 'text' }, { marks: [{ type: 'em' }, { type: 'strong' }], text: '-r', type: 'text' }, { marks: [{ type: 'strike' }], text: 'un', type: 'text' }, { marks: [{ type: 'strike' }, { type: 'em' }, { type: 'strong' }], text: '-r', type: 'text' }, { text: 'z', type: 'text' }], 'paragraph'], + [[{ marks: [{ type: 'strike' }], text: ' b', type: 'text' }, { marks: [{ type: 'em' }], text: '!-', type: 'text' }], 'paragraph'], + [[{ marks: [{ type: 'strike' }], text: 'r', type: 'text' }, { marks: [{ type: 'strike' }, { type: 'strong' }, { type: 'em' }], text: '[ ', type: 'text' }, ...[1, 2, 3, 4, 5].map(() => ({ marks: [{ type: 'strike' }], text: ' ', type: 'text' }))], 'heading'], ], }, ) diff --git a/src/markdown/emit/line-escaping.ts b/src/markdown/emit/line-escaping.ts index d956e3c..3a8fa03 100644 --- a/src/markdown/emit/line-escaping.ts +++ b/src/markdown/emit/line-escaping.ts @@ -333,11 +333,22 @@ function misflankedHighlights(placed: readonly PlacedSegment[], output: string): return misflanked } -// A pair falls with the first where one run per pass would take it back too: one of its delimiters cannot flank however the unsettled pairs fall, and taking it back moves no delimiter another verdict reads. +// A pair falls with the first where one run per pass would take it back too: one of its delimiters cannot flank however the unsettled pairs fall. function fallsWithFirst(sweep: PairSweep, judged: number): boolean { if (!fallsCleanly(sweep, judged)) return false const spellings = pairSpellings(sweep, judged, judged) - return spellings !== undefined && spellings.every((character) => !mayFlank(sweep, judged, character, 'opener') || !mayFlank(sweep, judged, character, 'closer')) + return spellings !== undefined && spellings.every((character) => !mayFlank(sweep, judged, character, 'opener') || (!mayFlank(sweep, judged, character, 'closer') && !holdsAlike(sweep, judged, character))) +} + +// An opener that may open can pair with a delimiter of its own character inside, so one run per pass may take back a pair standing there first. +function holdsAlike({ fallen, pairs }: PairSweep, judged: number, character: string): boolean { + const pair = pairs[judged] + for (let held = judged - 1; pair !== undefined && held >= 0; held -= 1) { + const inner = pairs[held] + if (inner === undefined || inner.closer < pair.opener) return false + if (fallen[held] === 0 && (inner.delimiter.length === 1 ? character !== '~' : inner.delimiter.startsWith(character))) return true + } + return false } // A carry splits every pair holding it, so a carried pair holds no pair that may fall first; a dropped pair keeps its leaves where no run merges at its ends. diff --git a/src/markdown/portable/inline-reduction.test.ts b/src/markdown/portable/inline-reduction.test.ts index b17dbcb..d6253f4 100644 --- a/src/markdown/portable/inline-reduction.test.ts +++ b/src/markdown/portable/inline-reduction.test.ts @@ -21,6 +21,9 @@ test('reduces a line alike dropping every run a pass may or the first alone', () [[1, 2, 3].flatMap(() => [{ marks: [{ type: 'em' }, { type: 'strike' }, { type: 'strong' }], text: '(', type: 'text' }, { attrs: { id: 'a' }, type: 'mention' }]), 'paragraph'], [[{ text: 'z', type: 'text' }, ...[1, 2, 3].flatMap(() => [{ marks: [{ type: 'strike' }], text: '(b', type: 'text' }, { marks: [{ type: 'em' }], text: '(b', type: 'text' }])], 'paragraph'], [[...[1, 2, 3].flatMap(() => [{ marks: [{ type: 'strike' }], text: 'b(', type: 'text' }, { marks: [{ type: 'em' }], text: 'b(', type: 'text' }]), { text: 'z', type: 'text' }], 'paragraph'], + [[...[1, 2, 3].flatMap(() => [{ marks: [{ type: 'em' }, { type: 'strong' }], text: '(', type: 'text' }, { marks: [{ type: 'em' }], text: '(', type: 'text' }]), { text: 'a', type: 'text' }], 'paragraph'], + [[...[1, 2, 3, 4, 5].flatMap(() => [{ marks: [{ type: 'em' }, { type: 'strong' }], text: '~** ', type: 'text' }, { marks: [{ type: 'em' }], text: '- _', type: 'text' }]), { text: 'r', type: 'text' }], 'table-cell'], + [[...[1, 2, 3].flatMap(() => [{ marks: [{ type: 'em' }, { type: 'strong' }], text: ' ~~ ', type: 'text' }, { marks: [{ type: 'em' }, { type: 'strike' }, { type: 'strong' }], text: ')', type: 'text' }]), { text: 'a', type: 'text' }, { marks: [{ type: 'strike' }, { type: 'strong' }], text: '.unun', type: 'text' }], 'heading'], ], }, )