Read the node tables backwards, a directive to the node and marks it names
CI / gate (push) Successful in 5s

This commit is contained in:
2026-09-01 11:09:21 +02:00
parent 75d6bd426d
commit cd0a4cb0e8
31 changed files with 558 additions and 140 deletions
+91
View File
@@ -0,0 +1,91 @@
import type { AdfAttributes, AdfMark, AdfNode } from '../../adf/document.ts'
import type { AttributeVocabulary } from '../../adf/attribute-vocabulary.ts'
import type { BlockDirective } from '../../adf/block-directives.ts'
import type { DirectiveAttributes, DirectiveSpan, DirectiveValue } from '../directive-syntax.ts'
import { attributeValue, spellAttributeValue, unknownDirectiveFault } from '../directive-syntax.ts'
import { blockArgument } from '../block-directive-arguments.ts'
import { blockDirective } from '../../adf/block-directives.ts'
import { carryName } from '../opaque-carry.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { inlineDirective } from '../../adf/inline-directives.ts'
import { marksAttribute, readMarkValues } from '../block-directive-marks.ts'
export type BlockDirectiveNode = { contentModel: BlockDirective['contentModel']; node: AdfNode }
type Elsewhere = { key: string; place: string }
export function readBlockDirectiveNode(
name: string,
argument: string | undefined,
attributes: DirectiveAttributes,
path: ConvertErrorPath,
): Result<BlockDirectiveNode> {
if (name === carryName) {
return failure('malformed-directive', `the name ${carryName} is reserved for the opaque carry, whose block form is the fence`, path)
}
const directive = blockDirective(name)
if (directive === undefined) return faulted(unknownDirectiveFault(name), path)
const argumentKey = blockArgument(name)
const rest = new Map(attributes)
rest.delete(marksAttribute)
const elsewhere = argumentKey === undefined ? undefined : { key: argumentKey, place: 'as the directive argument' }
const attrs = readVocabulary(name, rest, directive.attributes, elsewhere, path)
if (!attrs.ok) return attrs
if (argument !== undefined) {
if (argumentKey === undefined) return failure('unsupported-node-shape', `a ${name} takes no argument`, path)
attrs.value[argumentKey] = argument
}
const spelled = attributes.get(marksAttribute)
const marks: Result<AdfMark[] | undefined> = spelled === undefined ? success(undefined) : readMarks(name, spelled, path)
if (!marks.ok) return marks
return success({ contentModel: directive.contentModel, node: directiveNode(name, attrs.value, marks.value) })
}
export function readInlineDirectiveNode(span: DirectiveSpan, path: ConvertErrorPath): Result<AdfNode> {
const directive = inlineDirective(span.name)
if (directive === undefined) return faulted(unknownDirectiveFault(span.name), path)
const slot = directive.textAttribute
if (span.content !== undefined) {
const message = slot === undefined ? `a ${span.name} takes no content` : `the content slot a ${span.name} spells its ${slot} attribute in is unsupported`
return failure('unsupported-node-shape', message, path)
}
const elsewhere = slot === undefined ? undefined : { key: slot, place: 'in the content slot' }
const attrs = readVocabulary(span.name, span.attributes, directive.attributes, elsewhere, path)
if (!attrs.ok) return attrs
return success(directiveNode(span.name, attrs.value, undefined))
}
function readVocabulary(
type: string,
attributes: DirectiveAttributes,
vocabulary: AttributeVocabulary,
elsewhere: Elsewhere | undefined,
path: ConvertErrorPath,
): Result<AdfAttributes> {
const attrs: AdfAttributes = {}
for (const [key, spelled] of attributes) {
if (key === elsewhere?.key) return failure('unsupported-node-shape', `a ${type} spells its ${key} attribute ${elsewhere.place}`, path)
const kind = Object.hasOwn(vocabulary, key) ? vocabulary[key] : undefined
if (kind === undefined) return failure('unsupported-node-shape', `a ${type} holds no ${key} attribute`, path)
const read = attributeValue(spelled.text, kind)
if (read === undefined) return failure('unsupported-node-shape', `the ${key} attribute of a ${type} is a ${kind}`, path)
const spelling = spellAttributeValue(read)
if (spelling !== spelled.spelling) return failure('unsupported-node-shape', `a ${type} spells its ${key} attribute as ${key}=${spelling}`, path)
attrs[key] = read.value
}
return success(attrs)
}
function readMarks(type: string, spelled: DirectiveValue, path: ConvertErrorPath): Result<AdfMark[]> {
const read = attributeValue(spelled.text, 'json')
const marks = read === undefined || spellAttributeValue(read) !== spelled.spelling ? undefined : readMarkValues(read.value)
if (marks === undefined) {
return failure('unsupported-node-shape', `the ${marksAttribute} attribute of a ${type} is its marks array in canonical JSON`, path)
}
return success(marks)
}
function directiveNode(type: string, attrs: AdfAttributes, marks: readonly AdfMark[] | undefined): AdfNode {
const named = Object.keys(attrs).length === 0 ? { type } : { attrs, type }
return marks === undefined ? named : { ...named, marks: [...marks] }
}