mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-12 11:48:29 +01:00
Answer architecture round 1: closeLeaf records the block definitions end beside the blocks it pushes, openedList's comment names the exception, the README drops its counts, the changelog puts other changes last, and item 98 files the blank-line re-scan
This commit is contained in:
+6
-6
@@ -6,8 +6,6 @@
|
||||
CommonMark reads it: `- a` then `* b`, `1. a` then `1) b`. `adfToLosslessMarkdown` gives a list
|
||||
right after one of its kind the other marker, `* ` or `1)`, in place of the `::listBreak`
|
||||
separator. Convert stored markdown per `MIGRATION.md`.
|
||||
- **Breaking:** two lists a link reference definition parts read as two lists, as CommonMark
|
||||
reads them, where they read as one; see `MIGRATION.md`.
|
||||
- **Breaking:** each call names the markdown it reads or writes. `adfToMarkdown` and `markdownToAdf`
|
||||
are now `adfToLosslessMarkdown` and `losslessMarkdownToAdf`. A bot's or a model's markdown reads
|
||||
in `portableMarkdownToAdf`, and markdown known to be strict CommonMark in the new
|
||||
@@ -58,6 +56,8 @@
|
||||
`losslessMarkdownToAdf` refuses a directive mark wrapping only hard breaks as
|
||||
`unsupported-node-shape`. `adfToLosslessMarkdown` writes a hard break holding marks as
|
||||
`!adf:carry{json="…"}`. See `MIGRATION.md`.
|
||||
- **Breaking:** two lists a link reference definition parts read as two lists, as CommonMark
|
||||
reads them, where `0.1.0` read one; see `MIGRATION.md`.
|
||||
- `adfToLosslessMarkdown` and `adfToPortableMarkdown` write text holding a long run of `*`, `_`, `~`
|
||||
or backticks, or a heading ending in a long run of `#`, in time linear in the run: a run of 20000
|
||||
`*` took seconds before, and now takes milliseconds.
|
||||
@@ -79,16 +79,16 @@
|
||||
- Fix a cyclic document, in which an object holds itself: `adfToLosslessMarkdown` and
|
||||
`adfToPortableMarkdown` refuse it as `not-an-adf-document` and `isAdfDocument` returns `false`,
|
||||
where all three hung.
|
||||
- The README opens with what the package does and for whom, and `package.json` carries search
|
||||
keywords.
|
||||
- The source moves to [github.com/larvit/adf-codec](https://github.com/larvit/adf-codec), where
|
||||
issues and pull requests go.
|
||||
- Add `adfToPortableMarkdown` and `portableMarkdownToAdf`, a lossy pair converting ADF to and from
|
||||
markdown GitHub, GitLab and Obsidian render: alerts, callouts, task lists, `==highlights==` and
|
||||
pipe tables. A task item keeps its paragraphs, then its nested task lists; the rest of the item,
|
||||
such as an image or a code block, stands after the task list.
|
||||
- Spell `rule`'s `color`, `style` and `weight`, `layoutSection`'s `columnRuleStyle` and a link's
|
||||
`collection`, `id` and `occurrenceKey` directly where they rode the opaque carry.
|
||||
- The README opens with what the package does and for whom, and `package.json` carries search
|
||||
keywords.
|
||||
- The source moves to [github.com/larvit/adf-codec](https://github.com/larvit/adf-codec), where
|
||||
issues and pull requests go.
|
||||
|
||||
## 0.1.0
|
||||
|
||||
|
||||
@@ -239,8 +239,8 @@ Serves Goals 1, 4 and 5.
|
||||
without a delimiter row, is an error result; each can be kept literal (`spec/flavour.md`).
|
||||
- A document any reader built, written back with `adfToLosslessMarkdown`, takes the library's
|
||||
canonical spelling, which round-trips byte-identically.
|
||||
- Six CommonMark spellings parse without an error and build a document the reference
|
||||
implementation renders differently. Four because ADF holds no node for what it renders:
|
||||
- These CommonMark spellings parse without an error and build a document the reference
|
||||
implementation renders differently, where ADF holds no node for what it renders:
|
||||
- An empty link text reads as its destination, `[](/url)` as `/url` linked to `/url`, and `[]()`
|
||||
as nothing. A link text holding only hard breaks reads the same, followed by its hard breaks.
|
||||
- An image reads as an image only alone in its paragraph. Anywhere else — beside other text, in
|
||||
@@ -253,11 +253,12 @@ Serves Goals 1, 4 and 5.
|
||||
- An image's title becomes its caption when the image stays an image, and its link's title when
|
||||
it becomes linked alt text. Inside another link, the title is dropped.
|
||||
|
||||
And two more: a shortcut reference matching its definition only under Unicode case folding
|
||||
stays unresolved, and a link whose text holds an autolink keeps the inner link and leaves the
|
||||
outer brackets literal text, which the spec requires and the reference itself breaks, nesting one
|
||||
`<a>` in the other. `corpus/commonmark-spec/exceptions.json` pins the first four `node-model`
|
||||
where the suite reaches them and the fifth `pending`; the suite holds no example of the last.
|
||||
And these: a shortcut reference matching its definition only under Unicode case folding stays
|
||||
unresolved, and a link whose text holds an autolink keeps the inner link and leaves the outer
|
||||
brackets literal text, which the spec requires and the reference itself breaks, nesting one `<a>`
|
||||
in the other. `corpus/commonmark-spec/exceptions.json` pins the spellings ADF holds no node for
|
||||
`node-model` where the suite reaches them, and the case-folded reference `pending`; the suite
|
||||
holds no example of a link holding an autolink.
|
||||
- Not every document converts back: `adfToLosslessMarkdown` refuses a document nesting past 500
|
||||
levels, and the node shapes the `unsupported-node-shape` row lists. Show the refusal and keep the
|
||||
document read-only; saving markdown you could not produce is the loss the round-trip exists to
|
||||
|
||||
@@ -75,8 +75,8 @@ type ContainerStack = {
|
||||
|
||||
type Walk = ParsedBlocks & {
|
||||
claims: Claims
|
||||
// The block a paragraph of link reference definitions alone last closed after, which ends a list as any block does.
|
||||
definedAfter: Block | undefined
|
||||
// The last block before a paragraph of link reference definitions alone, which ends a list as any block does.
|
||||
endedByDefinitions: Block | undefined
|
||||
leaf: OpenLeaf | undefined
|
||||
leafOpeners: Map<Block[], Map<string, LeafOpener>>
|
||||
position: SourcePosition
|
||||
@@ -91,8 +91,8 @@ export function parseBlocks(markdown: string, claims: Claims): ParsedBlocks {
|
||||
const walk: Walk = {
|
||||
blocks: [],
|
||||
claims,
|
||||
definedAfter: undefined,
|
||||
definitions: new Map(),
|
||||
endedByDefinitions: undefined,
|
||||
leaf: undefined,
|
||||
leafOpeners: new Map(),
|
||||
position: { line: 1, offset: 0 },
|
||||
@@ -249,12 +249,12 @@ function openContainer(walk: Walk, start: ContainerStart): void {
|
||||
walk.stack.push({ blocks, indentation: start.indentation, kind: 'item', list })
|
||||
}
|
||||
|
||||
// An item opening after its list's last item closed, as an empty item a blank line ends does, continues that list.
|
||||
// An item opening after its list's last item closed, as an empty item a blank line ends does, continues that list, unless definitions ended it.
|
||||
function openedList(walk: Walk, start: Extract<ContainerStart, { kind: 'item' }>): ListBlock {
|
||||
if (!start.fresh) return start.list
|
||||
const blocks = currentBlocks(walk)
|
||||
const previous = blocks.at(-1)
|
||||
if ((previous?.kind === 'bulletList' || previous?.kind === 'orderedList') && previous !== walk.definedAfter && continuesList(previous, start.list)) return previous
|
||||
if ((previous?.kind === 'bulletList' || previous?.kind === 'orderedList') && previous !== walk.endedByDefinitions && continuesList(previous, start.list)) return previous
|
||||
blocks.push(start.list)
|
||||
return start.list
|
||||
}
|
||||
@@ -466,7 +466,8 @@ function closeLeaf(walk: Walk): void {
|
||||
if (leaf === undefined) return
|
||||
if (leaf.kind === 'paragraph') {
|
||||
const paragraph = takeParagraph(walk)
|
||||
if (paragraph !== undefined) currentBlocks(walk).push(bareTableFault(paragraph, walk.claims) ?? paragraph)
|
||||
if (paragraph === undefined) walk.endedByDefinitions = currentBlocks(walk).at(-1)
|
||||
else currentBlocks(walk).push(bareTableFault(paragraph, walk.claims) ?? paragraph)
|
||||
return
|
||||
}
|
||||
walk.leaf = undefined
|
||||
@@ -517,10 +518,7 @@ function takeParagraph(walk: Walk): Extract<Block, { kind: 'paragraph' }> | unde
|
||||
if (leaf?.kind !== 'paragraph') return undefined
|
||||
walk.leaf = undefined
|
||||
const text = readLinkDefinitions(walk.definitions, leaf.lines.join('\n'))
|
||||
if (text === '') {
|
||||
walk.definedAfter = currentBlocks(walk).at(-1)
|
||||
return undefined
|
||||
}
|
||||
if (text === '') return undefined
|
||||
const kept = leaf.positions[leaf.lines.length - text.split('\n').length]
|
||||
return { kind: 'paragraph', position: kept ?? leaf.position, text }
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
`Bar = 9`
|
||||
|
||||
`Next ID = 98`
|
||||
`Next ID = 99`
|
||||
|
||||
| Goal | W |
|
||||
|---|---|
|
||||
@@ -35,6 +35,7 @@
|
||||
| 96 | 0.2.1 | defect | **Read and write a text holding many unclosed backtick runs, or many `<!--`, `<?`, `<![CDATA[` or `<!` openers without a terminator, in time linear in the text.** | 2 | 3 | 5 | 9 | 9 | 9.2 |
|
||||
| 92 | 0.2.1 | | **Lower the reading cost of the files a chunk reads most before its first commit.** | 2 | 4 | 2 | 4 | 1 | 9.0 |
|
||||
| 33 | 0.2.1 | defect | **Emit a line in time linear in its mark runs, in `adfToLosslessMarkdown` and `adfToPortableMarkdown`.** | 4 | 5 | 6 | 9 | 9 | 8.7 |
|
||||
| 98 | 0.2.1 | defect | **Read blank lines inside deeply nested list items in time linear in the markdown.** | 3 | 3 | 5 | 9 | 9 | 8.2 |
|
||||
| 7 | 0.3.0 | decision | **Ship HTML: `adfToHtml`, `htmlToAdf`, and markdown to and from HTML composed through ADF, each call named by its flavour.** | 6 | 9 | 9 | 9 | 2, 4 | 25.8 |
|
||||
| 45 | 0.3.0 | | **Replace `isAdfDocument` with a reader returning `Result<AdfDocument>`.** | 2 | 3 | 6 | 8 | 1 | 25.2 |
|
||||
| 6 | 0.3.0 | decision | **Specify the HTML dialect.** | 2 | 6 | 7 | 8 | 2, 4 | 24.7 |
|
||||
@@ -143,6 +144,15 @@ the portable reduction's `spellableLine` drops one mark per re-emit the same way
|
||||
whose attributes no spelling writes re-emits the line the same way before riding the carry. Make all
|
||||
three linear.
|
||||
|
||||
### 98. Read blank lines inside deeply nested list items in time linear in the markdown.
|
||||
|
||||
`matchContainers` in `src/markdown/parse/blocks.ts` continues every open item holding a block on
|
||||
each blank line, and `parseBlocks` holds no depth guard: `readBlocks` refuses past 500 levels only
|
||||
after the walk. `'- '.repeat(n) + 'a\n' + '\n'.repeat(n)` took 675 ms at n = 5000, 2.7 s at 10000
|
||||
and 10.7 s at 20000 (60 kB) before refusing as `unsupported-nesting-depth`. `docs/decisions.md`
|
||||
§Cost fixes are measured, never timed calls the re-scan bounded by that guard; correct it with the
|
||||
fix.
|
||||
|
||||
### 7. Ship HTML: `adfToHtml`, `htmlToAdf`, and markdown to and from HTML composed through ADF, each call named by its flavour.
|
||||
|
||||
Lands after items 6, 59, 60 and 62. Which flavours get a composed call is this item's to settle;
|
||||
@@ -344,7 +354,8 @@ quadratic in a run of blanks inside one leaf: a paragraph of `a`, 80 000 spaces,
|
||||
### 56. Give each piece of `blocks.ts`'s block-walk state and `inline-content.ts`'s `Scan` one owner that returns what it changes.
|
||||
|
||||
Technical principle "One owner per value": the `Walk` record passes through twelve functions that
|
||||
mutate it and return `void`; `walk.leaf` alone is written in seven places. `ContainerStack` in the
|
||||
mutate it and return `void`; `walk.leaf` alone is written in seven places, and `openedList` reads
|
||||
the `endedByDefinitions` that `closeLeaf` leaves. `ContainerStack` in the
|
||||
same file shows the shape to follow. `inline-content.ts`'s `Scan` has the same shape: about fifteen
|
||||
functions write `pending`, `pieces`, `deactivatedBefore` and `openingSpellableLink` and return
|
||||
`void`, and `parseInlineContent` reads a flag `scanInline` leaves on it. The same shape recurs in
|
||||
|
||||
Reference in New Issue
Block a user