3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-13 04:08:29 +01:00

Append the reading-cost figure

This commit is contained in:
2026-10-21 23:43:05 +02:00
committed by Lillem4n
parent cb509d270b
commit db10287cae
10 changed files with 4 additions and 0 deletions
@@ -1,33 +0,0 @@
import type { AdfAttributes } from '../../adf/document.ts'
import type { AttributeVocabulary } from '../../adf/attribute-vocabulary.ts'
import type { DirectiveAttributes } from '../directive-syntax.ts'
import { attributeNestingMessage } from '../../adf/document.ts'
import { attributeValue, spellAttributeValue } from '../directive-syntax.ts'
import { failure, success, type ConvertErrorPath, type Result } from '../../result.ts'
export type Elsewhere = { key: string; slot: 'argument' | 'content' }
export 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) {
const place = elsewhere.slot === 'argument' ? 'as the directive argument' : 'in the content slot'
return failure('unsupported-node-shape', `${type} spells its ${key} attribute ${place}, never in {attrs}`, path)
}
const kind = Object.hasOwn(vocabulary, key) ? vocabulary[key] : undefined
if (kind === undefined) return failure('unsupported-node-shape', `${type} holds no ${key} attribute: this one spells it`, path)
const read = attributeValue(spelled.decoded, kind)
if (read.refusal === 'nesting') return failure('unsupported-nesting-depth', attributeNestingMessage(key, type), path)
if (read.value === undefined) return failure('unsupported-node-shape', `the ${key} attribute of ${type} is no ${kind === 'numbers' ? 'comma list of numbers' : kind}`, path)
const spelling = spellAttributeValue(read.value)
if (spelling !== spelled.spelling) return failure('unsupported-node-shape', `${type} spells its ${key} attribute as ${key}=${spelling}`, path)
attrs[key] = read.value.value
}
return success(attrs)
}
-47
View File
@@ -1,47 +0,0 @@
import type { AdfMark } from '../../adf/document.ts'
import type { AttributeVocabulary } from '../../adf/attribute-vocabulary.ts'
import type { ConvertFault } from '../../result.ts'
import type { DirectiveAttributes } from '../directive-syntax.ts'
import type { MarkSpelling } from '../mark-spellings.ts'
import { directivePrefix } from '../directive-syntax.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { markSpelling } from '../mark-spellings.ts'
import { readEmptyKeys } from '../empty-keys.ts'
import { readVocabulary } from './directive-attributes.ts'
export function readDirectiveMark(name: string, attributes: DirectiveAttributes, path: ConvertErrorPath): Result<AdfMark> | undefined {
const spelling = markSpelling(name)
if (spelling === undefined) return undefined
const markdown = markdownForm(spelling)
if (markdown !== undefined) return failure('unsupported-node-shape', `${name} is spelled ${markdown}, never as a directive`, path)
return readMarkAttributes(name, attributes, spelling.attributes, path)
}
export function readMarkAttributes(type: string, attributes: DirectiveAttributes, vocabulary: AttributeVocabulary, path: ConvertErrorPath): Result<AdfMark> {
const empty = readEmptyKeys(type, attributes, ['attrs'], false)
if (empty.fault !== undefined) return faulted(empty.fault, path)
const attrs = readVocabulary(type, empty.value.rest, vocabulary, undefined, path)
if (!attrs.ok) return attrs
if (empty.value.empty.has('attrs')) return success({ attrs: {}, type })
return success(Object.keys(attrs.value).length === 0 ? { type } : { attrs: attrs.value, type })
}
export function inlineMarkSpellingFault(name: string): ConvertFault | undefined {
const spelling = markSpelling(name)
if (spelling === undefined) return undefined
const directive = `${directivePrefix}${name}[…]`
const forms = spelling.kind === 'link' ? `[x](url) or ${directive}` : (markdownForm(spelling) ?? directive)
return { code: 'unsupported-node-shape', message: `${name} is spelled ${forms}, never as a block directive` }
}
function markdownForm(spelling: MarkSpelling): string | undefined {
switch (spelling.kind) {
case 'code':
return '`x`'
case 'directive':
case 'link':
return undefined
case 'emphasis':
return `${spelling.spelling}x${spelling.spelling}`
}
}
-155
View File
@@ -1,155 +0,0 @@
import type { AdfAttributes, AdfMark, AdfNode, EmptyKey } from '../../adf/document.ts'
import type { BlockNodeModel } from '../../adf/block-nodes.ts'
import type { ConvertFault } from '../../result.ts'
import type { DirectiveAttributes, DirectiveValue } from '../directive-syntax.ts'
import type { Elsewhere } from './directive-attributes.ts'
import { attributeNestingMessage, isUnmarkedSpellableText } from '../../adf/document.ts'
import { attributePairCause, attributeValue, directivePrefix, readAttributes, readDirectiveName, unknownDirectiveFault } from '../directive-syntax.ts'
import { blockArgument, blockDirectiveForm, marksAttribute, readMarkValues, spellBlockMarks } from '../block-directive.ts'
import { blockNodeModel } from '../../adf/block-nodes.ts'
import { carryFencePrefix, carryName } from '../opaque-carry.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { inlineMarkSpellingFault, readMarkAttributes } from './directive-marks.ts'
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
import { markVocabularies } from '../../adf/mark-attributes.ts'
import { readEmptyKeys, spellsEmpty } from '../empty-keys.ts'
import { readVocabulary } from './directive-attributes.ts'
import { slotFault } from '../directive-syntax.ts'
import { textBreakName } from '../adjacent-text.ts'
import { textDirectiveName } from '../text-directive.ts'
export type BlockDirectiveNode = { contentModel: BlockNodeModel['contentModel']; node: AdfNode }
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 ${carryFencePrefix} fence`, path)
}
const model = blockNodeModel(name)
if (model === undefined) return faulted(inlineSpellingFault(name) ?? unknownDirectiveFault(name), path)
const argumentKey = blockArgument(name)
const spelled = attributes.get(marksAttribute)
const empty = readEmptyKeys(name, attributes, spellsEmpty(spelled) ? ['attrs', 'content', 'marks'] : ['attrs', 'content'], argument !== undefined)
if (empty.fault !== undefined) return faulted(empty.fault, path)
const { rest } = empty.value
rest.delete(marksAttribute)
const elsewhere: Elsewhere | undefined = argumentKey === undefined ? undefined : { key: argumentKey, slot: 'argument' }
const attrs = readVocabulary(name, rest, model.attributes, elsewhere, path)
if (!attrs.ok) return attrs
if (argument !== undefined) {
if (argumentKey === undefined) return failure('unsupported-node-shape', `${name} takes no argument: this one spells one`, path)
attrs.value[argumentKey] = argument
}
const marks: Result<AdfMark[] | undefined> = spelled === undefined || spellsEmpty(spelled) ? success(undefined) : readMarks(name, spelled, path)
if (!marks.ok) return marks
return success({ contentModel: model.contentModel, node: namedNode(name, attrs.value, marks.value, empty.value.empty) })
}
export function readInlineDirectiveNode(
name: string,
attributes: DirectiveAttributes,
content: readonly AdfNode[] | undefined,
path: ConvertErrorPath,
): Result<AdfNode> {
const model = inlineNodeModel(name)
if (model === undefined) return faulted(blockSpellingFault(name) ?? unknownDirectiveFault(name), path)
const slot = model.textAttribute
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 empty = readEmptyKeys(name, attributes, ['attrs', 'content', 'marks'], slot !== undefined && content !== undefined)
if (empty.fault !== undefined) return faulted(empty.fault, path)
const attrs = readVocabulary(name, empty.value.rest, model.attributes, elsewhere, path)
if (!attrs.ok) return attrs
if (slot !== undefined && content !== undefined) {
const text = slotText(content)
if (text === undefined) {
return failure('unsupported-node-shape', `the ${name} content slot holds one text node carrying neither marks, attributes nor content: this one holds something else`, path)
}
const fault = slotFault(name, text)
if (fault !== undefined) return faulted(fault, path)
attrs.value[slot] = text
}
return success(namedNode(name, attrs.value, undefined, empty.value.empty))
}
// A name the other position spells names that spelling, never the code a later MINOR may fill (docs/decisions.md §Which code a cause takes).
function inlineSpellingFault(name: string): ConvertFault | undefined {
const mark = inlineMarkSpellingFault(name)
if (mark !== undefined) return mark
if (inlineNodeModel(name) === undefined && name !== textDirectiveName && name !== textBreakName) return undefined
return { code: 'unsupported-node-shape', message: `${name} takes the inline form, ${directivePrefix}${name}{…}, never the block form` }
}
function blockSpellingFault(name: string): ConvertFault | undefined {
if (blockDirectiveForm(name) === undefined) return undefined
return { code: 'unsupported-node-shape', message: `${name} takes the block form, ${directivePrefix}${name}, never the inline form` }
}
// spec/flavour.md, Inline nodes: the slot is plain text, its adjacent nodes already merged.
function slotText(content: readonly AdfNode[]): string | undefined {
if (content.length === 0) return ''
const only = content.length === 1 ? content[0] : undefined
if (only === undefined || !isUnmarkedSpellableText(only) || typeof only.text !== 'string') return undefined
return only.text
}
function readMarks(type: string, spelled: DirectiveValue, path: ConvertErrorPath): Result<AdfMark[]> {
const marks = spelled.decoded.startsWith('[') ? readMarkArray(type, spelled.decoded, path) : readNamedMarks(type, spelled.decoded, path)
if (!marks.ok) return marks
const spelling = spellBlockMarks(marks.value)
return spelling === spelled.spelling ? marks : failure('unsupported-node-shape', `${type} spells its ${marksAttribute} attribute as ${marksAttribute}=${spelling}`, path)
}
function readMarkArray(type: string, text: string, path: ConvertErrorPath): Result<AdfMark[]> {
const read = attributeValue(text, 'json')
if (read.refusal === 'nesting') return failure('unsupported-nesting-depth', attributeNestingMessage(marksAttribute, type), path)
const marks = read.value === undefined ? undefined : readMarkValues(read.value.value)
if (marks === undefined) {
return failure('unsupported-node-shape', `the ${marksAttribute} attribute of ${type} is its marks array in canonical JSON: this one is not`, path)
}
return success(marks)
}
function readNamedMarks(type: string, text: string, path: ConvertErrorPath): Result<AdfMark[]> {
const marks: AdfMark[] = []
let cursor = 0
for (;;) {
const mark = readNamedMark(type, text, cursor, path)
if (!mark.ok) return mark
marks.push(mark.value.mark)
cursor = mark.value.end
if (cursor === text.length) return success(marks)
if (text.charAt(cursor) !== ' ') return failure('unsupported-node-shape', unnamedMarks(type), path)
cursor += 1
}
}
function readNamedMark(type: string, text: string, index: number, path: ConvertErrorPath): Result<{ end: number; mark: AdfMark }> {
const name = readDirectiveName(text, index)
if (name === undefined) return failure('unsupported-node-shape', unnamedMarks(type), path)
const vocabulary = markVocabularies.get(name)
if (vocabulary === undefined) return failure('unsupported-node-shape', `the ${marksAttribute} attribute of ${type} names ${name}, no mark the flavour lists: write the marks as their JSON array, marks="[{\\"type\\":\\"${name}\\"}]"`, path)
const start = index + name.length
// These {attrs} sit inside the quoted marks value, where no directive opens, so a malformed pair takes no escape hint.
const attributes = text.charAt(start) === '{' ? readAttributes(text, start, attributePairCause) : { value: { attributes: new Map<string, DirectiveValue>(), end: start } }
if (attributes.fault !== undefined) {
return failure('unsupported-node-shape', `the ${name} mark in the ${marksAttribute} attribute of ${type}: ${attributes.fault.message}`, path)
}
const mark = readMarkAttributes(name, attributes.value.attributes, vocabulary, path)
return mark.ok ? success({ end: attributes.value.end, mark: mark.value }) : mark
}
function unnamedMarks(type: string): string {
return `the ${marksAttribute} attribute of ${type} names its marks one space apart, each as its type and any {attrs}: this one does not`
}
function namedNode(type: string, attrs: AdfAttributes, marks: readonly AdfMark[] | undefined, empty: ReadonlySet<EmptyKey>): AdfNode {
const node: AdfNode = Object.keys(attrs).length > 0 || empty.has('attrs') ? { attrs, type } : { type }
if (empty.has('content')) node.content = []
if (marks !== undefined || empty.has('marks')) node.marks = [...(marks ?? [])]
return node
}