26 - the escape phases and the container stack hold their invariants in types #125

Merged
lilleman merged 2 commits from 26 into main 2026-09-24 00:03:45 +02:00
3 changed files with 13 additions and 11 deletions
Showing only changes of commit d5370b126d - Show all commits
+3 -2
View File
@@ -91,14 +91,15 @@ function escapeClaims(scan: string, escapings: readonly InlineEscaping[], contai
if (index > line.start + line.text.length) line = scanLine(scan, line.start + line.text.length + 1)
const escaping = escapings[index]
const escapable = escaping === 'backslash' || escaping === 'bracketed'
afterEscape =
const claimed: boolean =
(escapable &&
(claimsLineStart(line, index, container) ||
mergesWithSyntax(scan, escapings, index) ||
opensConstruct(scan, linkClose, index, escaping === 'bracketed', container, afterEscape))) ||
(escaping === 'bracketed-link-target' &&
((scan.charAt(index) === '`' && opensCodeSpan(scan, index, afterEscape)) || claimsDirectivePrefix(scan, index)))
if (afterEscape) escaped.add(index)
if (claimed) escaped.add(index)
afterEscape = claimed
}
return escaped
}
+9 -8
View File
@@ -43,7 +43,7 @@ export type DirectiveBlock = Extract<Block, { kind: 'directive' }>
type ListBlock = Extract<Block, { items: Block[][] }>
type OpenDirective = { blocks: Block[]; depths: number[]; index: number; kind: 'directive'; name: string; parent: Block[]; position: SourcePosition }
type OpenDirective = { blocks: Block[]; index: number; kind: 'directive'; name: string; parent: Block[]; position: SourcePosition }
type EdgeContainer = Extract<Block, { kind: 'blockquote' }> | { blocks: Block[]; indentation: number; kind: 'item'; list: ListBlock }
@@ -65,6 +65,7 @@ type Line = { column: number; text: string }
type LeafOpener = { index: number; position: SourcePosition }
type ContainerStack = {
directiveDepth: (name: string) => number | undefined
drop: (depth: number) => OpenContainer[]
edges: readonly { container: EdgeContainer; depth: number }[]
open: readonly OpenContainer[]
@@ -72,7 +73,6 @@ type ContainerStack = {
}
type Walk = ParsedBlocks & {
directiveDepths: Map<string, number[]>
leaf: OpenLeaf | undefined
leafOpeners: Map<Block[], Map<string, LeafOpener>>
position: SourcePosition
@@ -87,7 +87,6 @@ export function parseBlocks(markdown: string): ParsedBlocks {
const walk: Walk = {
blocks: [],
definitions: new Map(),
directiveDepths: new Map(),
leaf: undefined,
leafOpeners: new Map(),
position: { line: 1, offset: 0 },
@@ -102,13 +101,16 @@ export function parseBlocks(markdown: string): ParsedBlocks {
}
function containerStack(): ContainerStack {
const directiveDepths = new Map<string, number[]>()
const depthsOf = (name: string): number[] => entryOf(directiveDepths, name, () => [])
const edges: { container: EdgeContainer; depth: number }[] = []
const open: OpenContainer[] = []
return {
directiveDepth: (name) => directiveDepths.get(name)?.at(-1),
drop: (depth) => {
const dropped = open.splice(depth)
for (const container of dropped) {
if (container.kind === 'directive') container.depths.pop()
if (container.kind === 'directive') depthsOf(container.name).pop()
else edges.pop()
}
return dropped
@@ -117,7 +119,7 @@ function containerStack(): ContainerStack {
open,
push: (container) => {
const depth = open.push(container) - 1
if (container.kind === 'directive') container.depths.push(depth)
if (container.kind === 'directive') depthsOf(container.name).push(depth)
else edges.push({ container, depth })
},
}
@@ -281,8 +283,7 @@ function openDirective(walk: Walk, directive: Extract<DirectiveLine, { kind: 'op
entryOf(walk.leafOpeners, parent, () => new Map<string, LeafOpener>()).set(name, { index, position })
return
}
const depths = entryOf(walk.directiveDepths, name, (): number[] => [])
walk.stack.push({ blocks: block.blocks, depths, index, kind: 'directive', name, parent, position })
walk.stack.push({ blocks: block.blocks, index, kind: 'directive', name, parent, position })
}
function closeDirective(walk: Walk, name: string): void {
@@ -302,7 +303,7 @@ function closeDirective(walk: Walk, name: string): void {
// A closer crosses no list item or blockquote edge.
function openDirectiveDepth(walk: Walk, name: string): number | undefined {
const depth = walk.directiveDepths.get(name)?.at(-1)
const depth = walk.stack.directiveDepth(name)
return depth === undefined || depth < (walk.stack.edges.at(-1)?.depth ?? -1) ? undefined : depth
}
+1 -1
View File
@@ -1064,7 +1064,7 @@ The done `todo.md` items in full, as they were written. `todo.md` keeps a one-li
panel's first and second hardest places. `escapeClaims` hands each predicate the one fact
it reads — whether the character before was escaped — and `escapeClosedRuns` takes that
pass's set read-only and returns the whole. `containerStack` is the one writer of the stack,
`edges` and each directive's `depths`, all read-only elsewhere; deriving `edges` from the
`edges` and the open directives' depths by name, all read-only elsewhere; deriving `edges` from the
stack instead makes every line walk the open directives, quadratic in their nesting.
## 5 — Ship `0.1.0`