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:
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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: '' } }
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user