21 - the ADF tables carry ADF's nouns #119

Merged
lilleman merged 3 commits from item-21 into main 2026-09-21 22:05:17 +02:00
16 changed files with 111 additions and 102 deletions
Showing only changes of commit c2acee7a1d - Show all commits
+4 -4
View File
@@ -7,8 +7,8 @@ import test from 'node:test'
import type { AttributeKind, AttributeVocabulary } from './adf/attribute-vocabulary.ts' import type { AttributeKind, AttributeVocabulary } from './adf/attribute-vocabulary.ts'
import { blockArgument } from './markdown/block-directive-arguments.ts' import { blockArgument } from './markdown/block-directive-arguments.ts'
import { blockDirectives } from './adf/block-directives.ts' import { blockNodes } from './adf/block-nodes.ts'
import { inlineDirectives } from './adf/inline-directives.ts' import { inlineNodes } from './adf/inline-nodes.ts'
import { markAttributes } from './adf/mark-attributes.ts' import { markAttributes } from './adf/mark-attributes.ts'
type Held = Map<string, Set<AttributeKind>> type Held = Map<string, Set<AttributeKind>>
@@ -78,8 +78,8 @@ test("the ADF JSON Schemas hold no type the tables leave unspelled, the pinned c
function spelled(): Spelled[] { function spelled(): Spelled[] {
return [ return [
...Object.entries(blockDirectives).map(([type, entry]) => spelledType(type, entry.attributes, blockArgument(type))), ...Object.entries(blockNodes).map(([type, entry]) => spelledType(type, entry.attributes, blockArgument(type))),
...Object.entries(inlineDirectives).map(([type, entry]) => spelledType(type, entry.attributes)), ...Object.entries(inlineNodes).map(([type, entry]) => spelledType(type, entry.attributes)),
...Object.entries(markAttributes).map(([type, attributes]) => spelledType(type, attributes)), ...Object.entries(markAttributes).map(([type, attributes]) => spelledType(type, attributes)),
] ]
} }
@@ -1,6 +1,6 @@
import type { AttributeVocabulary } from './attribute-vocabulary.ts' import type { AttributeVocabulary } from './attribute-vocabulary.ts'
export type BlockDirective = { export type BlockNode = {
attributes: AttributeVocabulary attributes: AttributeVocabulary
contentModel: 'block' | 'code' | 'inline' | 'none' contentModel: 'block' | 'code' | 'inline' | 'none'
} }
@@ -41,7 +41,7 @@ const mediaAttributes: AttributeVocabulary = {
const syncBlockAttributes: AttributeVocabulary = { localId: 'string', resourceId: 'string' } const syncBlockAttributes: AttributeVocabulary = { localId: 'string', resourceId: 'string' }
export const blockDirectives = { export const blockNodes = {
blockTaskItem: { attributes: localIdAttributes, contentModel: 'block' }, blockTaskItem: { attributes: localIdAttributes, contentModel: 'block' },
blockquote: { attributes: localIdAttributes, contentModel: 'block' }, blockquote: { attributes: localIdAttributes, contentModel: 'block' },
bodiedExtension: { attributes: extensionAttributes, contentModel: 'block' }, bodiedExtension: { attributes: extensionAttributes, contentModel: 'block' },
@@ -80,14 +80,14 @@ export const blockDirectives = {
tableRow: { attributes: localIdAttributes, contentModel: 'block' }, tableRow: { attributes: localIdAttributes, contentModel: 'block' },
taskItem: { attributes: localIdAttributes, contentModel: 'inline' }, taskItem: { attributes: localIdAttributes, contentModel: 'inline' },
taskList: { attributes: localIdAttributes, contentModel: 'block' }, taskList: { attributes: localIdAttributes, contentModel: 'block' },
} satisfies Readonly<Record<string, BlockDirective>> } satisfies Readonly<Record<string, BlockNode>>
export type BlockType = keyof typeof blockDirectives export type BlockType = keyof typeof blockNodes
export function blockDirective(type: string): BlockDirective | undefined { export function blockNodeNamed(type: string): BlockNode | undefined {
return isBlockType(type) ? blockDirectives[type] : undefined return isBlockType(type) ? blockNodes[type] : undefined
} }
function isBlockType(type: string): type is BlockType { function isBlockType(type: string): type is BlockType {
return Object.hasOwn(blockDirectives, type) return Object.hasOwn(blockNodes, type)
} }
@@ -1,11 +1,11 @@
import type { AttributeVocabulary } from './attribute-vocabulary.ts' import type { AttributeVocabulary } from './attribute-vocabulary.ts'
export type InlineDirective = { export type InlineNode = {
attributes: AttributeVocabulary attributes: AttributeVocabulary
textAttribute?: string textAttribute?: string
} }
export const inlineDirectives: Readonly<Record<string, InlineDirective>> = { export const inlineNodes: Readonly<Record<string, InlineNode>> = {
date: { attributes: { localId: 'string', timestamp: 'string' } }, date: { attributes: { localId: 'string', timestamp: 'string' } },
emoji: { attributes: { id: 'string', localId: 'string', shortName: 'string', text: 'string' }, textAttribute: 'text' }, emoji: { attributes: { id: 'string', localId: 'string', shortName: 'string', text: 'string' }, textAttribute: 'text' },
hardBreak: { attributes: { localId: 'string', text: 'string' } }, hardBreak: { attributes: { localId: 'string', text: 'string' } },
@@ -27,6 +27,6 @@ export const inlineDirectives: Readonly<Record<string, InlineDirective>> = {
status: { attributes: { color: 'string', localId: 'string', style: 'string', text: 'string' }, textAttribute: 'text' }, status: { attributes: { color: 'string', localId: 'string', style: 'string', text: 'string' }, textAttribute: 'text' },
} }
export function inlineDirective(type: string): InlineDirective | undefined { export function inlineNodeNamed(type: string): InlineNode | undefined {
return Object.hasOwn(inlineDirectives, type) ? inlineDirectives[type] : undefined return Object.hasOwn(inlineNodes, type) ? inlineNodes[type] : undefined
} }
+5 -5
View File
@@ -5,8 +5,8 @@ import { readFileSync } from 'node:fs'
import test from 'node:test' import test from 'node:test'
import type { AttributeKind, AttributeVocabulary } from './adf/attribute-vocabulary.ts' import type { AttributeKind, AttributeVocabulary } from './adf/attribute-vocabulary.ts'
import { blockDirectives } from './adf/block-directives.ts' import { blockNodes } from './adf/block-nodes.ts'
import { inlineDirectives } from './adf/inline-directives.ts' import { inlineNodes } from './adf/inline-nodes.ts'
import { markAttributes } from './adf/mark-attributes.ts' import { markAttributes } from './adf/mark-attributes.ts'
import { textDirectiveName } from './markdown/text-directive.ts' import { textDirectiveName } from './markdown/text-directive.ts'
@@ -90,16 +90,16 @@ function vocabularies(table: Readonly<Record<string, Declared>>): Record<string,
} }
test('the block node table holds the attributes spec/flavour.md gives each node', () => { test('the block node table holds the attributes spec/flavour.md gives each node', () => {
assert.deepEqual(declarations('Block nodes'), vocabularies(blockDirectives)) assert.deepEqual(declarations('Block nodes'), vocabularies(blockNodes))
}) })
test('the inline node table holds the attributes spec/flavour.md gives each node', () => { test('the inline node table holds the attributes spec/flavour.md gives each node', () => {
assert.deepEqual(declarations('Inline nodes'), vocabularies(inlineDirectives)) assert.deepEqual(declarations('Inline nodes'), vocabularies(inlineNodes))
}) })
// A name in two tables would make the position a directive is read in ambiguous. // A name in two tables would make the position a directive is read in ambiguous.
test('no name is spelled in more than one position', () => { test('no name is spelled in more than one position', () => {
const names = [...Object.keys(blockDirectives), ...Object.keys(inlineDirectives), ...Object.keys(markAttributes), textDirectiveName] const names = [...Object.keys(blockNodes), ...Object.keys(inlineNodes), ...Object.keys(markAttributes), textDirectiveName]
assert.equal(new Set(names).size, names.length) assert.equal(new Set(names).size, names.length)
}) })
+11 -11
View File
@@ -10,7 +10,7 @@ import type { Result } from './result.ts'
import { adfDocument, attributes, jsonKey, jsonValue, markdownPieces, propertyRuns, propertyTimeout, textOf } from './property-harness.ts' import { adfDocument, attributes, jsonKey, jsonValue, markdownPieces, propertyRuns, propertyTimeout, textOf } from './property-harness.ts'
import { adfToMarkdown } from './markdown/emit/adf-to-markdown.ts' import { adfToMarkdown } from './markdown/emit/adf-to-markdown.ts'
import { blockArgument } from './markdown/block-directive-arguments.ts' import { blockArgument } from './markdown/block-directive-arguments.ts'
import { blockDirectives } from './adf/block-directives.ts' import { blockNodes } from './adf/block-nodes.ts'
import { carryName } from './markdown/opaque-carry.ts' import { carryName } from './markdown/opaque-carry.ts'
import { import {
directivePrefix, directivePrefix,
@@ -24,7 +24,7 @@ import {
spellVocabulary, spellVocabulary,
} from './markdown/directive-syntax.ts' } from './markdown/directive-syntax.ts'
import { fencedCodeBlock } from './markdown/commonmark/backtick-runs.ts' import { fencedCodeBlock } from './markdown/commonmark/backtick-runs.ts'
import { inlineDirectives } from './adf/inline-directives.ts' import { inlineNodes } from './adf/inline-nodes.ts'
import { listBreakName } from './markdown/list-break.ts' import { listBreakName } from './markdown/list-break.ts'
import { markAttributes } from './adf/mark-attributes.ts' import { markAttributes } from './adf/mark-attributes.ts'
import { markSpelling } from './markdown/mark-spellings.ts' import { markSpelling } from './markdown/mark-spellings.ts'
@@ -50,11 +50,11 @@ const fixpointFloor = 600
const gateRuns = 1000 const gateRuns = 1000
const markdownMarkTypes = new Set(Object.keys(markAttributes).filter((type) => markSpelling(type)?.kind !== 'directive')) const markdownMarkTypes = new Set(Object.keys(markAttributes).filter((type) => markSpelling(type)?.kind !== 'directive'))
const vocabularies = [...Object.values(blockDirectives).map((directive) => directive.attributes), ...Object.values(inlineDirectives).map((directive) => directive.attributes), ...Object.values(markAttributes)] const vocabularies = [...Object.values(blockNodes).map((blockNode) => blockNode.attributes), ...Object.values(inlineNodes).map((inlineNode) => inlineNode.attributes), ...Object.values(markAttributes)]
const attributeKeys = [ const attributeKeys = [
...new Set([...vocabularies.flatMap((vocabulary) => Object.keys(vocabulary)), ...Object.keys(blockDirectives).flatMap((type) => blockArgument(type) ?? []), marksAttribute, 'json', textDirectiveName]), ...new Set([...vocabularies.flatMap((vocabulary) => Object.keys(vocabulary)), ...Object.keys(blockNodes).flatMap((type) => blockArgument(type) ?? []), marksAttribute, 'json', textDirectiveName]),
] ]
const directiveNames = [...Object.keys(blockDirectives), ...Object.keys(inlineDirectives), ...Object.keys(markAttributes), carryName, listBreakName, textDirectiveName] const directiveNames = [...Object.keys(blockNodes), ...Object.keys(inlineNodes), ...Object.keys(markAttributes), carryName, listBreakName, textDirectiveName]
// Hostile generation reaches refusals; clean generation holds none a single piece would trip, so a whole document reaches the emitter. // Hostile generation reaches refusals; clean generation holds none a single piece would trip, so a whole document reaches the emitter.
function choose(hostile: boolean, choices: readonly Choice[], depth?: { depthIdentifier: DepthIdentifier; maxDepth: number }): Arbitrary<string> { function choose(hostile: boolean, choices: readonly Choice[], depth?: { depthIdentifier: DepthIdentifier; maxDepth: number }): Arbitrary<string> {
@@ -233,9 +233,9 @@ function inlineMarkdown(hostile: boolean): InlineMarkdown {
}, },
{ {
arbitrary: fc.oneof( arbitrary: fc.oneof(
...Object.entries(inlineDirectives).map(([name, directive]) => ...Object.entries(inlineNodes).map(([name, inlineNode]) =>
fc fc
.tuple(directive.textAttribute === undefined ? fc.constant(null) : fc.option(hostile ? word : prose), tableAttributes(directive.attributes, directive.textAttribute)) .tuple(inlineNode.textAttribute === undefined ? fc.constant(null) : fc.option(hostile ? word : prose), tableAttributes(inlineNode.attributes, inlineNode.textAttribute))
.map(([slot, attrs]) => (slot === null ? spellInlineLeafDirective(name, attrs) : `${spellInlineDirectiveOpener(name)}${slot}]${attrs}`)), .map(([slot, attrs]) => (slot === null ? spellInlineLeafDirective(name, attrs) : `${spellInlineDirectiveOpener(name)}${slot}]${attrs}`)),
), ),
...Object.entries(markAttributes) ...Object.entries(markAttributes)
@@ -328,15 +328,15 @@ function blockMarkdown(hostile: boolean, { inlines, oneLine }: InlineMarkdown, {
const blockDepth = fc.createDepthIdentifier() const blockDepth = fc.createDepthIdentifier()
const { blocks } = fc.letrec<{ block: string; blocks: string }>((tie) => { const { blocks } = fc.letrec<{ block: string; blocks: string }>((tie) => {
const bodyByModel = { block: fc.oneof(tie('blocks'), fc.constant('')), code: fencedCode, inline: fc.oneof(oneLine, fc.constant('')) } const bodyByModel = { block: fc.oneof(tie('blocks'), fc.constant('')), code: fencedCode, inline: fc.oneof(oneLine, fc.constant('')) }
const tableDirectives = Object.entries(blockDirectives).map(([name, directive]) => { const tableDirectives = Object.entries(blockNodes).map(([name, blockNode]) => {
const argument = const argument =
blockArgument(name) === undefined blockArgument(name) === undefined
? fc.constant(undefined) ? fc.constant(undefined)
: fc.oneof({ arbitrary: fc.constantFrom('DONE', 'TODO', 'custom', 'info', 'warning'), weight: 3 }, { arbitrary: bareToken, weight: 1 }) : fc.oneof({ arbitrary: fc.constantFrom('DONE', 'TODO', 'custom', 'info', 'warning'), weight: 3 }, { arbitrary: bareToken, weight: 1 })
const attrs = hostile ? fc.oneof({ arbitrary: tableAttributes(directive.attributes), weight: 4 }, { arbitrary: hostileAttributes, weight: 1 }) : tableAttributes(directive.attributes) const attrs = hostile ? fc.oneof({ arbitrary: tableAttributes(blockNode.attributes), weight: 4 }, { arbitrary: hostileAttributes, weight: 1 }) : tableAttributes(blockNode.attributes)
if (directive.contentModel === 'none') return fc.tuple(argument, attrs).map(([held, spelled]) => spellDirectiveOpener(name, held, spelled)) if (blockNode.contentModel === 'none') return fc.tuple(argument, attrs).map(([held, spelled]) => spellDirectiveOpener(name, held, spelled))
return fc return fc
.tuple(argument, attrs, bodyByModel[directive.contentModel], hostile ? closerDrift : fc.constant(null)) .tuple(argument, attrs, bodyByModel[blockNode.contentModel], hostile ? closerDrift : fc.constant(null))
.map(([held, spelled, body, closer]) => container(spellDirectiveOpener(name, held, spelled), body, closer ?? spellDirectiveCloser(name))) .map(([held, spelled, body, closer]) => container(spellDirectiveOpener(name, held, spelled), body, closer ?? spellDirectiveCloser(name)))
}) })
return { return {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { BlockType } from '../adf/block-directives.ts' import type { BlockType } from '../adf/block-nodes.ts'
const argumentByType = new Map( const argumentByType = new Map(
Object.entries({ Object.entries({
+4 -4
View File
@@ -1,9 +1,9 @@
import { blockDirective } from '../adf/block-directives.ts' import { blockNodeNamed } from '../adf/block-nodes.ts'
import { listBreakName } from './list-break.ts' import { listBreakName } from './list-break.ts'
export function blockDirectiveForm(name: string): 'container' | 'leaf' | undefined { export function blockDirectiveForm(name: string): 'container' | 'leaf' | undefined {
if (name === listBreakName) return 'leaf' if (name === listBreakName) return 'leaf'
const directive = blockDirective(name) const blockNode = blockNodeNamed(name)
if (directive === undefined) return undefined if (blockNode === undefined) return undefined
return directive.contentModel === 'none' ? 'leaf' : 'container' return blockNode.contentModel === 'none' ? 'leaf' : 'container'
} }
+14 -14
View File
@@ -1,8 +1,8 @@
import type { AdfDocument, AdfNode } from '../../adf/document.ts' import type { AdfDocument, AdfNode } from '../../adf/document.ts'
import type { BlockDirective } from '../../adf/block-directives.ts' import type { BlockNode } from '../../adf/block-nodes.ts'
import { adfDocumentFault, carriesOnly, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts' import { adfDocumentFault, carriesOnly, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
import { blockDirective, blockDirectives } from '../../adf/block-directives.ts'
import { blockDirectiveForm } from '../block-directive-forms.ts' import { blockDirectiveForm } from '../block-directive-forms.ts'
import { blockNodeNamed, blockNodes } from '../../adf/block-nodes.ts'
import { carriedBlock } from '../opaque-carry.ts' import { carriedBlock } from '../opaque-carry.ts'
import { emitInlineLine } from './inline-line.ts' import { emitInlineLine } from './inline-line.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts' import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
@@ -87,11 +87,11 @@ function interruptsParagraph(node: AdfNode): boolean {
} }
function emitBlock(node: AdfNode, path: ConvertErrorPath, depth: number, memo: SpellingMemo | undefined): Result<EmittedBlock> { function emitBlock(node: AdfNode, path: ConvertErrorPath, depth: number, memo: SpellingMemo | undefined): Result<EmittedBlock> {
const directive = blockDirective(node.type) const blockNode = blockNodeNamed(node.type)
if (directive === undefined) return commonMarkLine(carriedBlock(node, path, depth)) if (blockNode === undefined) return commonMarkLine(carriedBlock(node, path, depth))
const readable = readableBlock(node, path, depth, memo) const readable = readableBlock(node, path, depth, memo)
if (readable !== undefined) return readable if (readable !== undefined) return readable
return emitDirectiveBlock(node, directive, path, depth, () => walkBlocks(nodeContent(node), path, depth + 1, memo)) return emitDirectiveBlock(node, blockNode, path, depth, () => walkBlocks(nodeContent(node), path, depth + 1, memo))
} }
export function commonMarkSpelling(node: AdfNode, path: ConvertErrorPath, depth: number, memo: SpellingMemo): Result<null> | undefined { export function commonMarkSpelling(node: AdfNode, path: ConvertErrorPath, depth: number, memo: SpellingMemo): Result<null> | undefined {
@@ -143,18 +143,18 @@ function directivePair(node: AdfNode, opener: string, body: string, headroom: nu
return { headroom, spelling: 'directive', text: `${opener}\n${body === '' ? '' : `${body}\n`}${spellDirectiveCloser(node.type)}` } return { headroom, spelling: 'directive', text: `${opener}\n${body === '' ? '' : `${body}\n`}${spellDirectiveCloser(node.type)}` }
} }
function emitDirectiveBlock(node: AdfNode, directive: BlockDirective, path: ConvertErrorPath, depth: number, walkBody: () => Result<Walk>): Result<EmittedBlock> { function emitDirectiveBlock(node: AdfNode, blockNode: BlockNode, path: ConvertErrorPath, depth: number, walkBody: () => Result<Walk>): Result<EmittedBlock> {
if (node.text !== undefined) return failure('unsupported-node-shape', `a ${node.type} carries no text: this one holds text`, path) if (node.text !== undefined) return failure('unsupported-node-shape', `a ${node.type} carries no text: this one holds text`, path)
if (blockDirectiveForm(node.type) === 'leaf' && nodeContent(node).length > 0) return failure('unsupported-node-shape', `a ${node.type} holds no content: this one holds some`, path) if (blockDirectiveForm(node.type) === 'leaf' && nodeContent(node).length > 0) return failure('unsupported-node-shape', `a ${node.type} holds no content: this one holds some`, path)
if (directive.contentModel === 'code') return emitCodeDirective(node, directive, path, depth) if (blockNode.contentModel === 'code') return emitCodeDirective(node, blockNode, path, depth)
const opener = spellBlockDirectiveOpener(node, directive) const opener = spellBlockDirectiveOpener(node, blockNode)
if (opener === undefined) return commonMarkLine(carriedBlock(node, path, depth)) if (opener === undefined) return commonMarkLine(carriedBlock(node, path, depth))
return emitDirectiveBody(node, directive, opener, path, walkBody) return emitDirectiveBody(node, blockNode, opener, path, walkBody)
} }
function emitDirectiveBody(node: AdfNode, directive: BlockDirective, opener: string, path: ConvertErrorPath, walkBody: () => Result<Walk>): Result<EmittedBlock> { function emitDirectiveBody(node: AdfNode, blockNode: BlockNode, opener: string, path: ConvertErrorPath, walkBody: () => Result<Walk>): Result<EmittedBlock> {
if (blockDirectiveForm(node.type) === 'leaf') return success({ headroom: Number.POSITIVE_INFINITY, spelling: 'directive', text: opener }) if (blockDirectiveForm(node.type) === 'leaf') return success({ headroom: Number.POSITIVE_INFINITY, spelling: 'directive', text: opener })
if (directive.contentModel === 'inline') { if (blockNode.contentModel === 'inline') {
const line = emitInlineLine(nodeContent(node), 'paragraph', path) const line = emitInlineLine(nodeContent(node), 'paragraph', path)
if (!line.ok) return line if (!line.ok) return line
return success(directivePair(node, opener, line.value)) return success(directivePair(node, opener, line.value))
@@ -184,9 +184,9 @@ function tryCodeBlock(node: AdfNode, path: ConvertErrorPath): Result<EmittedBloc
return success(commonMarkText(fencedCodeBlock(slot.kind === 'fence' ? slot.info : '', text.value))) return success(commonMarkText(fencedCodeBlock(slot.kind === 'fence' ? slot.info : '', text.value)))
} }
function emitCodeDirective(node: AdfNode, directive: BlockDirective, path: ConvertErrorPath, depth: number): Result<EmittedBlock> { function emitCodeDirective(node: AdfNode, blockNode: BlockNode, path: ConvertErrorPath, depth: number): Result<EmittedBlock> {
const slot = languageSlot(nodeAttrs(node)['language']) const slot = languageSlot(nodeAttrs(node)['language'])
const opener = spellBlockDirectiveOpener(node, directive, slot.kind === 'attribute' ? [] : ['language']) const opener = spellBlockDirectiveOpener(node, blockNode, slot.kind === 'attribute' ? [] : ['language'])
if (opener === undefined) return commonMarkLine(carriedBlock(node, path, depth)) if (opener === undefined) return commonMarkLine(carriedBlock(node, path, depth))
const text = codeBlockText(node, path) const text = codeBlockText(node, path)
if (!text.ok) return text if (!text.ok) return text
@@ -246,7 +246,7 @@ function tryList(node: AdfNode, path: ConvertErrorPath, depth: number, memo: Spe
const line = tryListItemLines(item.walk.blocks, ordered ? `${start + offset}. ` : '- ') const line = tryListItemLines(item.walk.blocks, ordered ? `${start + offset}. ` : '- ')
if (line === undefined) { if (line === undefined) {
if (headroom < 1) return tooDeep(path) if (headroom < 1) return tooDeep(path)
return emitDirectiveBlock(node, ordered ? blockDirectives.orderedList : blockDirectives.bulletList, path, depth, () => success({ blocks: directiveItems(walked), headroom: headroom - 1 })) return emitDirectiveBlock(node, ordered ? blockNodes.orderedList : blockNodes.bulletList, path, depth, () => success({ blocks: directiveItems(walked), headroom: headroom - 1 }))
} }
lines.push(line) lines.push(line)
} }
@@ -1,17 +1,17 @@
import type { AdfNode } from '../../adf/document.ts' import type { AdfNode } from '../../adf/document.ts'
import type { BlockDirective } from '../../adf/block-directives.ts' import type { BlockNode } from '../../adf/block-nodes.ts'
import { blockArgument } from '../block-directive-arguments.ts' import { blockArgument } from '../block-directive-arguments.ts'
import { isBareToken, spellAttributes, spellDirectiveOpener, spellJsonAttribute, spellVocabulary } from '../directive-syntax.ts' import { isBareToken, spellAttributes, spellDirectiveOpener, spellJsonAttribute, spellVocabulary } from '../directive-syntax.ts'
import { markValues, marksAttribute } from '../block-directive-marks.ts' import { markValues, marksAttribute } from '../block-directive-marks.ts'
import { nodeAttrs, nodeMarks } from '../../adf/document.ts' import { nodeAttrs, nodeMarks } from '../../adf/document.ts'
import { vocabularyPairs } from '../../adf/attribute-vocabulary.ts' import { vocabularyPairs } from '../../adf/attribute-vocabulary.ts'
export function spellBlockDirectiveOpener(node: AdfNode, directive: BlockDirective, spelledByBody: readonly string[] = []): string | undefined { export function spellBlockDirectiveOpener(node: AdfNode, blockNode: BlockNode, spelledByBody: readonly string[] = []): string | undefined {
const argumentAttribute = blockArgument(node.type) const argumentAttribute = blockArgument(node.type)
const slot = bareArgument(node, argumentAttribute) const slot = bareArgument(node, argumentAttribute)
if (slot === undefined) return undefined if (slot === undefined) return undefined
const spelled = argumentAttribute === undefined ? spelledByBody : [argumentAttribute, ...spelledByBody] const spelled = argumentAttribute === undefined ? spelledByBody : [argumentAttribute, ...spelledByBody]
const pairs = vocabularyPairs(nodeAttrs(node), directive.attributes, spelled) const pairs = vocabularyPairs(nodeAttrs(node), blockNode.attributes, spelled)
if (pairs === undefined) return undefined if (pairs === undefined) return undefined
const spelledPairs = spellVocabulary(pairs) const spelledPairs = spellVocabulary(pairs)
const marks = nodeMarks(node) const marks = nodeMarks(node)
@@ -1,10 +1,10 @@
import type { AdfNode } from '../../adf/document.ts' import type { AdfNode } from '../../adf/document.ts'
import type { InlineDirective } from '../../adf/inline-directives.ts' import type { InlineNode } from '../../adf/inline-nodes.ts'
import { nodeAttrs } from '../../adf/document.ts' import { nodeAttrs } from '../../adf/document.ts'
import { spellAttributes, spellVocabulary } from '../directive-syntax.ts' import { spellAttributes, spellVocabulary } from '../directive-syntax.ts'
import { vocabularyPairs } from '../../adf/attribute-vocabulary.ts' import { vocabularyPairs } from '../../adf/attribute-vocabulary.ts'
export function spellInlineNodeAttributes(node: AdfNode, directive: InlineDirective): string | undefined { export function spellInlineNodeAttributes(node: AdfNode, inlineNode: InlineNode): string | undefined {
const pairs = vocabularyPairs(nodeAttrs(node), directive.attributes, directive.textAttribute === undefined ? [] : [directive.textAttribute]) const pairs = vocabularyPairs(nodeAttrs(node), inlineNode.attributes, inlineNode.textAttribute === undefined ? [] : [inlineNode.textAttribute])
return pairs === undefined ? undefined : spellAttributes(spellVocabulary(pairs)) return pairs === undefined ? undefined : spellAttributes(spellVocabulary(pairs))
} }
+12 -12
View File
@@ -1,12 +1,12 @@
import type { AdfMark, AdfNode } from '../../adf/document.ts' import type { AdfMark, AdfNode } from '../../adf/document.ts'
import type { InlineDirective } from '../../adf/inline-directives.ts' import type { InlineNode } from '../../adf/inline-nodes.ts'
import { assembleInlineLine, isSyntax, type InlineEscaping, type InlineSegment, type LineContainer, type NodeRange } from './line-escaping.ts' import { assembleInlineLine, isSyntax, type InlineEscaping, type InlineSegment, type LineContainer, 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'
import { escapeUnbalanced, spellDestination } from '../commonmark/link-syntax.ts' import { escapeUnbalanced, spellDestination } from '../commonmark/link-syntax.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts' import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { inlineDirective } from '../../adf/inline-directives.ts' import { inlineNodeNamed } from '../../adf/inline-nodes.ts'
import { largestNesting } from '../../nesting.ts' import { largestNesting } from '../../nesting.ts'
import { longestBacktickRun } from '../commonmark/backtick-runs.ts' import { longestBacktickRun } from '../commonmark/backtick-runs.ts'
import { nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts' import { nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
@@ -196,7 +196,7 @@ function nodePath(context: InlineContext, index: number): ConvertErrorPath {
function carries(node: AdfNode, carried: ReadonlySet<number>, index: number): boolean { function carries(node: AdfNode, carried: ReadonlySet<number>, index: number): boolean {
if (carried.has(index)) return true if (carried.has(index)) return true
return node.type !== 'text' && inlineDirective(node.type) === undefined return node.type !== 'text' && inlineNodeNamed(node.type) === undefined
} }
function emitLeaf(node: AdfNode, context: InlineContext, index: number): Result<Emission> { function emitLeaf(node: AdfNode, context: InlineContext, index: number): Result<Emission> {
@@ -208,27 +208,27 @@ function emitLeaf(node: AdfNode, context: InlineContext, index: number): Result<
} }
const types = nodeMarks(node).map((mark) => mark.type) const types = nodeMarks(node).map((mark) => mark.type)
if (new Set(types).size !== types.length) return failure('unsupported-node-shape', `a ${node.type} node carries one mark type twice`, path) if (new Set(types).size !== types.length) return failure('unsupported-node-shape', `a ${node.type} node carries one mark type twice`, path)
const directive = inlineDirective(node.type) const inlineNode = inlineNodeNamed(node.type)
if (directive === undefined) return emitText(node, context, index, path) if (inlineNode === undefined) return emitText(node, context, index, path)
if (node.type === 'hardBreak') return emitHardBreak(node, directive, context, index, path) if (node.type === 'hardBreak') return emitHardBreak(node, inlineNode, context, index, path)
return emitInlineDirective(node, directive, index, path) return emitInlineDirective(node, inlineNode, index, path)
} }
function emitHardBreak(node: AdfNode, directive: InlineDirective, context: InlineContext, index: number, path: ConvertErrorPath): Result<Emission> { function emitHardBreak(node: AdfNode, inlineNode: InlineNode, context: InlineContext, index: number, path: ConvertErrorPath): Result<Emission> {
const empty = refuseContentAndText(node, path) const empty = refuseContentAndText(node, path)
if (!empty.ok) return empty if (!empty.ok) return empty
const attributes = spellInlineNodeAttributes(node, directive) const attributes = spellInlineNodeAttributes(node, inlineNode)
if (attributes === undefined) return success({ carry: { first: index, last: index } }) if (attributes === undefined) return success({ carry: { first: index, last: index } })
if (attributes === '' && context.spansLines && !context.atBlockEnd) return success({ segments: [syntax('\\\n')] }) if (attributes === '' && context.spansLines && !context.atBlockEnd) return success({ segments: [syntax('\\\n')] })
return success({ segments: [syntax(spellInlineLeafDirective('hardBreak', attributes))] }) return success({ segments: [syntax(spellInlineLeafDirective('hardBreak', attributes))] })
} }
function emitInlineDirective(node: AdfNode, directive: InlineDirective, index: number, path: ConvertErrorPath): Result<Emission> { function emitInlineDirective(node: AdfNode, inlineNode: InlineNode, index: number, path: ConvertErrorPath): Result<Emission> {
const empty = refuseContentAndText(node, path) const empty = refuseContentAndText(node, path)
if (!empty.ok) return empty if (!empty.ok) return empty
const attributes = spellInlineNodeAttributes(node, directive) const attributes = spellInlineNodeAttributes(node, inlineNode)
if (attributes === undefined) return success({ carry: { first: index, last: index } }) if (attributes === undefined) return success({ carry: { first: index, last: index } })
const slot = directive.textAttribute === undefined ? undefined : nodeAttrs(node)[directive.textAttribute] const slot = inlineNode.textAttribute === undefined ? undefined : nodeAttrs(node)[inlineNode.textAttribute]
if (slot === undefined) return success({ segments: [syntax(spellInlineLeafDirective(node.type, attributes))] }) if (slot === undefined) return success({ segments: [syntax(spellInlineLeafDirective(node.type, attributes))] })
if (typeof slot !== 'string') return success({ carry: { first: index, last: index } }) if (typeof slot !== 'string') return success({ carry: { first: index, last: index } })
const spans = slotLineEndingFault(node.type, slot) const spans = slotLineEndingFault(node.type, slot)
+13 -13
View File
@@ -1,23 +1,23 @@
import type { AdfAttributes, AdfMark, AdfNode } from '../../adf/document.ts' import type { AdfAttributes, AdfMark, AdfNode } from '../../adf/document.ts'
import type { BlockDirective } from '../../adf/block-directives.ts' import type { BlockNode } from '../../adf/block-nodes.ts'
import type { ConvertFault } from '../../result.ts' import type { ConvertFault } from '../../result.ts'
import type { DirectiveAttributes, DirectiveValue } from '../directive-syntax.ts' import type { DirectiveAttributes, DirectiveValue } from '../directive-syntax.ts'
import type { Elsewhere } from './directive-attributes.ts' import type { Elsewhere } from './directive-attributes.ts'
import { attributeNestingMessage, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts' import { attributeNestingMessage, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
import { attributeValue, directivePrefix, spellAttributeValue, unknownDirectiveFault } from '../directive-syntax.ts' import { attributeValue, directivePrefix, spellAttributeValue, unknownDirectiveFault } from '../directive-syntax.ts'
import { blockArgument } from '../block-directive-arguments.ts' import { blockArgument } from '../block-directive-arguments.ts'
import { blockDirective } from '../../adf/block-directives.ts'
import { blockDirectiveForm } from '../block-directive-forms.ts' import { blockDirectiveForm } from '../block-directive-forms.ts'
import { blockNodeNamed } from '../../adf/block-nodes.ts'
import { carryName } from '../opaque-carry.ts' import { carryName } from '../opaque-carry.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts' import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { inlineDirective } from '../../adf/inline-directives.ts'
import { inlineMarkSpellingFault } from './directive-marks.ts' import { inlineMarkSpellingFault } from './directive-marks.ts'
import { inlineNodeNamed } from '../../adf/inline-nodes.ts'
import { marksAttribute, readMarkValues } from '../block-directive-marks.ts' import { marksAttribute, readMarkValues } from '../block-directive-marks.ts'
import { readVocabulary } from './directive-attributes.ts' import { readVocabulary } from './directive-attributes.ts'
import { slotLineEndingFault } from '../directive-syntax.ts' import { slotLineEndingFault } from '../directive-syntax.ts'
import { textDirectiveName } from '../text-directive.ts' import { textDirectiveName } from '../text-directive.ts'
export type BlockDirectiveNode = { contentModel: BlockDirective['contentModel']; node: AdfNode } export type BlockDirectiveNode = { contentModel: BlockNode['contentModel']; node: AdfNode }
export function readBlockDirectiveNode( export function readBlockDirectiveNode(
name: string, name: string,
@@ -28,13 +28,13 @@ export function readBlockDirectiveNode(
if (name === carryName) { if (name === carryName) {
return failure('malformed-directive', `the name ${carryName} is reserved for the opaque carry, whose block form is the ${carryName} fence`, path) return failure('malformed-directive', `the name ${carryName} is reserved for the opaque carry, whose block form is the ${carryName} fence`, path)
} }
const directive = blockDirective(name) const blockNode = blockNodeNamed(name)
if (directive === undefined) return faulted(inlineSpellingFault(name) ?? unknownDirectiveFault(name), path) if (blockNode === undefined) return faulted(inlineSpellingFault(name) ?? unknownDirectiveFault(name), path)
const argumentKey = blockArgument(name) const argumentKey = blockArgument(name)
const rest = new Map(attributes) const rest = new Map(attributes)
rest.delete(marksAttribute) rest.delete(marksAttribute)
const elsewhere: Elsewhere | undefined = argumentKey === undefined ? undefined : { key: argumentKey, slot: 'argument' } const elsewhere: Elsewhere | undefined = argumentKey === undefined ? undefined : { key: argumentKey, slot: 'argument' }
const attrs = readVocabulary(name, rest, directive.attributes, elsewhere, path) const attrs = readVocabulary(name, rest, blockNode.attributes, elsewhere, path)
if (!attrs.ok) return attrs if (!attrs.ok) return attrs
if (argument !== undefined) { if (argument !== undefined) {
if (argumentKey === undefined) return failure('unsupported-node-shape', `${name} takes no argument: this one spells one`, path) if (argumentKey === undefined) return failure('unsupported-node-shape', `${name} takes no argument: this one spells one`, path)
@@ -43,7 +43,7 @@ export function readBlockDirectiveNode(
const spelled = attributes.get(marksAttribute) const spelled = attributes.get(marksAttribute)
const marks: Result<AdfMark[] | undefined> = spelled === undefined ? success(undefined) : readMarks(name, spelled, path) const marks: Result<AdfMark[] | undefined> = spelled === undefined ? success(undefined) : readMarks(name, spelled, path)
if (!marks.ok) return marks if (!marks.ok) return marks
return success({ contentModel: directive.contentModel, node: namedNode(name, attrs.value, marks.value) }) return success({ contentModel: blockNode.contentModel, node: namedNode(name, attrs.value, marks.value) })
} }
export function readInlineDirectiveNode( export function readInlineDirectiveNode(
@@ -52,12 +52,12 @@ export function readInlineDirectiveNode(
content: readonly AdfNode[] | undefined, content: readonly AdfNode[] | undefined,
path: ConvertErrorPath, path: ConvertErrorPath,
): Result<AdfNode> { ): Result<AdfNode> {
const directive = inlineDirective(name) const inlineNode = inlineNodeNamed(name)
if (directive === undefined) return faulted(blockSpellingFault(name) ?? unknownDirectiveFault(name), path) if (inlineNode === undefined) return faulted(blockSpellingFault(name) ?? unknownDirectiveFault(name), path)
const slot = directive.textAttribute const slot = inlineNode.textAttribute
if (slot === undefined && content !== undefined) return failure('unsupported-node-shape', `${name} takes no content: this one holds some`, path) if (slot === undefined && content !== undefined) return failure('unsupported-node-shape', `${name} takes no content: this one holds some`, path)
const elsewhere: Elsewhere | undefined = slot === undefined ? undefined : { key: slot, slot: 'content' } const elsewhere: Elsewhere | undefined = slot === undefined ? undefined : { key: slot, slot: 'content' }
const attrs = readVocabulary(name, attributes, directive.attributes, elsewhere, path) const attrs = readVocabulary(name, attributes, inlineNode.attributes, elsewhere, path)
if (!attrs.ok) return attrs if (!attrs.ok) return attrs
if (slot !== undefined && content !== undefined) { if (slot !== undefined && content !== undefined) {
const text = slotText(content) const text = slotText(content)
@@ -75,7 +75,7 @@ export function readInlineDirectiveNode(
function inlineSpellingFault(name: string): ConvertFault | undefined { function inlineSpellingFault(name: string): ConvertFault | undefined {
const mark = inlineMarkSpellingFault(name) const mark = inlineMarkSpellingFault(name)
if (mark !== undefined) return mark if (mark !== undefined) return mark
if (inlineDirective(name) === undefined && name !== textDirectiveName) return undefined if (inlineNodeNamed(name) === undefined && name !== textDirectiveName) return undefined
return { code: 'unsupported-node-shape', message: `${name} takes the inline form, ${directivePrefix}${name}{…}, never the block form` } return { code: 'unsupported-node-shape', message: `${name} takes the inline form, ${directivePrefix}${name}{…}, never the block form` }
} }
+2 -2
View File
@@ -8,7 +8,7 @@ import { backtickRun, closingBacktickRun } from '../commonmark/backtick-runs.ts'
import { commonMarkLink, linkHref } from '../mark-spellings.ts' import { commonMarkLink, linkHref } from '../mark-spellings.ts'
import { delimiterFlags, matchEmphasis, runLength } from '../commonmark/emphasis-matching.ts' import { delimiterFlags, matchEmphasis, runLength } from '../commonmark/emphasis-matching.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts' import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { inlineDirective } from '../../adf/inline-directives.ts' import { inlineNodeNamed } from '../../adf/inline-nodes.ts'
import { mergeAdjacentText } from '../../adf/editor-normal.ts' import { mergeAdjacentText } from '../../adf/editor-normal.ts'
import { noSpans, readInlineDirective } from '../directive-syntax.ts' import { noSpans, readInlineDirective } from '../directive-syntax.ts'
import { nodeAttrs, nodeMarks } from '../../adf/document.ts' import { nodeAttrs, nodeMarks } from '../../adf/document.ts'
@@ -407,7 +407,7 @@ function imageAlt(inner: readonly Piece[], path: ConvertErrorPath): Result<strin
// spec/flavour.md, The CommonMark image: the description's plain text, the content slot included. // spec/flavour.md, The CommonMark image: the description's plain text, the content slot included.
function altText(node: AdfNode): string { function altText(node: AdfNode): string {
if (node.type === 'hardBreak') return ' ' if (node.type === 'hardBreak') return ' '
const slot = inlineDirective(node.type)?.textAttribute const slot = inlineNodeNamed(node.type)?.textAttribute
const spelled = slot === undefined ? undefined : nodeAttrs(node)[slot] const spelled = slot === undefined ? undefined : nodeAttrs(node)[slot]
return typeof spelled === 'string' ? spelled : (node.text ?? '') return typeof spelled === 'string' ? spelled : (node.text ?? '')
} }
+14 -14
View File
@@ -7,9 +7,9 @@ import type { Arbitrary } from 'fast-check'
import type { AttributeKind, AttributeVocabulary } from './adf/attribute-vocabulary.ts' import type { AttributeKind, AttributeVocabulary } from './adf/attribute-vocabulary.ts'
import type { JsonValue } from './json-value.ts' import type { JsonValue } from './json-value.ts'
import { blockArgument } from './markdown/block-directive-arguments.ts' import { blockArgument } from './markdown/block-directive-arguments.ts'
import { blockDirectives } from './adf/block-directives.ts' import { blockNodes } from './adf/block-nodes.ts'
import { directivePrefix } from './markdown/directive-syntax.ts' import { directivePrefix } from './markdown/directive-syntax.ts'
import { inlineDirectives } from './adf/inline-directives.ts' import { inlineNodes } from './adf/inline-nodes.ts'
import { markAttributes } from './adf/mark-attributes.ts' import { markAttributes } from './adf/mark-attributes.ts'
import { toEditorNormal } from './adf/editor-normal.ts' import { toEditorNormal } from './adf/editor-normal.ts'
@@ -25,7 +25,7 @@ const emptyCell: AdfNode = { content: [{ type: 'paragraph' }], type: 'tableCell'
const flatCommonMarkShapeWeight = 4 const flatCommonMarkShapeWeight = 4
export const markdownPieces = fc.constantFrom(...'aZ09 \t\n!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~é\xa0🎉', 'ab:', 'http://', directivePrefix, `${directivePrefix}a[`, `${directivePrefix}a{`) export const markdownPieces = fc.constantFrom(...'aZ09 \t\n!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~é\xa0🎉', 'ab:', 'http://', directivePrefix, `${directivePrefix}a[`, `${directivePrefix}a{`)
const nestingCommonMarkShapeWeight = 21 const nestingCommonMarkShapeWeight = 21
const spelledTypes = new Set(['text', ...Object.keys(blockDirectives), ...Object.keys(inlineDirectives), ...Object.keys(markAttributes)]) const spelledTypes = new Set(['text', ...Object.keys(blockNodes), ...Object.keys(inlineNodes), ...Object.keys(markAttributes)])
export function textOf(minLength: number): Arbitrary<string> { export function textOf(minLength: number): Arbitrary<string> {
return fc.oneof( return fc.oneof(
@@ -94,8 +94,8 @@ const autolinkTextNode = fc
.record({ href: fc.tuple(fc.constantFrom('ab:', 'http://'), textOf(0)).map(([scheme, rest]) => `${scheme}${rest}`), marks }) .record({ href: fc.tuple(fc.constantFrom('ab:', 'http://'), textOf(0)).map(([scheme, rest]) => `${scheme}${rest}`), marks })
.map(({ href, marks: held }): AdfNode => ({ marks: [...held.filter((outer) => outer.type !== 'link'), { attrs: { href }, type: 'link' }], text: href, type: 'text' })) .map(({ href, marks: held }): AdfNode => ({ marks: [...held.filter((outer) => outer.type !== 'link'), { attrs: { href }, type: 'link' }], text: href, type: 'text' }))
const inlineNodes = Object.entries(inlineDirectives).map(([type, directive]) => const tableInlineNodes = Object.entries(inlineNodes).map(([type, inlineNode]) =>
fc.record({ attrs: attributes(directive.attributes), marks }).map((held): AdfNode => ({ ...held, type })), fc.record({ attrs: attributes(inlineNode.attributes), marks }).map((held): AdfNode => ({ ...held, type })),
) )
function weighted(arbitraries: readonly Arbitrary<AdfNode>[], weight: number): { arbitrary: Arbitrary<AdfNode>; weight: number }[] { function weighted(arbitraries: readonly Arbitrary<AdfNode>[], weight: number): { arbitrary: Arbitrary<AdfNode>; weight: number }[] {
@@ -112,17 +112,17 @@ const positions = fc.letrec<Positions>((tie) => {
none: fc.constant<AdfNode[]>([]), none: fc.constant<AdfNode[]>([]),
} }
const blockMarks = fc.oneof({ arbitrary: fc.constant<AdfMark[]>([]), weight: 4 }, { arbitrary: marks, weight: 1 }) const blockMarks = fc.oneof({ arbitrary: fc.constant<AdfMark[]>([]), weight: 4 }, { arbitrary: marks, weight: 1 })
const blockNodes = Object.entries(blockDirectives).map(([type, directive]) => { const tableBlockNodes = Object.entries(blockNodes).map(([type, blockNode]) => {
const argument = blockArgument(type) const argument = blockArgument(type)
const vocabulary: AttributeVocabulary = argument === undefined ? directive.attributes : { ...directive.attributes, [argument]: 'string' } const vocabulary: AttributeVocabulary = argument === undefined ? blockNode.attributes : { ...blockNode.attributes, [argument]: 'string' }
const node = fc.record({ attrs: attributes(vocabulary), content: contentByModel[directive.contentModel], marks: blockMarks }).map((held): AdfNode => ({ ...held, type })) const node = fc.record({ attrs: attributes(vocabulary), content: contentByModel[blockNode.contentModel], marks: blockMarks }).map((held): AdfNode => ({ ...held, type }))
return { leaf: directive.contentModel === 'code' || directive.contentModel === 'none', node } return { leaf: blockNode.contentModel === 'code' || blockNode.contentModel === 'none', node }
}) })
const unknownNode = fc const unknownNode = fc
.record({ attrs: fc.dictionary(jsonKey, jsonValue, { maxKeys: 2, noNullPrototype: true }), content: fc.array(tie('inline'), { depthIdentifier, maxLength: 2 }), marks, type: unknownType }) .record({ attrs: fc.dictionary(jsonKey, jsonValue, { maxKeys: 2, noNullPrototype: true }), content: fc.array(tie('inline'), { depthIdentifier, maxLength: 2 }), marks, type: unknownType })
.map((held): AdfNode => held) .map((held): AdfNode => held)
const leafBlocks = blockNodes.filter((entry) => entry.leaf).map((entry) => entry.node) const leafBlocks = tableBlockNodes.filter((entry) => entry.leaf).map((entry) => entry.node)
const containerBlocks = blockNodes.filter((entry) => !entry.leaf).map((entry) => entry.node) const containerBlocks = tableBlockNodes.filter((entry) => !entry.leaf).map((entry) => entry.node)
const misplacedWeight = 7 const misplacedWeight = 7
const paragraph = fc.oneof({ arbitrary: inlineContent, weight: 3 }, { arbitrary: fc.array(backtickRunNode, { maxLength: 4, minLength: 2 }), weight: 1 }).map((content): AdfNode => ({ content, type: 'paragraph' })) const paragraph = fc.oneof({ arbitrary: inlineContent, weight: 3 }, { arbitrary: fc.array(backtickRunNode, { maxLength: 4, minLength: 2 }), weight: 1 }).map((content): AdfNode => ({ content, type: 'paragraph' }))
const cell = (type: string) => paragraph.map((held): AdfNode => ({ content: [held], type })) const cell = (type: string) => paragraph.map((held): AdfNode => ({ content: [held], type }))
@@ -148,7 +148,7 @@ const positions = fc.letrec<Positions>((tie) => {
{ depthIdentifier, depthSize: 'small', maxDepth: 4 }, { depthIdentifier, depthSize: 'small', maxDepth: 4 },
{ arbitrary: fc.oneof(...flatBlocks), weight: flatBlocks.reduce((sum, entry) => sum + entry.weight, 0) }, { arbitrary: fc.oneof(...flatBlocks), weight: flatBlocks.reduce((sum, entry) => sum + entry.weight, 0) },
{ arbitrary: fc.oneof(...containerBlocks), weight: containerBlocks.length * 2 }, { arbitrary: fc.oneof(...containerBlocks), weight: containerBlocks.length * 2 },
{ arbitrary: fc.oneof(textNode, ...inlineNodes, unknownNode), weight: misplacedWeight }, { arbitrary: fc.oneof(textNode, ...tableInlineNodes, unknownNode), weight: misplacedWeight },
{ arbitrary: fc.oneof(...nestingCommonMarkShapes), weight: nestingCommonMarkShapes.length * nestingCommonMarkShapeWeight }, { arbitrary: fc.oneof(...nestingCommonMarkShapes), weight: nestingCommonMarkShapes.length * nestingCommonMarkShapeWeight },
), ),
inline: fc.oneof( inline: fc.oneof(
@@ -156,8 +156,8 @@ const positions = fc.letrec<Positions>((tie) => {
{ arbitrary: textNode, weight: 12 }, { arbitrary: textNode, weight: 12 },
{ arbitrary: autolinkTextNode, weight: 2 }, { arbitrary: autolinkTextNode, weight: 2 },
{ arbitrary: backtickRunNode, weight: 3 }, { arbitrary: backtickRunNode, weight: 3 },
{ arbitrary: fc.oneof(...inlineNodes), weight: 7 }, { arbitrary: fc.oneof(...tableInlineNodes), weight: 7 },
{ arbitrary: fc.oneof(...blockNodes.map((entry) => entry.node), unknownNode), weight: 2 }, { arbitrary: fc.oneof(...tableBlockNodes.map((entry) => entry.node), unknownNode), weight: 2 },
), ),
} }
}) })
+13
View File
@@ -994,6 +994,19 @@ The done `todo.md` items in full, as they were written. `todo.md` keeps a one-li
go whole. The gate's seconds is confirmed stated nowhere: `docker-runner.sh` measures each go whole. The gate's seconds is confirmed stated nowhere: `docker-runner.sh` measures each
leg's elapsed time and no number bounds it. leg's elapsed time and no number bounds it.
- [x] **21 — The ADF tables carry ADF's nouns (`0.2.0`).** `adf/block-directives.ts` and
`adf/inline-directives.ts` hold the ADF node tables — `paragraph`, `heading`, `blockquote`
and `rule` among them — under the markdown flavour's word, inside the directory §11 forbids
to know a format. Rename to the noun `spec/flavour.md` uses, types and accessors with them.
Before 7 doubles the import sites.
**Done** (2026-09-21): the tables are `adf/block-nodes.ts` and `adf/inline-nodes.ts`,
`blockNodes` and `inlineNodes` holding `BlockNode` and `InlineNode`, read by
`blockNodeNamed` and `inlineNodeNamed` — the accessor named apart from the entry so every
call site's local is the one name `blockNode`/`inlineNode` rather than shadowing its own
lookup. `BlockType` stays: it names a node type, the parallel of `mark-attributes.ts`'s
`MarkType`. `markdown/`'s `blockDirectiveForm`, `blockArgument` and the `spell*Directive*`
family keep the word, naming the spelling rather than the node.
## 5 — Ship `0.1.0` ## 5 — Ship `0.1.0`
- [ ] **5 — Ship `0.1.0`.** Only the maintainer's own acts are left (§15): make the Gitea repo - [ ] **5 — Ship `0.1.0`.** Only the maintainer's own acts are left (§15): make the Gitea repo
+1 -5
View File
@@ -55,11 +55,6 @@ number under the pipeline.
upward, so the first raise to the measured figure reddens a run that changed nothing. Make upward, so the first raise to the measured figure reddens a run that changed nothing. Make
the measurement repeatable, or state the number the floor may be raised to and why it is not the measurement repeatable, or state the number the floor may be raised to and why it is not
the measured one. the measured one.
- [ ] **21 — The ADF tables carry ADF's nouns (`0.2.0`).** `adf/block-directives.ts` and
`adf/inline-directives.ts` hold the ADF node tables — `paragraph`, `heading`, `blockquote`
and `rule` among them — under the markdown flavour's word, inside the directory §11 forbids
to know a format. Rename to the noun `spec/flavour.md` uses, types and accessors with them.
Before 7 doubles the import sites.
- [ ] **22 — `LineContainer` sits at the markdown level (`0.2.0`).** Two of the four - [ ] **22 — `LineContainer` sits at the markdown level (`0.2.0`).** Two of the four
`parse/` → `emit/` imports fetch this type from `emit/line-escaping.ts`, camouflaging the two `parse/` → `emit/` imports fetch this type from `emit/line-escaping.ts`, camouflaging the two
that are the deliberate spelling consultation. Move it, and name those two in §11 as the whole that are the deliberate spelling consultation. Move it, and name those two in §11 as the whole
@@ -287,6 +282,7 @@ number under the pipeline.
- [x] **18 — The subtree the directive spelling asks about (`0.2.0`).** - [x] **18 — The subtree the directive spelling asks about (`0.2.0`).**
- [x] **19 — A home for what both formats read (`0.2.0`).** - [x] **19 — A home for what both formats read (`0.2.0`).**
- [x] **20 — The give-way channel is unmistakable (`0.2.0`).** - [x] **20 — The give-way channel is unmistakable (`0.2.0`).**
- [x] **21 — The ADF tables carry ADF's nouns (`0.2.0`).**
- [x] **28 — `emitLine`'s retry loop cannot spin (`0.2.0`).** - [x] **28 — `emitLine`'s retry loop cannot spin (`0.2.0`).**
- [x] **29 — The README reads raw HTML as refused for good (`0.2.0`).** - [x] **29 — The README reads raw HTML as refused for good (`0.2.0`).**
- [x] **30 — AGENTS.md says each thing once (`0.2.0`).** - [x] **30 — AGENTS.md says each thing once (`0.2.0`).**