Read the node tables backwards, a directive to the node and marks it names #36
@@ -172,7 +172,8 @@ both answer to the round-trip corpus and to nothing else where a node has no fix
|
|||||||
directions must answer alike — whether a list marker interrupts a paragraph — is one function
|
directions must answer alike — whether a list marker interrupts a paragraph — is one function
|
||||||
there too, never a copy per direction, however conservative the copy would be. Where the rule is
|
there too, never a copy per direction, however conservative the copy would be. Where the rule is
|
||||||
the emitter's own choice, input consults it rather than restating it: the parser asks
|
the emitter's own choice, input consults it rather than restating it: the parser asks
|
||||||
`spellsCommonMark` which form the emitter picks, so no fixture the emitter writes can be refused.
|
`commonMarkSpelling` which form the emitter picks, so no fixture the emitter writes can be
|
||||||
|
refused, and a spelling the emitter refuses gives its own error rather than a second name for it.
|
||||||
- The attribute vocabulary is ADF's: `adf/` walks it and narrows each value to its kind, and a
|
- The attribute vocabulary is ADF's: `adf/` walks it and narrows each value to its kind, and a
|
||||||
format spells the narrowed value. A spelling that re-checks the type is the check's second copy.
|
format spells the narrowed value. A spelling that re-checks the type is the check's second copy.
|
||||||
Reading a spelling back is the format's own: the reader sits beside the spelling it inverts, so
|
Reading a spelling back is the format's own: the reader sits beside the spelling it inverts, so
|
||||||
|
|||||||
+2
-2
@@ -157,8 +157,8 @@ included, is an error result naming it. The flavour never emits raw HTML.
|
|||||||
The directive name is always the ADF node type. A container's body is the node's `content`; a
|
The directive name is always the ADF node type. A container's body is the node's `content`; a
|
||||||
leaf has none. Every directive parses in any position — `markdownToAdf` builds exactly what is
|
leaf has none. Every directive parses in any position — `markdownToAdf` builds exactly what is
|
||||||
written; validity against ADF's content models stays the author's business (AGENTS.md §14). It
|
written; validity against ADF's content models stays the author's business (AGENTS.md §14). It
|
||||||
parses only in the form the emitter picks, though: a directive spelling a node CommonMark holds
|
parses only in the form the emitter picks, though: a directive spelling a node the emitter would
|
||||||
is a named error, the per-node plain-versus-directive choice below read backwards.
|
have written as CommonMark is a named error.
|
||||||
|
|
||||||
Each section lists attributes as `name (type)`. A parenthesized value set documents what real
|
Each section lists attributes as `name (type)`. A parenthesized value set documents what real
|
||||||
payloads hold; the type stays string and any value round-trips verbatim. Values map to attrs by
|
payloads hold; the type stays string and any value round-trips verbatim. Values map to attrs by
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ export function unknownDirectiveFault(name: string): ConvertFault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function keyOrder(left: string, right: string): number {
|
function keyOrder(left: string, right: string): number {
|
||||||
return left < right ? -1 : 1
|
if (left < right) return -1
|
||||||
|
return left > right ? 1 : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function quote(text: string): string {
|
function quote(text: string): string {
|
||||||
|
|||||||
@@ -86,9 +86,10 @@ function emitBlock(node: AdfNode, path: ConvertErrorPath, depth: number): Result
|
|||||||
return emitDirectiveBlock(node, directive, path, depth)
|
return emitDirectiveBlock(node, directive, path, depth)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The plain-versus-directive choice is the emitter's; input reads it back rather than restating it (AGENTS.md §11).
|
export function commonMarkSpelling(node: AdfNode, path: ConvertErrorPath, depth: number): Result<null> | undefined {
|
||||||
export function spellsCommonMark(node: AdfNode, path: ConvertErrorPath, depth: number): boolean {
|
const readable = readableBlock(node, path, depth)
|
||||||
return readableBlock(node, path, depth) !== undefined
|
if (readable === undefined) return undefined
|
||||||
|
return readable.ok ? success(null) : readable
|
||||||
}
|
}
|
||||||
|
|
||||||
function readableBlock(node: AdfNode, path: ConvertErrorPath, depth: number): Result<EmittedBlock> | undefined {
|
function readableBlock(node: AdfNode, path: ConvertErrorPath, depth: number): Result<EmittedBlock> | undefined {
|
||||||
|
|||||||
@@ -148,7 +148,6 @@ test('reads the three directive forms into the nodes the tables name', () => {
|
|||||||
assert.deepEqual(content(markdownToAdf('Part:hardBreak{}.\n')), [{ content: [text('Part'), hardBreak(), text('.')], type: 'paragraph' }])
|
assert.deepEqual(content(markdownToAdf('Part:hardBreak{}.\n')), [{ content: [text('Part'), hardBreak(), text('.')], type: 'paragraph' }])
|
||||||
})
|
})
|
||||||
|
|
||||||
// The emitter's plain-versus-directive choice, read backwards: only the form it picks parses.
|
|
||||||
test('names the directive form a node CommonMark spells refuses', () => {
|
test('names the directive form a node CommonMark spells refuses', () => {
|
||||||
const named = (type: string): string => `unsupported-node-shape: ${type} takes the CommonMark spelling, not the directive form`
|
const named = (type: string): string => `unsupported-node-shape: ${type} takes the CommonMark spelling, not the directive form`
|
||||||
assert.equal(content(markdownToAdf('::rule\n')), named('rule'))
|
assert.equal(content(markdownToAdf('::rule\n')), named('rule'))
|
||||||
@@ -160,6 +159,12 @@ test('names the directive form a node CommonMark spells refuses', () => {
|
|||||||
assert.deepEqual(content(markdownToAdf('::::bulletList\n:::listItem\n---\n:::\n::::\n')), [bulletList(item({ type: 'rule' }))])
|
assert.deepEqual(content(markdownToAdf('::::bulletList\n:::listItem\n---\n:::\n::::\n')), [bulletList(item({ type: 'rule' }))])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// The spelling the emitter refuses gives the emitter's own error, never a second name for it.
|
||||||
|
test('gives back the refusal the CommonMark spelling itself raises', () => {
|
||||||
|
const nested = '::::::::bulletList\n:::::::listItem\n---\n\n::::::bulletList\n:::::listItem\n---\n\n::::bulletList\n:::listItem\n---\n:::\n::::\n:::::\n::::::\n:::::::\n::::::::\n'
|
||||||
|
assert.equal(code(markdownToAdf(nested)), 'unspelled-block-separation')
|
||||||
|
})
|
||||||
|
|
||||||
test('names the directive name no node reads back to', () => {
|
test('names the directive name no node reads back to', () => {
|
||||||
assert.equal(code(markdownToAdf(':::widget info\nx\n:::\n')), 'unknown-directive-name')
|
assert.equal(code(markdownToAdf(':::widget info\nx\n:::\n')), 'unknown-directive-name')
|
||||||
assert.equal(content(markdownToAdf('::widget\n')), 'unknown-directive-name: the directive name widget reads back to no node')
|
assert.equal(content(markdownToAdf('::widget\n')), 'unknown-directive-name: the directive name widget reads back to no node')
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import type { Block, DirectiveBlock } from './blocks.ts'
|
|||||||
import type { BlockDirectiveNode } from './directive-nodes.ts'
|
import type { BlockDirectiveNode } from './directive-nodes.ts'
|
||||||
import type { LinkDefinitions } from './inline-content.ts'
|
import type { LinkDefinitions } from './inline-content.ts'
|
||||||
import { carryName } from '../opaque-carry.ts'
|
import { carryName } from '../opaque-carry.ts'
|
||||||
|
import { commonMarkSpelling } from '../emit/adf-to-markdown.ts'
|
||||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||||
import { largestNesting } from '../../nesting.ts'
|
import { largestNesting } from '../../nesting.ts'
|
||||||
import { parseBlocks } from './blocks.ts'
|
import { parseBlocks } from './blocks.ts'
|
||||||
import { parseInlineContent } from './inline-content.ts'
|
import { parseInlineContent } from './inline-content.ts'
|
||||||
import { readBlockDirectiveNode } from './directive-nodes.ts'
|
import { readBlockDirectiveNode } from './directive-nodes.ts'
|
||||||
import { spellsCommonMark } from '../emit/adf-to-markdown.ts'
|
|
||||||
|
|
||||||
export function markdownToAdf(markdown: string): Result<AdfDocument> {
|
export function markdownToAdf(markdown: string): Result<AdfDocument> {
|
||||||
const parsed = parseBlocks(markdown)
|
const parsed = parseBlocks(markdown)
|
||||||
@@ -58,11 +58,11 @@ function directiveNode(block: DirectiveBlock, definitions: LinkDefinitions, path
|
|||||||
if (!read.ok) return read
|
if (!read.ok) return read
|
||||||
const built = directiveBody(read.value, block.blocks, definitions, path, depth)
|
const built = directiveBody(read.value, block.blocks, definitions, path, depth)
|
||||||
if (!built.ok) return built
|
if (!built.ok) return built
|
||||||
if (spellsCommonMark(built.value, path, depth)) {
|
const readable = commonMarkSpelling(built.value, path, depth)
|
||||||
|
if (readable === undefined) return built
|
||||||
|
if (!readable.ok) return readable
|
||||||
return failure('unsupported-node-shape', `${built.value.type} takes the CommonMark spelling, not the directive form`, path)
|
return failure('unsupported-node-shape', `${built.value.type} takes the CommonMark spelling, not the directive form`, path)
|
||||||
}
|
}
|
||||||
return built
|
|
||||||
}
|
|
||||||
|
|
||||||
function directiveBody(read: BlockDirectiveNode, blocks: Block[] | undefined, definitions: LinkDefinitions, path: ConvertErrorPath, depth: number): Result<AdfNode> {
|
function directiveBody(read: BlockDirectiveNode, blocks: Block[] | undefined, definitions: LinkDefinitions, path: ConvertErrorPath, depth: number): Result<AdfNode> {
|
||||||
const { contentModel, node } = read
|
const { contentModel, node } = read
|
||||||
|
|||||||
@@ -335,6 +335,9 @@ detail is settled at its own milestone.
|
|||||||
`corpus.test.ts` too, and `unspelled-block-separation`, which loses its only cause here.
|
`corpus.test.ts` too, and `unspelled-block-separation`, which loses its only cause here.
|
||||||
The emitter's other refusals survive on causes no fixture in that directory covers, so
|
The emitter's other refusals survive on causes no fixture in that directory covers, so
|
||||||
3k's one-list pass is where they get fixtures or the directory goes.
|
3k's one-list pass is where they get fixtures or the directory goes.
|
||||||
|
Losing that cause closes the last shape input accepts and emit refuses — a CommonMark block
|
||||||
|
beside a directive one inside a list item — so the parse-then-emit fixpoint the README
|
||||||
|
promises holds from here rather than only for what the emitter wrote.
|
||||||
- [ ] **3i — The inline nodes and the marks.** `inline-nodes/` reads back: the content slot's
|
- [ ] **3i — The inline nodes and the marks.** `inline-nodes/` reads back: the content slot's
|
||||||
`text` attribute and the error a slot holding anything but one unmarked text node is; the
|
`text` attribute and the error a slot holding anything but one unmarked text node is; the
|
||||||
`:text{text="…"}` whitespace spelling; the four directive marks and their nesting order,
|
`:text{text="…"}` whitespace spelling; the four directive marks and their nesting order,
|
||||||
|
|||||||
Reference in New Issue
Block a user