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

Answer architecture round 1: 2 🔴, 5 🟡; nesting counts the JSON fallback alone, one mark attrs read and spell, one mark table, a malformed mark is unsupported-node-shape, the old colwidth names the comma list, README and notes name the attrs fallback

This commit is contained in:
2026-10-14 10:09:06 +02:00
committed by Lillem4n
parent 9e9d88a8e4
commit c8ea357fa7
16 changed files with 88 additions and 68 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ export function readVocabulary(
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}`, 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
+9 -4
View File
@@ -1,4 +1,5 @@
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'
@@ -13,12 +14,16 @@ export function readDirectiveMark(name: string, attributes: DirectiveAttributes,
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)
const empty = readEmptyKeys(name, attributes, ['attrs'], false)
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(name, empty.value.rest, spelling.attributes, undefined, 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: name })
return success(Object.keys(attrs.value).length === 0 ? { type: name } : { attrs: attrs.value, type: name })
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 {
+17 -21
View File
@@ -1,5 +1,4 @@
import type { AdfAttributes, AdfMark, AdfNode, EmptyKey } from '../../adf/document.ts'
import type { AttributeVocabulary } from '../../adf/attribute-vocabulary.ts'
import type { BlockNodeModel } from '../../adf/block-nodes.ts'
import type { ConvertFault } from '../../result.ts'
import type { DirectiveAttributes, DirectiveValue } from '../directive-syntax.ts'
@@ -10,9 +9,9 @@ import { blockArgument, blockDirectiveForm, marksAttribute, readMarkValues, spel
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 } from './directive-marks.ts'
import { inlineMarkSpellingFault, readMarkAttributes } from './directive-marks.ts'
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
import { markVocabulary } from '../../adf/mark-attributes.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'
@@ -119,33 +118,30 @@ function readNamedMarks(type: string, text: string, path: ConvertErrorPath): Res
const marks: AdfMark[] = []
let cursor = 0
for (;;) {
const name = readDirectiveName(text, cursor)
if (name === undefined) return failure('unsupported-node-shape', unnamedMarks(type), path)
const vocabulary = markVocabulary(name)
if (vocabulary === undefined) return failure('unsupported-node-shape', `the ${marksAttribute} attribute of ${type} names ${name}, a mark it spells only in its JSON array`, path)
cursor += name.length
const attributes = text.charAt(cursor) === '{' ? readAttributes(text, cursor) : { value: { attributes: new Map<string, DirectiveValue>(), end: cursor } }
if (attributes.fault !== undefined) return faulted(attributes.fault, path)
const mark = readNamedMark(name, attributes.value.attributes, vocabulary, path)
const mark = readNamedMark(type, text, cursor, path)
if (!mark.ok) return mark
marks.push(mark.value)
cursor = attributes.value.end
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 unnamedMarks(type: string): string {
return `the ${marksAttribute} attribute of ${type} names each mark with its {attrs}, one space apart: this one does not`
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}, a mark it spells only in its JSON array`, path)
const start = index + name.length
const attributes = text.charAt(start) === '{' ? readAttributes(text, start) : { value: { attributes: new Map<string, DirectiveValue>(), end: start } }
if (attributes.fault !== undefined) return failure('unsupported-node-shape', unnamedMarks(type), path)
const mark = readMarkAttributes(name, attributes.value.attributes, vocabulary, path)
return mark.ok ? success({ end: attributes.value.end, mark: mark.value }) : mark
}
function readNamedMark(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
return success(Object.keys(attrs.value).length > 0 || empty.value.empty.has('attrs') ? { attrs: attrs.value, type } : { type })
function unnamedMarks(type: string): string {
return `the ${marksAttribute} attribute of ${type} names each mark with its {attrs}, one space apart: this one does not`
}
function namedNode(type: string, attrs: AdfAttributes, marks: readonly AdfMark[] | undefined, empty: ReadonlySet<EmptyKey>): AdfNode {
+9 -3
View File
@@ -556,6 +556,10 @@ test('names the marks key no marks read back from', () => {
assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="{}"}\n')), named)
assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="em strong"}\n')), named)
assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="em "}\n')), named)
assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="breakout{width=1 mode=wide}"}\n')), named)
assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="breakout{mode}"}\n')), named)
assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="breakout{mode=wide"}\n')), named)
assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="em{attrs=empty}strong"}\n')), named)
assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="em{attrs=x}"}\n')), 'unsupported-node-shape: the reserved key attrs takes only the value empty: this one spells attrs=x')
assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks=x}\n')), 'unsupported-node-shape: the marks attribute of rule names x, a mark it spells only in its JSON array')
assert.equal(content(losslessMarkdownToAdf('!adf:rule {marks="breakout{width=wide}"}\n')), 'unsupported-node-shape: the width attribute of breakout is no number')
@@ -577,9 +581,10 @@ test('names the attribute a node holds no reading for', () => {
assert.equal(content(losslessMarkdownToAdf('!adf:media {width=wide}\n')), 'unsupported-node-shape: the width attribute of media is no number')
assert.equal(content(losslessMarkdownToAdf('!adf:table {isNumberColumnEnabled=yes}\n!adf:/table\n')), 'unsupported-node-shape: the isNumberColumnEnabled attribute of table is no boolean')
assert.equal(content(losslessMarkdownToAdf('!adf:media {width=true}\n')), 'unsupported-node-shape: the width attribute of media is no number')
assert.equal(content(losslessMarkdownToAdf('!adf:tableCell {colwidth="340,"}\n!adf:/tableCell\n')), 'unsupported-node-shape: the colwidth attribute of tableCell is no numbers')
assert.equal(content(losslessMarkdownToAdf('!adf:tableCell {colwidth="[340,420]"}\n!adf:/tableCell\n')), 'unsupported-node-shape: the colwidth attribute of tableCell is no numbers')
assert.equal(content(losslessMarkdownToAdf('!adf:tableCell {colwidth=""}\n!adf:/tableCell\n')), 'unsupported-node-shape: the colwidth attribute of tableCell is no numbers')
assert.equal(content(losslessMarkdownToAdf('!adf:tableCell {colwidth="340,"}\n!adf:/tableCell\n')), 'unsupported-node-shape: the colwidth attribute of tableCell is no comma list of numbers')
assert.equal(content(losslessMarkdownToAdf('!adf:tableCell {colwidth="[]"}\n!adf:/tableCell\n')), 'unsupported-node-shape: the colwidth attribute of tableCell is no comma list of numbers')
assert.equal(content(losslessMarkdownToAdf('!adf:tableCell {colwidth="[340,\\"420\\"]"}\n!adf:/tableCell\n')), 'unsupported-node-shape: the colwidth attribute of tableCell is no comma list of numbers')
assert.equal(content(losslessMarkdownToAdf('!adf:tableCell {colwidth=""}\n!adf:/tableCell\n')), 'unsupported-node-shape: the colwidth attribute of tableCell is no comma list of numbers')
assert.equal(content(losslessMarkdownToAdf('!adf:panel info {panelType=note}\nx\n!adf:/panel\n')), 'unsupported-node-shape: panel spells its panelType attribute as the directive argument, never in {attrs}')
assert.equal(content(losslessMarkdownToAdf('Part !adf:mention{id=b1c2 text=A}.\n')), 'unsupported-node-shape: mention spells its text attribute in the content slot, never in {attrs}')
})
@@ -600,6 +605,7 @@ test('names the attribute value spelled outside the canonical form', () => {
assert.equal(content(losslessMarkdownToAdf('!adf:media {width="20.0"}\n')), 'unsupported-node-shape: media spells its width attribute as width=20')
assert.equal(content(losslessMarkdownToAdf('!adf:tableCell {colwidth="340, 420"}\n!adf:/tableCell\n')), 'unsupported-node-shape: tableCell spells its colwidth attribute as colwidth="340,420"')
assert.equal(content(losslessMarkdownToAdf('!adf:tableCell {colwidth="340"}\n!adf:/tableCell\n')), 'unsupported-node-shape: tableCell spells its colwidth attribute as colwidth=340')
assert.equal(content(losslessMarkdownToAdf('!adf:tableCell {colwidth="[340,420]"}\n!adf:/tableCell\n')), 'unsupported-node-shape: tableCell spells its colwidth attribute as colwidth="340,420"')
})
test('names the argument and the body a node takes no reading for', () => {