10a - adfToPlainMarkdown's ADF→ADF reduction #128
@@ -1,7 +1,7 @@
|
|||||||
import type { AdfMark, AdfNode } from '../../adf/document.ts'
|
import type { AdfMark, AdfNode } from '../../adf/document.ts'
|
||||||
import type { InlineNodeModel } from '../../adf/inline-nodes.ts'
|
import type { InlineNodeModel } from '../../adf/inline-nodes.ts'
|
||||||
import type { LineContainer } from '../line-container.ts'
|
import type { LineContainer } from '../line-container.ts'
|
||||||
import { assembleInlineLine, isSyntax, type InlineEscaping, type InlineSegment, type NodeRange } from './line-escaping.ts'
|
import { assembleInlineLine, isSyntax, type InlineEscaping, type InlineSegment, type MarkRun, type NodeRange } from './line-escaping.ts'
|
||||||
import { carriedInline } from '../opaque-carry.ts'
|
import { carriedInline } from '../opaque-carry.ts'
|
||||||
import { claimsLine, holdsNullCharacter, trimTrailingSpace } from '../commonmark/grammar.ts'
|
import { claimsLine, holdsNullCharacter, trimTrailingSpace } from '../commonmark/grammar.ts'
|
||||||
import { commonMarkLink, linkHref, markSpelling, spellMarkAttributes } from '../mark-spellings.ts'
|
import { commonMarkLink, linkHref, markSpelling, spellMarkAttributes } from '../mark-spellings.ts'
|
||||||
@@ -35,6 +35,8 @@ type LineAttempt = { fallback: NodeRange | 'opening-link'; line?: undefined } |
|
|||||||
|
|
||||||
type LineFallbacks = { carried: Set<number>; openingLinkAsDirective: boolean }
|
type LineFallbacks = { carried: Set<number>; openingLinkAsDirective: boolean }
|
||||||
|
|
||||||
|
export type PlainLineFallback = { kind: 'claimed-line'; line: number } | { kind: 'opening-link' } | { kind: 'unspellable-run'; run: MarkRun }
|
||||||
|
|
||||||
export function emitInlineLine(nodes: readonly AdfNode[], container: LineContainer, path: ConvertErrorPath): Result<string> {
|
export function emitInlineLine(nodes: readonly AdfNode[], container: LineContainer, path: ConvertErrorPath): Result<string> {
|
||||||
const emitted = emitLine(nodes, container, path)
|
const emitted = emitLine(nodes, container, path)
|
||||||
if (!emitted.ok) return emitted
|
if (!emitted.ok) return emitted
|
||||||
@@ -47,6 +49,17 @@ export function openingLinkTakesDirective(nodes: readonly AdfNode[], path: Conve
|
|||||||
return success(emitted.value.openingLinkAsDirective)
|
return success(emitted.value.openingLinkAsDirective)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function plainLineFallback(nodes: readonly AdfNode[], container: LineContainer, path: ConvertErrorPath): Result<PlainLineFallback | undefined> {
|
||||||
|
const emission = lineSegments(nodes, container, path, { carried: new Set(), openingLinkAsDirective: false })
|
||||||
|
if (!emission.ok) return emission
|
||||||
|
if (emission.value.carry !== undefined) return failure('unsupported-node-shape', 'an inline node on a plain line has no spelling but the carry', path)
|
||||||
|
const assembled = assembleInlineLine(emission.value.segments, container)
|
||||||
|
if (assembled.openingLinkAsDirective) return success({ kind: 'opening-link' })
|
||||||
|
if (assembled.unspellableRun !== undefined) return success({ kind: 'unspellable-run', run: assembled.unspellableRun })
|
||||||
|
const claimed = claimedLine(assembled.line, container)
|
||||||
|
return success(claimed === undefined ? undefined : { kind: 'claimed-line', line: claimed })
|
||||||
|
}
|
||||||
|
|
||||||
export function tryPipeCell(nodes: readonly AdfNode[], path: ConvertErrorPath): string | undefined {
|
export function tryPipeCell(nodes: readonly AdfNode[], path: ConvertErrorPath): string | undefined {
|
||||||
const emitted = emitLine(nodes, 'table-cell', path)
|
const emitted = emitLine(nodes, 'table-cell', path)
|
||||||
if (!emitted.ok) return undefined
|
if (!emitted.ok) return undefined
|
||||||
@@ -108,14 +121,19 @@ function attemptLine(segments: readonly InlineSegment[], container: LineContaine
|
|||||||
const assembled = assembleInlineLine(segments, container)
|
const assembled = assembleInlineLine(segments, container)
|
||||||
if (assembled.openingLinkAsDirective) return success({ fallback: 'opening-link' })
|
if (assembled.openingLinkAsDirective) return success({ fallback: 'opening-link' })
|
||||||
if (assembled.unspellableRun !== undefined) return success({ fallback: assembled.unspellableRun })
|
if (assembled.unspellableRun !== undefined) return success({ fallback: assembled.unspellableRun })
|
||||||
for (const [index, single] of assembled.line.split('\n').entries()) {
|
const claimed = claimedLine(assembled.line, container)
|
||||||
if (container === 'paragraph' && claimsLine(single, index === 0 ? 'first' : 'later')) {
|
if (claimed !== undefined) {
|
||||||
return failure('unspellable-line-start', `block parsing would claim the emitted line ${JSON.stringify(single)}`, path)
|
return failure('unspellable-line-start', `block parsing would claim the emitted line ${JSON.stringify(assembled.line.split('\n')[claimed])}`, path)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return success({ line: assembled.line })
|
return success({ line: assembled.line })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function claimedLine(line: string, container: LineContainer): number | undefined {
|
||||||
|
if (container !== 'paragraph') return undefined
|
||||||
|
const index = line.split('\n').findIndex((single, index) => claimsLine(single, index === 0 ? 'first' : 'later'))
|
||||||
|
return index === -1 ? undefined : index
|
||||||
|
}
|
||||||
|
|
||||||
// spec/flavour.md, Inline nodes.
|
// spec/flavour.md, Inline nodes.
|
||||||
function carryStrippedWhitespace(segments: readonly InlineSegment[]): InlineSegment[] {
|
function carryStrippedWhitespace(segments: readonly InlineSegment[]): InlineSegment[] {
|
||||||
const carried: InlineSegment[] = []
|
const carried: InlineSegment[] = []
|
||||||
@@ -275,9 +293,9 @@ function emitEmphasis(nodes: readonly AdfNode[], spelling: string, depth: number
|
|||||||
const carried = carryStrippedWhitespace(inner.value.segments)
|
const carried = carryStrippedWhitespace(inner.value.segments)
|
||||||
return success({
|
return success({
|
||||||
segments: [
|
segments: [
|
||||||
{ emphasis: 'open', escaping: 'none', nodes: range, text: spelling },
|
{ emphasis: 'open', escaping: 'none', nodes: { ...range, depth }, text: spelling },
|
||||||
...carried,
|
...carried,
|
||||||
{ emphasis: 'close', escaping: 'none', nodes: range, text: spelling },
|
{ emphasis: 'close', escaping: 'none', nodes: { ...range, depth }, text: spelling },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ export type InlineEscaping = 'backslash' | 'bracketed' | 'bracketed-link-target'
|
|||||||
|
|
||||||
export type NodeRange = { first: number; last: number }
|
export type NodeRange = { first: number; last: number }
|
||||||
|
|
||||||
|
export type MarkRun = NodeRange & { depth: number }
|
||||||
|
|
||||||
export type InlineSegment =
|
export type InlineSegment =
|
||||||
| { emphasis: EmphasisRole; escaping: 'none'; nodes: NodeRange; text: string }
|
| { emphasis: EmphasisRole; escaping: 'none'; nodes: MarkRun; text: string }
|
||||||
| { emphasis?: undefined; escaping: 'none'; nodes: NodeRange; text: string }
|
| { emphasis?: undefined; escaping: 'none'; nodes: NodeRange; text: string }
|
||||||
| { emphasis?: undefined; escaping: InlineEscaping; nodes?: undefined; text: string }
|
| { emphasis?: undefined; escaping: InlineEscaping; nodes?: undefined; text: string }
|
||||||
|
|
||||||
export type AssembledLine = { line: string; openingLinkAsDirective?: true; unspellableRun: NodeRange | undefined }
|
export type AssembledLine = { line: string; openingLinkAsDirective?: true; unspellableRun: MarkRun | undefined }
|
||||||
|
|
||||||
type ScanLine = { position: LinePosition; start: number; text: string }
|
type ScanLine = { position: LinePosition; start: number; text: string }
|
||||||
|
|
||||||
@@ -132,7 +134,7 @@ function escapeClosedRuns(scan: string, escapings: readonly InlineEscaping[], cl
|
|||||||
return escaped
|
return escaped
|
||||||
}
|
}
|
||||||
|
|
||||||
function unspellableRun(segments: readonly InlineSegment[], output: string, placements: readonly number[]): NodeRange | undefined {
|
function unspellableRun(segments: readonly InlineSegment[], output: string, placements: readonly number[]): MarkRun | undefined {
|
||||||
const { nodes, runs } = emittedRuns(segments, placements, output)
|
const { nodes, runs } = emittedRuns(segments, placements, output)
|
||||||
const pair = misflanked(runs) ?? unpaired(runs)
|
const pair = misflanked(runs) ?? unpaired(runs)
|
||||||
return pair === undefined ? undefined : nodes[pair]
|
return pair === undefined ? undefined : nodes[pair]
|
||||||
@@ -168,9 +170,9 @@ function delimiterAt(run: EmittedRun, closes: boolean, offset: number, width: nu
|
|||||||
return run.delimiters.find((delimiter) => delimiter.closes === closes && delimiter.offset === offset && delimiter.width === width)
|
return run.delimiters.find((delimiter) => delimiter.closes === closes && delimiter.offset === offset && delimiter.width === width)
|
||||||
}
|
}
|
||||||
|
|
||||||
function emittedRuns(segments: readonly InlineSegment[], placements: readonly number[], output: string): { nodes: NodeRange[]; runs: EmittedRun[] } {
|
function emittedRuns(segments: readonly InlineSegment[], placements: readonly number[], output: string): { nodes: MarkRun[]; runs: EmittedRun[] } {
|
||||||
const runs: EmittedRun[] = []
|
const runs: EmittedRun[] = []
|
||||||
const nodes: NodeRange[] = []
|
const nodes: MarkRun[] = []
|
||||||
const open: number[] = []
|
const open: number[] = []
|
||||||
let cursor = 0
|
let cursor = 0
|
||||||
for (const segment of segments) {
|
for (const segment of segments) {
|
||||||
|
|||||||
Reference in New Issue
Block a user