Read the block nodes back, and stop a marker change splitting a list #40
+2
-1
@@ -6,7 +6,8 @@ One directory per contract kind, each landing with its milestone:
|
|||||||
document, byte for byte, and that `markdownToAdf` must read back to it (AGENTS.md §2). Grouped
|
document, byte for byte, and that `markdownToAdf` must read back to it (AGENTS.md §2). Grouped
|
||||||
by what the fixture exercises.
|
by what the fixture exercises.
|
||||||
- `normalization/` — `<name>.md` + `<name>.json`: markdown input, and the document
|
- `normalization/` — `<name>.md` + `<name>.json`: markdown input, and the document
|
||||||
`markdownToAdf` must build from it. One-way; the markdown is not canonical.
|
`markdownToAdf` must build from it, which must in turn emit and read back to itself. The
|
||||||
|
markdown is not canonical.
|
||||||
- `errors/` — `<name>.md`: markdown input that must not convert. A `<name>.error` beside it
|
- `errors/` — `<name>.md`: markdown input that must not convert. A `<name>.error` beside it
|
||||||
pins which error.
|
pins which error.
|
||||||
- `real-payloads/` — `<name>.json`: sanitized live ADF, round-tripped ADF→markdown→ADF. No
|
- `real-payloads/` — `<name>.json`: sanitized live ADF, round-tripped ADF→markdown→ADF. No
|
||||||
|
|||||||
@@ -223,6 +223,18 @@ export function setextHeadingLevel(line: string): number | undefined {
|
|||||||
return underline.startsWith('=') ? 1 : 2
|
return underline.startsWith('=') ? 1 : 2
|
||||||
}
|
}
|
||||||
|
|
||||||
export function trimSpace(text: string): string {
|
function spaceOrTab(character: string): boolean {
|
||||||
return text.replace(/^[ \t]+|[ \t]+$/g, '')
|
return character === ' ' || character === '\t'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trimSpace(text: string): string {
|
||||||
|
let start = 0
|
||||||
|
while (start < text.length && spaceOrTab(text.charAt(start))) start += 1
|
||||||
|
return trimTrailingSpace(text.slice(start))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trimTrailingSpace(text: string): string {
|
||||||
|
let end = text.length
|
||||||
|
while (end > 0 && spaceOrTab(text.charAt(end - 1))) end -= 1
|
||||||
|
return text.slice(0, end)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { AdfMark, AdfNode } from '../../adf/document.ts'
|
import type { AdfMark, AdfNode } from '../../adf/document.ts'
|
||||||
import type { EmphasisPairing } from '../emphasis-matching.ts'
|
import type { EmphasisPairing } from '../emphasis-matching.ts'
|
||||||
import type { LinkDefinition } from '../link-syntax.ts'
|
import type { LinkDefinition } from '../link-syntax.ts'
|
||||||
import { backslashEscape, decodeTextEscapes, inlineHtmlConstruct, readBracketedAutolink, readEmailAutolink } from '../commonmark-grammar.ts'
|
import { backslashEscape, decodeTextEscapes, inlineHtmlConstruct, readBracketedAutolink, readEmailAutolink, trimTrailingSpace } from '../commonmark-grammar.ts'
|
||||||
import { backtickRun, closingBacktickRun } from '../backtick-runs.ts'
|
import { backtickRun, closingBacktickRun } from '../backtick-runs.ts'
|
||||||
import { delimiterFlags, matchEmphasis, runLength } from '../emphasis-matching.ts'
|
import { delimiterFlags, matchEmphasis, runLength } from '../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'
|
||||||
@@ -28,9 +28,7 @@ type Run = { canClose: boolean; canOpen: boolean; character: string; index: numb
|
|||||||
|
|
||||||
type Scan = { definitions: LinkDefinitions; path: ConvertErrorPath; pending: string; pieces: Piece[]; source: string }
|
type Scan = { definitions: LinkDefinitions; path: ConvertErrorPath; pending: string; pieces: Piece[]; source: string }
|
||||||
|
|
||||||
const hardBreakSpaces = / {2,}$/
|
|
||||||
const imageAlone = 'an image fits only as a paragraph of its own'
|
const imageAlone = 'an image fits only as a paragraph of its own'
|
||||||
const trailingSpace = /[ \t]+$/
|
|
||||||
|
|
||||||
export function parseInlineContent(source: string, definitions: LinkDefinitions, path: ConvertErrorPath): Result<InlineContent> {
|
export function parseInlineContent(source: string, definitions: LinkDefinitions, path: ConvertErrorPath): Result<InlineContent> {
|
||||||
const scan: Scan = { definitions, path, pending: '', pieces: [], source }
|
const scan: Scan = { definitions, path, pending: '', pieces: [], source }
|
||||||
@@ -95,7 +93,7 @@ function readBackslash(scan: Scan, index: number): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function readLineEnding(scan: Scan, index: number): number {
|
function readLineEnding(scan: Scan, index: number): number {
|
||||||
const hard = hardBreakSpaces.test(scan.pending)
|
const hard = scan.pending.endsWith(' ')
|
||||||
flush(scan, true)
|
flush(scan, true)
|
||||||
if (hard) pushNode(scan, { type: 'hardBreak' })
|
if (hard) pushNode(scan, { type: 'hardBreak' })
|
||||||
else scan.pending = ' '
|
else scan.pending = ' '
|
||||||
@@ -154,7 +152,7 @@ function readDirective(scan: Scan, index: number): Result<number> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function flush(scan: Scan, strip: boolean): void {
|
function flush(scan: Scan, strip: boolean): void {
|
||||||
const raw = strip ? scan.pending.replace(trailingSpace, '') : scan.pending
|
const raw = strip ? trimTrailingSpace(scan.pending) : scan.pending
|
||||||
scan.pending = ''
|
scan.pending = ''
|
||||||
if (raw !== '') scan.pieces.push({ kind: 'nodes', nodes: [{ text: decodeTextEscapes(raw), type: 'text' }] })
|
if (raw !== '') scan.pieces.push({ kind: 'nodes', nodes: [{ text: decodeTextEscapes(raw), type: 'text' }] })
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-26
@@ -281,32 +281,7 @@ Under **3 — `markdownToAdf` (`0.1.0`)**:
|
|||||||
`spec/flavour.md`'s closing-fence sentence now says: a run reaching past the innermost
|
`spec/flavour.md`'s closing-fence sentence now says: a run reaching past the innermost
|
||||||
leaves the fence it did not close a named error, which §2 prefers to closing more than the
|
leaves the fence it did not close a named error, which §2 prefers to closing more than the
|
||||||
author wrote.
|
author wrote.
|
||||||
- [x] **3h — The block nodes.** `block-nodes/` reads back: the `codeBlock` directive's fenced body and the
|
- [x] **3g — The node tables read backwards.** `commonmark-subset/` reads back, the first
|
||||||
`language` attribute a bare fence leaves it; the media family's composition; and both
|
|
||||||
table forms, the pipe table's cell split and its named errors. `fenceInfo` is a rule both
|
|
||||||
directions answer alike and moves to the `markdown/` root with the language attribute.
|
|
||||||
**Settled** (the maintainer, 2026-08-27): 1d's last pick, the one
|
|
||||||
`container-block-separation` holds — a CommonMark block and a directive block sit adjacent
|
|
||||||
in a container body with no blank line between them. That reduces the three cases to one
|
|
||||||
rule, separation only where its absence would merge the blocks: the `:::` fence is
|
|
||||||
separation already, and 3c's claim ends the lazy continuation that would otherwise swallow
|
|
||||||
it. The fixture becomes a round-trip pair, and with `nested-list-separation` and 3e's pair
|
|
||||||
that empties `corpus/unspellable/`: this chunk settles the directory's own guard in
|
|
||||||
`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
|
|
||||||
3k's one-list pass is where they get fixtures or the directory goes.
|
|
||||||
**Settled** (the maintainer, 2026-09-01): losing that cause closed one of the shapes input
|
|
||||||
accepted and emit refused, not the last. Two adjacent lists of a kind are what
|
|
||||||
`adfToMarkdown` refuses and one `- ` spelling cannot hold apart, and the walk reached them
|
|
||||||
two ways — a marker change, which CommonMark opens a second list on, and an empty last item,
|
|
||||||
whose blank line pops the container the list's identity hung from. The parser opens no list
|
|
||||||
beside one of its own kind instead, the way it already drops the blank lines between items;
|
|
||||||
3k owes the CommonMark suite an exception where the reference HTML holds two `<ul>`. The
|
|
||||||
`normalization/` arm emits each document and reads it back from here, so the population that
|
|
||||||
class lives in is checked rather than read. The README's canonical-fixpoint sentence still
|
|
||||||
claims more than the parser keeps — 3e's three shapes — which stays milestone 5's to
|
|
||||||
narrow.
|
|
||||||
- [x] **3g — The node tables read backwards.** `commonmark-subset/` reads back, the first
|
|
||||||
directory to. A parsed directive becomes its node: the name to the type and an unknown one
|
directory to. A parsed directive becomes its node: the name to the type and an unknown one
|
||||||
to a named error, the arg to the attribute it names, each value to the type its section
|
to a named error, the arg to the attribute it names, each value to the type its section
|
||||||
assigns, the body to `content`, the reserved `marks` key to the marks array. 3a's drift
|
assigns, the body to `content`, the reserved `marks` key to the marks array. 3a's drift
|
||||||
@@ -334,3 +309,28 @@ Under **3 — `markdownToAdf` (`0.1.0`)**:
|
|||||||
path and returns `Result`, so no second reader took it. The drift guard earned itself on
|
path and returns `Result`, so no second reader took it. The drift guard earned itself on
|
||||||
the way in: the spec's `text` attribute was missing from three inline table entries, which
|
the way in: the spec's `text` attribute was missing from three inline table entries, which
|
||||||
the content slot spells and the vocabulary walk already passes over.
|
the content slot spells and the vocabulary walk already passes over.
|
||||||
|
- [x] **3h — The block nodes.** `block-nodes/` reads back: the `codeBlock` directive's fenced body and the
|
||||||
|
`language` attribute a bare fence leaves it; the media family's composition; and both
|
||||||
|
table forms, the pipe table's cell split and its named errors. `fenceInfo` is a rule both
|
||||||
|
directions answer alike and moves to the `markdown/` root with the language attribute.
|
||||||
|
**Settled** (the maintainer, 2026-08-27): 1d's last pick, the one
|
||||||
|
`container-block-separation` holds — a CommonMark block and a directive block sit adjacent
|
||||||
|
in a container body with no blank line between them. That reduces the three cases to one
|
||||||
|
rule, separation only where its absence would merge the blocks: the `:::` fence is
|
||||||
|
separation already, and 3c's claim ends the lazy continuation that would otherwise swallow
|
||||||
|
it. The fixture becomes a round-trip pair, and with `nested-list-separation` and 3e's pair
|
||||||
|
that empties `corpus/unspellable/`: this chunk settles the directory's own guard in
|
||||||
|
`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
|
||||||
|
3k's one-list pass is where they get fixtures or the directory goes.
|
||||||
|
**Settled** (the maintainer, 2026-09-01): losing that cause closed one of the shapes input
|
||||||
|
accepted and emit refused, not the last. Two adjacent lists of a kind are what
|
||||||
|
`adfToMarkdown` refuses and one `- ` spelling cannot hold apart, and the walk reached them
|
||||||
|
two ways — a marker change, which CommonMark opens a second list on, and an empty last item,
|
||||||
|
whose blank line pops the container the list's identity hung from. The parser opens no list
|
||||||
|
beside one of its own kind instead, the way it already drops the blank lines between items;
|
||||||
|
3k owes the CommonMark suite an exception where the reference HTML holds two `<ul>`. The
|
||||||
|
`normalization/` arm emits each document and reads it back from here, so the population that
|
||||||
|
class lives in is checked rather than read. The README's canonical-fixpoint sentence still
|
||||||
|
claims more than the parser keeps — 3e's three shapes — which stays milestone 5's to
|
||||||
|
narrow.
|
||||||
|
|||||||
Reference in New Issue
Block a user