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

Answer principles round 1: 0 🔴, 2 🟡; read an inline directive from the InlineSource holding its text and closer, and name the quote edges apart from the quotes passed

This commit is contained in:
2026-10-18 11:31:11 +02:00
committed by Lillem4n
parent 15f7d47fb4
commit 5f8c7cf2eb
5 changed files with 21 additions and 23 deletions
+2 -7
View File
@@ -2,8 +2,8 @@ import assert from 'node:assert/strict'
import test from 'node:test'
import type { DirectiveAttributes, DirectiveLine } from './directive-syntax.ts'
import { backtickCloser } from './commonmark/backtick-runs.ts'
import { claimDirectivePrefix, readDirectiveLine, readInlineDirective } from './directive-syntax.ts'
import { inlineSource } from './inline-tokens.ts'
import { largestNesting } from '../nesting.ts'
// A pair the input spells bare decodes to itself; a quoted one names its spelling beside the decoding.
@@ -25,7 +25,7 @@ function fault(line: string): string {
function inline(text: string): unknown {
const prefix = claimDirectivePrefix(text, 0)
if (prefix === undefined) return 'unclaimed'
const read = readInlineDirective(text, prefix, backtickCloser(text))
const read = readInlineDirective(inlineSource(text), prefix)
if (read.fault !== undefined) return read.fault.message
return { attributes: read.value.attributes, content: read.value.content, length: read.value.length, name: read.value.name }
}
@@ -157,8 +157,3 @@ test('refuses inline directives nested deeper than the parser carries', () => {
spans(nest(largestNesting), 'underline', nest(largestNesting - 1))
assert.equal(inline(nest(largestNesting + 1)), `the input nests inline directives deeper than the ${largestNesting} levels the parser carries`)
})
test('reads a directive whose content holds many unclosed backtick runs in time linear in its length', () => {
const runs = Array.from({ length: 3000 }, (_, index) => '`'.repeat(index + 1)).join(' ')
spans(`!adf:x[${runs}]`, 'x', runs)
})
+3 -2
View File
@@ -1,6 +1,7 @@
import type { AttributeKind, VocabularyPair, VocabularyValue } from '../adf/attribute-vocabulary.ts'
import type { CodeSpanCloser } from './commonmark/backtick-runs.ts'
import type { ConvertFault } from '../result.ts'
import type { InlineSource } from './inline-tokens.ts'
import type { JsonValue } from '../json-value.ts'
import { backslashEscape, holdsNullCharacter } from './commonmark/grammar.ts'
import { backtickRun } from './commonmark/backtick-runs.ts'
@@ -101,8 +102,8 @@ export function readDirectiveLine(line: string): Read<DirectiveLine> | undefined
return line.charAt(directivePrefix.length) === '/' ? readCloserLine(line) : readOpenerLine(line)
}
export function readInlineDirective(text: string, prefix: ClaimedPrefix, codeSpanCloser: CodeSpanCloser): Read<DirectiveSpan> {
return readClaimedDirective(text, prefix.index, 1, codeSpanCloser)
export function readInlineDirective(inline: InlineSource, prefix: ClaimedPrefix): Read<DirectiveSpan> {
return readClaimedDirective(inline.text, prefix.index, 1, inline.codeSpanCloser)
}
export function readSoleStringAttribute(span: DirectiveSpan, key: string): Read<string> {
+2 -1
View File
@@ -1,7 +1,8 @@
import type { ClaimedPrefix } from './directive-syntax.ts'
import type { CodeSpanCloser } from './commonmark/backtick-runs.ts'
import type { Claims } from './portable/conventions.ts'
import { backslashEscape, inlineHtmlConstruct, readBracketedAutolink, readEmailAutolink } from './commonmark/grammar.ts'
import { backtickCloser, backtickRun, type CodeSpanCloser } from './commonmark/backtick-runs.ts'
import { backtickCloser, backtickRun } from './commonmark/backtick-runs.ts'
import { claimDirectivePrefix } from './directive-syntax.ts'
import { delimiterFlags, runLength } from './commonmark/emphasis-matching.ts'
import { highlightDelimiter, highlightFlanking } from './portable/conventions.ts'
+13 -12
View File
@@ -74,7 +74,7 @@ type ContainerStack = {
open: readonly OpenContainer[]
push: (container: OpenContainer) => void
// The index in `edges` of each blockquote.
quotes: readonly number[]
quoteEdges: readonly number[]
}
type Walk = ParsedBlocks & {
@@ -114,7 +114,7 @@ function containerStack(): ContainerStack {
const depthsOf = (name: string): number[] => entryOf(directiveDepths, name, () => [])
const edges: { container: EdgeContainer; depth: number }[] = []
const open: OpenContainer[] = []
const quotes: number[] = []
const quoteEdges: number[] = []
return {
directiveDepth: (name) => directiveDepths.get(name)?.at(-1),
drop: (depth) => {
@@ -123,7 +123,7 @@ function containerStack(): ContainerStack {
if (container.kind === 'directive') depthsOf(container.name).pop()
else {
edges.pop()
if (container.kind === 'blockquote') quotes.pop()
if (container.kind === 'blockquote') quoteEdges.pop()
}
}
return dropped
@@ -134,11 +134,11 @@ function containerStack(): ContainerStack {
const depth = open.push(container) - 1
if (container.kind === 'directive') depthsOf(container.name).push(depth)
else {
if (container.kind === 'blockquote') quotes.push(edges.length)
if (container.kind === 'blockquote') quoteEdges.push(edges.length)
edges.push({ container, depth })
}
},
quotes,
quoteEdges,
}
}
@@ -168,15 +168,15 @@ function swallowsLines(leaf: OpenLeaf | undefined): boolean {
// A directive container has no continuation marker, so every line continues it.
function matchContainers(walk: Walk, line: Line): { depth: number; rest: Line } {
if (isBlankLine(line.text)) return matchBlankLine(walk, line, 0, 0)
let quotes = 0
let quotesPassed = 0
let rest = line
for (const [index, { container, depth }] of walk.stack.edges.entries()) {
const next = continuesContainer(container, rest)
if (next === undefined) return { depth, rest }
rest = next
if (container.kind !== 'blockquote') continue
quotes += 1
if (isBlankLine(rest.text)) return matchBlankLine(walk, rest, index + 1, quotes)
quotesPassed += 1
if (isBlankLine(rest.text)) return matchBlankLine(walk, rest, index + 1, quotesPassed)
}
return { depth: walk.stack.open.length, rest }
}
@@ -184,11 +184,12 @@ function matchContainers(walk: Walk, line: Line): { depth: number; rest: Line }
// A blank line continues every list item up to the next blockquote, so it is matched in one step rather than one per item.
// A list item begins with at most one blank line: an empty one gives the second up. Only the innermost item is empty:
// opening a container inside an item gives it a block.
function matchBlankLine(walk: Walk, line: Line, from: number, quote: number): { depth: number; rest: Line } {
const { edges, quotes } = walk.stack
function matchBlankLine(walk: Walk, line: Line, from: number, quotesPassed: number): { depth: number; rest: Line } {
const { edges, quoteEdges } = walk.stack
const last = edges.length - 1
const emptyItem = last >= from && edges[last]?.container.blocks.length === 0 && walk.leaf === undefined
const stop = Math.min(quotes[quote] ?? edges.length, emptyItem ? last : edges.length)
const innermost = edges[last]?.container
const emptyItem = last >= from && innermost?.kind === 'item' && innermost.blocks.length === 0 && walk.leaf === undefined
const stop = Math.min(quoteEdges[quotesPassed] ?? edges.length, emptyItem ? last : edges.length)
return { depth: edges[stop]?.depth ?? walk.stack.open.length, rest: stop === from ? line : { column: line.column, text: '' } }
}
+1 -1
View File
@@ -173,7 +173,7 @@ function readLineEnding(scan: Scan, index: number): number {
function readDirective(scan: Scan, inline: InlineSource, prefix: ClaimedPrefix): Result<number> {
const held = scan.spans.get(prefix.index)
if (held !== undefined) return pushDirective(scan, held, prefix.index)
const directive = readInlineDirective(scan.source, prefix, inline.codeSpanCloser)
const directive = readInlineDirective(inline, prefix)
if (directive.fault !== undefined) return faulted(directive.fault, scan.path)
return pushDirective(scan, directive.value, prefix.index)
}