18 - the parse keeps each node's readable spelling, so the directive ask spells it once #105
@@ -316,9 +316,12 @@ someone spells it or pins it.
|
|||||||
- The parse keeps each node's readable spelling in a memo, so the `commonMarkSpelling` ask spells a
|
- The parse keeps each node's readable spelling in a memo, so the `commonMarkSpelling` ask spells a
|
||||||
node once rather than once per level above it (18). The node reference is the key, which holds
|
node once rather than once per level above it (18). The node reference is the key, which holds
|
||||||
because the parse builds one object per position; `adfToMarkdown` passes no memo, where a
|
because the parse builds one object per position; `adfToMarkdown` passes no memo, where a
|
||||||
consumer's document may hold one node at two positions (4b). A kept spelling rebases: `text` and
|
consumer's document may hold one node at two positions (4b). `text` and `spelling` carry no depth
|
||||||
`spelling` carry no depth, `headroom` is affine in it, and a reuse sits at or above the depth that
|
and `headroom` is affine in it, so a read above the depth that filled the entry rebases; a read
|
||||||
filled it. Only what succeeded is kept, so no path minted at another position is ever read.
|
below re-spells, because a hit skips the depth guards the walk it replaces runs and an ordered
|
||||||
|
list past the marker cap gives way, spending two emitter levels where the parser spent one. A
|
||||||
|
give-way is kept too and serves any depth, reading the node's shape alone. Only what succeeded is
|
||||||
|
kept, so no path minted at another position is ever read.
|
||||||
- No casts: `as`, `as unknown as`, non-null `!`. A boundary owes a type guard validating the
|
- No casts: `as`, `as unknown as`, non-null `!`. A boundary owes a type guard validating the
|
||||||
fields it claims (`isAdfDocument`); past it everything is typed. Make invalid states
|
fields it claims (`isAdfDocument`); past it everything is typed. Make invalid states
|
||||||
unrepresentable.
|
unrepresentable.
|
||||||
|
|||||||
@@ -103,8 +103,11 @@ export function commonMarkSpelling(node: AdfNode, path: ConvertErrorPath, depth:
|
|||||||
|
|
||||||
function readableBlock(node: AdfNode, path: ConvertErrorPath, depth: number, memo: SpellingMemo | undefined): Result<EmittedBlock> | undefined {
|
function readableBlock(node: AdfNode, path: ConvertErrorPath, depth: number, memo: SpellingMemo | undefined): Result<EmittedBlock> | undefined {
|
||||||
const kept = memo?.get(node)
|
const kept = memo?.get(node)
|
||||||
// A reuse sits no deeper than the fill it reads, so the kept headroom only ever grows (AGENTS.md §11).
|
if (kept !== undefined) {
|
||||||
if (kept !== undefined) return kept.block === undefined ? undefined : success({ ...kept.block, headroom: kept.block.headroom + kept.depth - depth })
|
if (kept.block === undefined) return undefined
|
||||||
|
// A read below the fill would skip the depth guards the walk it replaces runs (AGENTS.md §11).
|
||||||
|
if (depth <= kept.depth) return success({ ...kept.block, headroom: kept.block.headroom + kept.depth - depth })
|
||||||
|
}
|
||||||
const spelled = spellReadableBlock(node, path, depth, memo)
|
const spelled = spellReadableBlock(node, path, depth, memo)
|
||||||
if (spelled === undefined) memo?.set(node, { block: undefined, depth })
|
if (spelled === undefined) memo?.set(node, { block: undefined, depth })
|
||||||
else if (spelled.ok) memo?.set(node, { block: spelled.value, depth })
|
else if (spelled.ok) memo?.set(node, { block: spelled.value, depth })
|
||||||
|
|||||||
@@ -672,7 +672,7 @@ test('refuses input nested deeper than the parser carries', () => {
|
|||||||
assert.equal(code(markdownToAdf(marks(largestNesting + 1))), 'unsupported-nesting-depth')
|
assert.equal(code(markdownToAdf(marks(largestNesting + 1))), 'unsupported-nesting-depth')
|
||||||
assert.deepEqual(content(markdownToAdf(marks(largestNesting))), [{ content: [marked('a', underline)], type: 'paragraph' }])
|
assert.deepEqual(content(markdownToAdf(marks(largestNesting))), [{ content: [marked('a', underline)], type: 'paragraph' }])
|
||||||
const nest = (names: readonly string[], body: string): string => [...names.map((name) => `!adf:${name}\n`), body, ...names.map((name) => `!adf:/${name}\n`).reverse()].join('')
|
const nest = (names: readonly string[], body: string): string => [...names.map((name) => `!adf:${name}\n`), body, ...names.map((name) => `!adf:/${name}\n`).reverse()].join('')
|
||||||
const repeated = (name: string): string[] => Array.from({ length: largestNesting }, () => name)
|
const repeated = (name: string, levels: number = largestNesting): string[] => Array.from({ length: levels }, () => name)
|
||||||
assert.ok(markdownToAdf(nest(repeated('panel'), '!adf:paragraph {localId=a-1}\nPart.\n!adf:/paragraph\n')).ok)
|
assert.ok(markdownToAdf(nest(repeated('panel'), '!adf:paragraph {localId=a-1}\nPart.\n!adf:/paragraph\n')).ok)
|
||||||
assert.deepEqual(position(markdownToAdf(nest(['expand', ...repeated('panel'), 'expand'], 'Part.\n'))), { line: 501, offset: 5501 })
|
assert.deepEqual(position(markdownToAdf(nest(['expand', ...repeated('panel'), 'expand'], 'Part.\n'))), { line: 501, offset: 5501 })
|
||||||
assert.equal(code(markdownToAdf(nest(['panel', ...repeated('expand'), 'panel'], 'Part.\n'))), 'unsupported-nesting-depth')
|
assert.equal(code(markdownToAdf(nest(['panel', ...repeated('expand'), 'panel'], 'Part.\n'))), 'unsupported-nesting-depth')
|
||||||
@@ -680,6 +680,16 @@ test('refuses input nested deeper than the parser carries', () => {
|
|||||||
const directiveLists = largestNesting / 2
|
const directiveLists = largestNesting / 2
|
||||||
assert.ok(markdownToAdf(listed(directiveLists)).ok)
|
assert.ok(markdownToAdf(listed(directiveLists)).ok)
|
||||||
assert.equal(code(markdownToAdf(listed(directiveLists + 1))), 'unsupported-nesting-depth')
|
assert.equal(code(markdownToAdf(listed(directiveLists + 1))), 'unsupported-nesting-depth')
|
||||||
|
const asking = (body: string): string => `!adf:bulletList\n!adf:listItem\n${body}!adf:/listItem\n!adf:/bulletList\n`
|
||||||
|
// Two items past the largest list marker leave the list no readable spelling, so the emitter
|
||||||
|
// walks it as list plus item where the parser counted one level.
|
||||||
|
const overflowing = (body: string): string => {
|
||||||
|
const marker = '999999999. '
|
||||||
|
return `${marker}${body.replace(/^(?!$)/gm, ' '.repeat(marker.length)).slice(marker.length)}\n${marker}z\n`
|
||||||
|
}
|
||||||
|
const overflowed = (panels: number): string => asking(overflowing(overflowing(asking(`---\n${nest(repeated('panel', panels), 'Part.\n')}`))))
|
||||||
|
assert.equal(code(markdownToAdf(overflowed(493))), 'unsupported-node-shape')
|
||||||
|
assert.equal(code(markdownToAdf(overflowed(494))), 'unsupported-nesting-depth')
|
||||||
assert.ok(markdownToAdf(`${'- '.repeat(largestNesting)}a\n`).ok)
|
assert.ok(markdownToAdf(`${'- '.repeat(largestNesting)}a\n`).ok)
|
||||||
assert.equal(code(markdownToAdf(`${'- '.repeat(largestNesting + 1)}a\n`)), 'unsupported-nesting-depth')
|
assert.equal(code(markdownToAdf(`${'- '.repeat(largestNesting + 1)}a\n`)), 'unsupported-nesting-depth')
|
||||||
})
|
})
|
||||||
|
|||||||
+11
-6
@@ -837,12 +837,17 @@ The done `todo.md` items in full, as they were written. `todo.md` keeps a one-li
|
|||||||
rejected: it reports the outer offender where the build reports the inner one (the
|
rejected: it reports the outer offender where the build reports the inner one (the
|
||||||
maintainer, 2026-09-18).
|
maintainer, 2026-09-18).
|
||||||
**Measured** (2026-09-19): the parse keeps each node's readable spelling (AGENTS.md §11), and
|
**Measured** (2026-09-19): the parse keeps each node's readable spelling (AGENTS.md §11), and
|
||||||
250 nested directive lists fall from 1.22 s to 0.07 s at 16.1 kB and from 5.20 s to 0.19 s at
|
250 nested directive lists fall from 1.22 s to 0.04 s at 16.1 kB and from 5.20 s to 0.12 s at
|
||||||
261 kB; a panel between every pair of lists 1.00 s to 0.07 s, an opaque carry in every item
|
261 kB; a panel between every pair of lists 1.00 s to 0.05 s, an opaque carry in every item
|
||||||
1.08 s to 0.04 s. The two depth-boundary tests are what pin the rebase — dropping it reddens
|
1.08 s to 0.03 s. No figure enters the gate (§14), as 4c settled for a behaviour-preserving
|
||||||
both — so no figure enters the gate (§14), as 4c settled for a behaviour-preserving cost fix.
|
cost fix; what the gate holds is the refusal. The rebase's sign is pinned by the two
|
||||||
The one list accounting was not taken: 4b settled that accounting the day this was filed, and
|
depth-boundary tests already there, its magnitude by a third case the review found: an ordered
|
||||||
reopening it is an ask rather than a chunk.
|
list past the marker cap gives way, so the emitter spends two levels where the parser spent
|
||||||
|
one, and two such lists put a read below the depth that filled its entry — which a hit would
|
||||||
|
answer without the depth guards the walk runs, naming a different refusal at a different path.
|
||||||
|
That read re-spells (the stability-reviewer, 2026-09-19). The one list accounting was not
|
||||||
|
taken: 4b settled that accounting the day this was filed, and reopening it is an ask rather
|
||||||
|
than a chunk.
|
||||||
|
|
||||||
## 5 — Ship `0.1.0`
|
## 5 — Ship `0.1.0`
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user