4c - the scanning rule's remaining sites #99
@@ -17,7 +17,10 @@ export type DirectiveLine =
|
|||||||
| { argument: string | undefined; attributes: DirectiveAttributes; kind: 'opener'; name: string }
|
| { argument: string | undefined; attributes: DirectiveAttributes; kind: 'opener'; name: string }
|
||||||
| { kind: 'closer'; name: string }
|
| { kind: 'closer'; name: string }
|
||||||
|
|
||||||
export type DirectiveSpan = { attributes: DirectiveAttributes; content: string | undefined; length: number; name: string }
|
// spans: the directives the content holds, at their offset in it, so reading the slot back never scans them again.
|
||||||
|
export type DirectiveSpan = { attributes: DirectiveAttributes; content: string | undefined; length: number; name: string; spans: NestedSpans }
|
||||||
|
|
||||||
|
export type NestedSpans = ReadonlyMap<number, DirectiveSpan>
|
||||||
|
|
||||||
export type Read<T> = { fault: ConvertFault; value?: undefined } | { fault?: undefined; value: T }
|
export type Read<T> = { fault: ConvertFault; value?: undefined } | { fault?: undefined; value: T }
|
||||||
|
|
||||||
@@ -25,7 +28,9 @@ type Attributes = { attributes: DirectiveAttributes; end: number }
|
|||||||
|
|
||||||
type AttributePair = { end: number; key: string; value: DirectiveValue }
|
type AttributePair = { end: number; key: string; value: DirectiveValue }
|
||||||
|
|
||||||
type Content = { content: string | undefined; end: number }
|
type Content = { content: string | undefined; end: number; spans: NestedSpans }
|
||||||
|
|
||||||
|
type DirectiveContent = { end: number; spans: NestedSpans }
|
||||||
|
|
||||||
export const directivePrefix = '!adf:'
|
export const directivePrefix = '!adf:'
|
||||||
|
|
||||||
@@ -41,6 +46,8 @@ const quotedEscapes = new RegExp(reservedSource, 'g')
|
|||||||
const rawReserved = new RegExp(reservedSource)
|
const rawReserved = new RegExp(reservedSource)
|
||||||
const noAttributes: DirectiveAttributes = new Map()
|
const noAttributes: DirectiveAttributes = new Map()
|
||||||
|
|
||||||
|
export const noSpans: NestedSpans = new Map()
|
||||||
|
|
||||||
export const directiveEscape = `\\${directivePrefix} keeps the prefix literal`
|
export const directiveEscape = `\\${directivePrefix} keeps the prefix literal`
|
||||||
|
|
||||||
const closerFault = `a closer carries nothing after its name: this one does; ${directiveEscape}`
|
const closerFault = `a closer carries nothing after its name: this one does; ${directiveEscape}`
|
||||||
@@ -213,14 +220,14 @@ function readNestedDirective(text: string, index: number, depth: number): Read<D
|
|||||||
if (slot.fault !== undefined) return { fault: slot.fault }
|
if (slot.fault !== undefined) return { fault: slot.fault }
|
||||||
const attributes = readAttributesAt(text, slot.value.end, slot.value.content === undefined)
|
const attributes = readAttributesAt(text, slot.value.end, slot.value.content === undefined)
|
||||||
if (attributes.fault !== undefined) return { fault: attributes.fault }
|
if (attributes.fault !== undefined) return { fault: attributes.fault }
|
||||||
return { value: { attributes: attributes.value.attributes, content: slot.value.content, length: attributes.value.end - index, name } }
|
return { value: { attributes: attributes.value.attributes, content: slot.value.content, length: attributes.value.end - index, name, spans: slot.value.spans } }
|
||||||
}
|
}
|
||||||
|
|
||||||
function readContentSlot(text: string, index: number, depth: number): Read<Content> {
|
function readContentSlot(text: string, index: number, depth: number): Read<Content> {
|
||||||
if (text.charAt(index) !== '[') return { value: { content: undefined, end: index } }
|
if (text.charAt(index) !== '[') return { value: { content: undefined, end: index, spans: noSpans } }
|
||||||
const close = readDirectiveContent(text, index + 1, depth)
|
const close = readDirectiveContent(text, index + 1, depth)
|
||||||
if (close.fault !== undefined) return { fault: close.fault }
|
if (close.fault !== undefined) return { fault: close.fault }
|
||||||
return { value: { content: text.slice(index + 1, close.value), end: close.value + 1 } }
|
return { value: { content: text.slice(index + 1, close.value.end), end: close.value.end + 1, spans: close.value.spans } }
|
||||||
}
|
}
|
||||||
|
|
||||||
function readAttributesAt(text: string, index: number, braceClaims: boolean): Read<Attributes> {
|
function readAttributesAt(text: string, index: number, braceClaims: boolean): Read<Attributes> {
|
||||||
@@ -231,7 +238,8 @@ function readAttributesAt(text: string, index: number, braceClaims: boolean): Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A code span, an escape and a nested directive each bind before the content's own closing bracket.
|
// A code span, an escape and a nested directive each bind before the content's own closing bracket.
|
||||||
function readDirectiveContent(text: string, start: number, depth: number): Read<number> {
|
function readDirectiveContent(text: string, start: number, depth: number): Read<DirectiveContent> {
|
||||||
|
const spans = new Map<number, DirectiveSpan>()
|
||||||
let brackets = 0
|
let brackets = 0
|
||||||
let cursor = start
|
let cursor = start
|
||||||
while (cursor < text.length && text.charAt(cursor) !== '\n') {
|
while (cursor < text.length && text.charAt(cursor) !== '\n') {
|
||||||
@@ -246,13 +254,13 @@ function readDirectiveContent(text: string, start: number, depth: number): Read<
|
|||||||
cursor = span
|
cursor = span
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const nested = readNestedDirective(text, cursor, depth + 1)
|
const nested = keepNestedSpan(text, cursor, depth, start, spans)
|
||||||
|
if (nested?.fault !== undefined) return { fault: nested.fault }
|
||||||
if (nested !== undefined) {
|
if (nested !== undefined) {
|
||||||
if (nested.fault !== undefined) return { fault: nested.fault }
|
cursor = nested.value
|
||||||
cursor += nested.value.length
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (character === ']' && brackets === 0) return { value: cursor }
|
if (character === ']' && brackets === 0) return { value: { end: cursor, spans } }
|
||||||
if (character === '[') brackets += 1
|
if (character === '[') brackets += 1
|
||||||
if (character === ']') brackets -= 1
|
if (character === ']') brackets -= 1
|
||||||
cursor += 1
|
cursor += 1
|
||||||
@@ -260,6 +268,14 @@ function readDirectiveContent(text: string, start: number, depth: number): Read<
|
|||||||
return { fault: malformedDirective(`an inline directive [content] is unclosed; ${directiveEscape}`) }
|
return { fault: malformedDirective(`an inline directive [content] is unclosed; ${directiveEscape}`) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function keepNestedSpan(text: string, cursor: number, depth: number, start: number, spans: Map<number, DirectiveSpan>): Read<number> | undefined {
|
||||||
|
const nested = readNestedDirective(text, cursor, depth + 1)
|
||||||
|
if (nested === undefined) return undefined
|
||||||
|
if (nested.fault !== undefined) return { fault: nested.fault }
|
||||||
|
spans.set(cursor - start, nested.value)
|
||||||
|
return { value: cursor + nested.value.length }
|
||||||
|
}
|
||||||
|
|
||||||
// `undefined` where the span crosses the line ending an inline directive may not cross.
|
// `undefined` where the span crosses the line ending an inline directive may not cross.
|
||||||
function readCodeSpanEnd(text: string, index: number): number | undefined {
|
function readCodeSpanEnd(text: string, index: number): number | undefined {
|
||||||
const opener = backtickRun(text, index)
|
const opener = backtickRun(text, index)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { AdfMark, AdfNode } from '../../adf/document.ts'
|
import type { AdfMark, AdfNode } from '../../adf/document.ts'
|
||||||
import type { DirectiveSpan } from '../directive-syntax.ts'
|
import type { DirectiveSpan, NestedSpans } from '../directive-syntax.ts'
|
||||||
import type { EmphasisPairing } from '../emphasis-matching.ts'
|
import type { EmphasisPairing } from '../emphasis-matching.ts'
|
||||||
import type { LineContainer } from '../emit/line-escaping.ts'
|
import type { LineContainer } from '../emit/line-escaping.ts'
|
||||||
import type { LinkDefinition } from '../link-syntax.ts'
|
import type { LinkDefinition } from '../link-syntax.ts'
|
||||||
@@ -15,7 +15,7 @@ import { normalizeLabel, readInlineTarget, readLabel } from '../link-syntax.ts'
|
|||||||
import { openingLinkTakesDirective } from '../emit/inline-line.ts'
|
import { openingLinkTakesDirective } from '../emit/inline-line.ts'
|
||||||
import { readCarriedInline } from '../opaque-carry.ts'
|
import { readCarriedInline } from '../opaque-carry.ts'
|
||||||
import { readDirectiveMark } from './directive-marks.ts'
|
import { readDirectiveMark } from './directive-marks.ts'
|
||||||
import { readInlineDirective } from '../directive-syntax.ts'
|
import { noSpans, readInlineDirective } from '../directive-syntax.ts'
|
||||||
import { readInlineDirectiveNode } from './directive-nodes.ts'
|
import { readInlineDirectiveNode } from './directive-nodes.ts'
|
||||||
import { readTextDirective } from '../text-directive.ts'
|
import { readTextDirective } from '../text-directive.ts'
|
||||||
|
|
||||||
@@ -45,6 +45,7 @@ type Scan = {
|
|||||||
pending: string
|
pending: string
|
||||||
pieces: Piece[]
|
pieces: Piece[]
|
||||||
source: string
|
source: string
|
||||||
|
spans: NestedSpans
|
||||||
}
|
}
|
||||||
|
|
||||||
type SlotContent = { carry: boolean; nodes: AdfNode[] }
|
type SlotContent = { carry: boolean; nodes: AdfNode[] }
|
||||||
@@ -54,11 +55,11 @@ const imageAlone = 'an image fits only as a paragraph of its own: this one sits
|
|||||||
const spellableLink = 'link takes the directive form only where CommonMark cannot spell it: this one it can, as [text](url "title") or <url>'
|
const spellableLink = 'link takes the directive form only where CommonMark cannot spell it: this one it can, as [text](url "title") or <url>'
|
||||||
|
|
||||||
export function parseInlineContent(source: string, definitions: LinkDefinitions, path: ConvertErrorPath, container: LineContainer): Result<InlineContent> {
|
export function parseInlineContent(source: string, definitions: LinkDefinitions, path: ConvertErrorPath, container: LineContainer): Result<InlineContent> {
|
||||||
return parseInline(source, definitions, path, container)
|
return parseInline(source, definitions, path, container, noSpans)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseInline(source: string, definitions: LinkDefinitions, path: ConvertErrorPath, container: LineContainer | undefined): Result<InlineContent> {
|
function parseInline(source: string, definitions: LinkDefinitions, path: ConvertErrorPath, container: LineContainer | undefined, spans: NestedSpans): Result<InlineContent> {
|
||||||
const scan: Scan = { container, definitions, openingSpellableLink: false, path, pending: '', pieces: [], source }
|
const scan: Scan = { container, definitions, openingSpellableLink: false, path, pending: '', pieces: [], source, spans }
|
||||||
let index = 0
|
let index = 0
|
||||||
while (index < source.length) {
|
while (index < source.length) {
|
||||||
switch (source.charAt(index)) {
|
switch (source.charAt(index)) {
|
||||||
@@ -168,14 +169,20 @@ function openBracket(scan: Scan, index: number): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function readDirective(scan: Scan, index: number): Result<number> | undefined {
|
function readDirective(scan: Scan, index: number): Result<number> | undefined {
|
||||||
|
const held = scan.spans.get(index)
|
||||||
|
if (held !== undefined) return pushDirective(scan, held, index)
|
||||||
const directive = readInlineDirective(scan.source, index)
|
const directive = readInlineDirective(scan.source, index)
|
||||||
if (directive === undefined) return undefined
|
if (directive === undefined) return undefined
|
||||||
if (directive.fault !== undefined) return faulted(directive.fault, scan.path)
|
if (directive.fault !== undefined) return faulted(directive.fault, scan.path)
|
||||||
const piece = directivePiece(scan, directive.value, index)
|
return pushDirective(scan, directive.value, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushDirective(scan: Scan, span: DirectiveSpan, index: number): Result<number> {
|
||||||
|
const piece = directivePiece(scan, span, index)
|
||||||
if (!piece.ok) return piece
|
if (!piece.ok) return piece
|
||||||
flush(scan, false)
|
flush(scan, false)
|
||||||
scan.pieces.push(piece.value)
|
scan.pieces.push(piece.value)
|
||||||
return success(index + directive.value.length)
|
return success(index + span.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
function directivePiece(scan: Scan, span: DirectiveSpan, index: number): Result<Piece> {
|
function directivePiece(scan: Scan, span: DirectiveSpan, index: number): Result<Piece> {
|
||||||
@@ -187,7 +194,7 @@ function directivePiece(scan: Scan, span: DirectiveSpan, index: number): Result<
|
|||||||
const text = readTextDirective(span)
|
const text = readTextDirective(span)
|
||||||
if (text?.fault !== undefined) return faulted(text.fault, scan.path)
|
if (text?.fault !== undefined) return faulted(text.fault, scan.path)
|
||||||
if (text !== undefined) return success({ kind: 'nodes', nodes: [{ text: text.value, type: 'text' }] })
|
if (text !== undefined) return success({ kind: 'nodes', nodes: [{ text: text.value, type: 'text' }] })
|
||||||
const slot = slotContent(scan, span.content)
|
const slot = slotContent(scan, span)
|
||||||
if (!slot.ok) return slot
|
if (!slot.ok) return slot
|
||||||
const mark = readDirectiveMark(span.name, span.attributes, scan.path)
|
const mark = readDirectiveMark(span.name, span.attributes, scan.path)
|
||||||
if (mark !== undefined) return mark.ok ? directiveMarkPiece(scan, span.name, mark.value, slot.value, index) : mark
|
if (mark !== undefined) return mark.ok ? directiveMarkPiece(scan, span.name, mark.value, slot.value, index) : mark
|
||||||
@@ -216,9 +223,9 @@ function refuseSpellableLink(scan: Scan, mark: AdfMark, nodes: readonly AdfNode[
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function slotContent(scan: Scan, content: string | undefined): Result<SlotContent | undefined> {
|
function slotContent(scan: Scan, span: DirectiveSpan): Result<SlotContent | undefined> {
|
||||||
if (content === undefined) return success(undefined)
|
if (span.content === undefined) return success(undefined)
|
||||||
const parsed = parseInline(content, scan.definitions, scan.path, undefined)
|
const parsed = parseInline(span.content, scan.definitions, scan.path, undefined, span.spans)
|
||||||
if (!parsed.ok) return parsed
|
if (!parsed.ok) return parsed
|
||||||
if (parsed.value.image !== undefined) return failure('unmappable-image', imageAlone, scan.path)
|
if (parsed.value.image !== undefined) return failure('unmappable-image', imageAlone, scan.path)
|
||||||
return success(parsed.value)
|
return success(parsed.value)
|
||||||
|
|||||||
Reference in New Issue
Block a user