10a - adfToPlainMarkdown's ADF→ADF reduction #128
@@ -1,7 +1,7 @@
|
||||
import type { AdfMark, AdfNode } from '../../adf/document.ts'
|
||||
import type { InlineNodeModel } from '../../adf/inline-nodes.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 { claimsLine, holdsNullCharacter, trimTrailingSpace } from '../commonmark/grammar.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 }
|
||||
|
||||
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> {
|
||||
const emitted = emitLine(nodes, container, path)
|
||||
if (!emitted.ok) return emitted
|
||||
@@ -47,6 +49,17 @@ export function openingLinkTakesDirective(nodes: readonly AdfNode[], path: Conve
|
||||
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 {
|
||||
const emitted = emitLine(nodes, 'table-cell', path)
|
||||
if (!emitted.ok) return undefined
|
||||
@@ -108,14 +121,19 @@ function attemptLine(segments: readonly InlineSegment[], container: LineContaine
|
||||
const assembled = assembleInlineLine(segments, container)
|
||||
if (assembled.openingLinkAsDirective) return success({ fallback: 'opening-link' })
|
||||
if (assembled.unspellableRun !== undefined) return success({ fallback: assembled.unspellableRun })
|
||||
for (const [index, single] of assembled.line.split('\n').entries()) {
|
||||
if (container === 'paragraph' && claimsLine(single, index === 0 ? 'first' : 'later')) {
|
||||
return failure('unspellable-line-start', `block parsing would claim the emitted line ${JSON.stringify(single)}`, path)
|
||||
}
|
||||
const claimed = claimedLine(assembled.line, container)
|
||||
if (claimed !== undefined) {
|
||||
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 })
|
||||
}
|
||||
|
||||
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.
|
||||
function carryStrippedWhitespace(segments: readonly InlineSegment[]): InlineSegment[] {
|
||||
const carried: InlineSegment[] = []
|
||||
@@ -275,9 +293,9 @@ function emitEmphasis(nodes: readonly AdfNode[], spelling: string, depth: number
|
||||
const carried = carryStrippedWhitespace(inner.value.segments)
|
||||
return success({
|
||||
segments: [
|
||||
{ emphasis: 'open', escaping: 'none', nodes: range, text: spelling },
|
||||
{ emphasis: 'open', escaping: 'none', nodes: { ...range, depth }, text: spelling },
|
||||
...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 MarkRun = NodeRange & { depth: number }
|
||||
|
||||
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: 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 }
|
||||
|
||||
@@ -132,7 +134,7 @@ function escapeClosedRuns(scan: string, escapings: readonly InlineEscaping[], cl
|
||||
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 pair = misflanked(runs) ?? unpaired(runs)
|
||||
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)
|
||||
}
|
||||
|
||||
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 nodes: NodeRange[] = []
|
||||
const nodes: MarkRun[] = []
|
||||
const open: number[] = []
|
||||
let cursor = 0
|
||||
for (const segment of segments) {
|
||||
|
||||
Reference in New Issue
Block a user