3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-12 11:48:29 +01:00

Answer prose round 1: 0 🔴, 7 🟡 (0 misread); the bound test's block count is 53, comments name their referents

This commit is contained in:
2026-10-29 16:19:12 +01:00
committed by Lillem4n
parent bbd2ad9c74
commit ee95b3035f
8 changed files with 11 additions and 12 deletions
+1 -1
View File
@@ -203,7 +203,7 @@ export function holdsThematicBreak(tail: ThematicBreakTail | undefined, text: st
return text.length >= tail.shortest && text.length <= tail.longest && text.charAt(0) === tail.marker
}
// True of every line isThematicBreak takes, and kept for a prefixed line from its parts without reading it.
// Every line isThematicBreak accepts passes this, and a joined line passes exactly when each part does, so BlockText derives it from a prefix and its body.
export function holdsOnlyRuleCharacters(text: string): boolean {
for (let index = 0; index < text.length; index += 1) {
const character = text.charAt(index)
+1 -1
View File
@@ -76,7 +76,7 @@ test('refuses a quote whose prefixed lines would pass V8\'s longest string', ()
assert.equal(pastTheBound(adfToLosslessMarkdown(document({ content: [code, code], type: 'blockquote' }))), true)
})
// A code block spelled in 55 characters around its text; the blocks between them take a blank line, and the markdown a closing line break.
// A code block written as its text plus 53 characters; a blank line parts two blocks, and the markdown ends with a line break.
function codeBlockOf(textLength: number): AdfNode {
return { attrs: {}, content: [{ text: `${'a'.repeat(textLength - 1)}\n`, type: 'text' }], type: 'codeBlock' }
}
+1 -1
View File
@@ -72,7 +72,7 @@ export function walkBlocks(nodes: readonly AdfNode[], path: ConvertErrorPath, de
for (const [index, node] of nodes.entries()) {
const block = emitBlock(node, [...path, 'content', index], depth, writing)
if (!block.ok) return block
// Refused on the measure, before any text is written, so every engine stops at the first block past the bound.
// Refused on the measure, before any text is written, so every engine stops at the first block past V8's longest string.
const fits = withinLongest(block.value.text.length, block.value)
if (!fits.ok) return fits
headroom = Math.min(headroom, block.value.headroom)
+2 -3
View File
@@ -8,7 +8,7 @@ type Lines =
| { kind: 'joined'; parts: readonly BlockText[] }
| { kind: 'text'; lines: readonly string[] }
type Prefix = Lines & { kind: 'prefixed' | 'quoted' }
// The written text's measures; the flags answer for the first line unread, since reading a concatenated string copies it whole, at every level it nests.
// Measures of the written text. The flags describe `first` so no caller reads it: reading a concatenated string copies it whole, once per nesting level.
type Shape = { blankRest: number; emptyRest: number; first: string; firstBlank: boolean; firstCharacter: string; firstRuleCharacters: boolean; length: number; lineCount: number }
export const emptyLine = blockText('')
@@ -20,7 +20,6 @@ export function blockText(text: string): BlockText {
return { ...firstShape, blankRest, emptyRest: rest.filter((line) => line === '').length, kind: 'text', length: text.length, lineCount: rest.length + 1, lines: [first, ...rest] }
}
// The parts' lines one after another.
export function joinedText(parts: readonly BlockText[]): BlockText {
const [first, ...others] = parts
if (first === undefined) return emptyLine
@@ -35,7 +34,7 @@ export function joinedText(parts: readonly BlockText[]): BlockText {
return { blankRest, emptyRest, first: first.first, firstBlank, firstCharacter, firstRuleCharacters, kind: 'joined', length, lineCount, parts }
}
// head, never blank, leads the first line, and indent, spaces alone, every later line holding text.
// head, never blank, leads the first line; indent, spaces alone, leads every later line holding text.
export function prefixedText(head: string, indent: string, body: BlockText): BlockText {
const first = `${head}${body.first}`
const length = body.length + head.length + indent.length * (body.lineCount - 1 - body.emptyRest)
+1 -1
View File
@@ -27,7 +27,7 @@ type Emission = { carries: [NodeRange, ...NodeRange[]]; segments?: undefined } |
type Fallback = [NodeRange, ...NodeRange[]] | 'opening-link'
// How many of a pass's fallbacks it takes: `every`, or the `first` alone, which a property holds to the same line.
// How many fallbacks one pass applies: `every`, or the `first` alone; a property checks that both write the same line.
export type FallbacksPerPass = 'every' | 'first'
type InlineContext = {
+3 -3
View File
@@ -270,7 +270,7 @@ function claimsCharacter({ container, escapings, headingCloser, inline: { text:
}
}
// Of the runs the line does not read back as written, the first emphasis run to close and every later one that gives up with it, else every highlight run: one misread emphasis pair can re-pair the delimiters after it.
// The runs the line does not read back as written: the first emphasis run to close and each later one that gives up with it, else every misflanked highlight run. One misread emphasis pair can re-pair the delimiters after it.
function unspellableRuns(placed: readonly PlacedSegment[], reading: InlineReading, output: string): MarkRun[] {
const misread: MarkRun[] = []
const misflanked: MarkRun[] = []
@@ -287,7 +287,7 @@ function unspellableRuns(placed: readonly PlacedSegment[], reading: InlineReadin
return misread.length > 0 ? misread : misflanked
}
// The first run gives up where the reading pairs its delimiters otherwise; a later one, with the earlier ones, where it holds none of them, so their fallbacks leave its range, and a delimiter of it cannot flank.
// The first run gives up where the reading pairs its delimiters otherwise. A later run gives up where it holds none of the earlier runs, so dropping them leaves its range alone, and one of its delimiters cannot flank.
function givesUp(pair: EmphasisPair, latest: MarkRun | undefined, reading: InlineReading, output: string): boolean {
if (latest === undefined) return !readAsWritten(reading, pair.opener, pair.closer, pair.delimiter.length)
// Runs close in order, so a run holding any earlier one holds the latest.
@@ -295,7 +295,7 @@ function givesUp(pair: EmphasisPair, latest: MarkRun | undefined, reading: Inlin
return !mayFlank(output, pair.opener, pair.delimiter, 'open') || !mayFlank(output, pair.closer, pair.delimiter, 'close')
}
// Whether the delimiter flanks once earlier runs fall back: a delimiter character beside it may fall back, while text or its own hard break stays.
// Whether the delimiter flanks once earlier runs fall back: a delimiter character beside it may fall back, while text or the run's own hard break stays.
function mayFlank(output: string, start: number, delimiter: string, role: DelimiterRole): boolean {
const before = output.charAt(start - 1)
const after = output.charAt(start + delimiter.length)
+1 -1
View File
@@ -1,7 +1,7 @@
type AlertMarker = { folded: boolean; length: number; panelType: string }
export const foldedAlertMarker = '[!NOTE]-'
// Task and alert markers open with it, so a writer tests a block's first character before reading its first line, which flattens that line at every level it nests.
// Task and alert markers open with it, so a block whose first character differs holds neither.
export const markerOpening = '['
const alertWords: Readonly<Record<string, string>> = {
+1 -1
View File
@@ -250,7 +250,7 @@ function withoutMarks(leaves: readonly AdfNode[], runs: readonly MarkRun[]): Adf
})
}
// Every misflanked highlight drops with the first run; taking `every`, so does a later emphasis run whose drop merges no run and moves no edge.
// Every misflanked highlight drops with the first run; with `every`, so does a later emphasis run whose drop merges no run and moves no edge.
function droppedRuns(leaves: readonly AdfNode[], [first, ...others]: readonly [MarkRun, ...MarkRun[]], perPass: FallbacksPerPass): MarkRun[] {
return [first, ...others.filter((run) => nodeMarks(leaves[run.first] ?? {})[run.depth]?.type === highlight || (perPass === 'every' && dropsAlone(leaves, run)))]
}