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

Answer prose round 1: 0 🔴, 7 🟡 (0 misread); reword the sweep's comments, the cost entry and item 160

This commit is contained in:
2026-10-30 01:37:27 +01:00
committed by Lillem4n
parent f151ea5c09
commit 4834e802d9
6 changed files with 26 additions and 22 deletions
+4 -3
View File
@@ -58,9 +58,10 @@
as in `[a](b` repeated: 16000 of them took 25 seconds, now 80 ms.
- `adfToLosslessMarkdown` and `adfToPortableMarkdown` write faster a line of many marked texts or
inline nodes that need a carry or lose their mark, as in text `un` beside strong `-r`, repeated:
1000 of them took 10 seconds, now 150 ms. So does a line mixing em, strike and strong: 1000
`un` beside `-r` marked em and strong took 9 seconds, now 140 ms, and 1000 `(` marked em, strike
and strong beside a mention took 18 seconds in `adfToPortableMarkdown`, now 110 ms.
1000 of them took 10 seconds, now 150 ms. They also write faster a line mixing em, strike and
strong: text `un` beside `-r` marked em and strong, repeated 1000 times, took up to 9 seconds,
now under 170 ms, and `(` marked em, strike and strong beside a mention, repeated 1000 times,
took 18 seconds in `adfToPortableMarkdown`, now 150 ms.
- `adfToLosslessMarkdown` and `adfToPortableMarkdown` take time linear in the markdown when lists
and quotes nest deeply, and so does `losslessMarkdownToAdf`: a list nested 480 levels around
64 KB of paragraphs took 2 seconds to write, now 100 ms. A `!adf:blockquote` holding 2 MB of lines
+1 -1
View File
@@ -10,7 +10,7 @@ test('spells a line alike taking every fallback a pass finds or the first alone'
fc.property(markedLine, lineContainer, (nodes, container) => {
assert.deepEqual(emitInlineLine(nodes, container, [], 'lossless'), emitInlineLine(nodes, container, [], 'lossless', 'first'))
}),
// Lines the seed's runs rarely reach: carrying one run lets a neighbour read as written, or one pass carries a repeated run's every copy.
// Lines the fixed seed rarely generates: carrying one run lets a neighbour read as written, or one pass carries every copy of a repeated mark run.
{
...propertyRuns(3000),
examples: [
+12 -12
View File
@@ -28,7 +28,7 @@ export type InlineSegment =
export type AssembledLine = { line: string; openingLinkAsDirective?: true; unspellableRuns: MarkRun[] }
// How the caller takes a run back: a carry swaps its nodes for one blob, splitting the runs around it; a drop removes its mark, moving no other delimiter where `alone` holds.
// How the caller takes a run back: a carry swaps its nodes for one carry directive, splitting every run holding it; a drop removes its mark, moving no other delimiter where `alone` holds.
export type RunFallback = { kind: 'carry' } | { alone: (run: MarkRun) => boolean; kind: 'drop' }
type ScanLine = { position: LinePosition; start: number; text: string }
@@ -38,12 +38,12 @@ type EmphasisPair = { closer: number; delimiter: string; nodes: MarkRun; opener:
type Escapings = (index: number) => InlineEscaping | undefined
// Which emphasis pairs fall with the first misread one, each marked in `fallen` once settled.
// Which emphasis pairs fall: go back in this pass with the first misread one. `fallen` flags each pair settled to fall.
type PairSweep = {
readonly fallback: RunFallback
readonly fallen: Uint8Array
readonly first: number
// The leaf boundaries where dropping a pair that may fall first can merge the runs meeting there.
// The ends of every run whose drop is not alone: dropping it can merge the runs meeting there.
readonly merging: ReadonlySet<number>
readonly output: string
// The pair each placed segment delimits, -1 where it delimits none.
@@ -55,15 +55,15 @@ type PairSweep = {
// Where a segment's first character landed in the output.
type PlacedSegment = { segment: InlineSegment; start: number }
// How a pair stands while another falls: `either` where it may fall first.
type Standing = 'either' | 'gone' | 'there'
// The escaped line once reading it back asks for no further escape.
type ReadBack = { output: string; placed: PlacedSegment[]; reading: InlineReading }
// Which of a run's characters escape: every escapable one, or every one before `end`.
type RunEscapes = { end: number; escapable: false } | { escapable: true }
// How a pair stands while another falls: `either` where it may fall before the judged one.
type Standing = 'either' | 'gone' | 'there'
// Which text characters escape wherever they stand: one of them opened a construct when the line was read back.
type TextEscapes = { backticks: boolean; brackets: boolean }
@@ -290,7 +290,7 @@ function claimsCharacter({ container, escapings, headingCloser, inline: { text:
}
}
// The runs the line does not read back as written: the first emphasis run to close and every run that falls with it, else every misflanked highlight run. One misread emphasis pair can re-pair the delimiters after it.
// The runs this pass takes back: the first emphasis run the line misreads and every run that falls with it, else every misflanked highlight run. One misread emphasis pair can re-pair the delimiters after it.
function unspellableRuns(placed: readonly PlacedSegment[], reading: InlineReading, output: string, fallback: RunFallback): MarkRun[] {
const { pairAt, pairs } = emphasisPairs(placed)
const first = pairs.findIndex((pair) => !readAsWritten(reading, placed, pair))
@@ -351,7 +351,7 @@ function holdsAlike({ fallen, pairs }: PairSweep, judged: number, character: str
return false
}
// A carry splits every pair holding it, so a carried pair holds no pair that may fall first; a dropped pair keeps its leaves where no run merges at its ends.
// A carry splits every pair holding it, so a pair falls cleanly under a carry only where it holds no pair closing before it, and under a drop only where no run merges at its ends.
function fallsCleanly({ fallback, merging, pairs }: PairSweep, judged: number): boolean {
const pair = pairs[judged]
if (pair === undefined) return false
@@ -376,10 +376,10 @@ function mayFlank(sweep: PairSweep, judged: number, character: string, end: 'clo
)
}
// The characters that may stand past the run of `character` holding the delimiter at `from`, walking by `step`; `undefined` where a pair beside may move.
// The characters that may stand past the run of `character` holding the delimiter at `from`; `undefined` where a pair beside may move.
function runEdge(sweep: PairSweep, judged: number, from: number, step: -1 | 1, character: string): string[] | undefined {
const found: string[] = []
// A backslash escaping the next text character stands past the last delimiter walked, so the walk tracks where that delimiter ends.
// Past the run stands the character after its last delimiter: a backslash where the text beside is escaped, which the next segment's start skips.
let edge = delimiterEdge(sweep.placed, from, step)
for (let at = from + step; ; at += step) {
const segment = sweep.placed[at]?.segment
@@ -403,7 +403,7 @@ function delimiterEdge(placed: readonly PlacedSegment[], at: number, step: -1 |
return step === 1 ? delimiter.start + delimiter.segment.text.length : delimiter.start
}
// How the other pair stands when the judged one falls. Past the first, every pair closing after the judged one still stands, since that one cannot read as written; `undefined` where a carry or a merge may move the other's delimiters.
// How the other pair stands when the judged one falls. Where the judged pair closes after the first, every pair closing after it still stands, since the judged pair is then the first that cannot read as written; `undefined` where a carry or a merge may move the other's delimiters.
function pairStanding({ fallback, fallen, first, merging, pairs }: PairSweep, judged: number, other: number): Standing | undefined {
const pair = pairs[other]
if (other === judged) return 'there'
@@ -413,7 +413,7 @@ function pairStanding({ fallback, fallen, first, merging, pairs }: PairSweep, ju
return fallen[other] === 1 ? 'gone' : 'either'
}
// The characters the other pair may be written with when the judged one falls: emphasis takes `_`, turning `*` beside a word character.
// The characters the other pair may be written with when the judged one falls: em takes `_`, turning `*` beside a word character.
function pairSpellings(sweep: PairSweep, judged: number, other: number): string[] | undefined {
const pair = sweep.pairs[other]
if (pair === undefined) return undefined
@@ -10,7 +10,7 @@ test('reduces a line alike dropping every run a pass may or the first alone', ()
fc.property(markedLine, lineContainer, (nodes, container) => {
assert.deepEqual(reduceInline(nodes, container, [], 0), reduceInline(nodes, container, [], 0, 'first'))
}),
// Lines the seed's runs rarely reach: a later run must not drop with the first, or one pass drops a repeated run's every copy.
// Lines the fixed seed rarely generates: a later run must not drop with the first, or one pass drops every copy of a repeated mark run.
{
...propertyRuns(3000),
examples: [
+1 -1
View File
@@ -261,7 +261,7 @@ function droppedRuns(leaves: readonly AdfNode[], runs: readonly [MarkRun, ...Mar
return perPass === 'every' ? runs : runs.filter((run, index) => index === 0 || nodeMarks(leaves[run.first] ?? {})[run.depth]?.type === highlight)
}
// Dropping the run merges no runs and moves no edge, whatever else drops: at each end, the leaf beside holds no mark type the edge leaf holds inside the run's mark.
// Dropping the run merges no runs and moves no run's end, whatever else drops: at each end, the leaf outside holds no mark type the end leaf holds inside the run's mark.
function dropsAlone(leaves: readonly AdfNode[], run: MarkRun): boolean {
return endsAlone(leaves[run.first], leaves[run.first - 1], run.depth) && endsAlone(leaves[run.last], leaves[run.last + 1], run.depth)
}
+7 -4
View File
@@ -12,7 +12,7 @@
| ID | Release | Blocked by | Exempt | Item | L | E | I | R | S | Moves | Score |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 160 | 0.3.0 | | | **Make the block-text tree and the batched mark fallbacks read as easily as the code before items 33 and 109, with no switch only tests use in production signatures.** | 3 | 1 | 2 | 4 | 5 | code health by 5 | 13.1 |
| 160 | 0.3.0 | | | **Make the block-text tree and the batched mark fallbacks read as easily as the code before items 33 and 109, and keep every switch only tests use out of production signatures.** | 3 | 1 | 2 | 4 | 5 | code health by 5 | 13.1 |
| 150 | 0.3.0 | | decision | **Read `adfToHtml`'s HTML in `htmlToAdf` as a browser serializes it too — a raw `"` in text, `&nbsp;`, `\r\n` line endings — so a page's `innerHTML` or a form post reads back.** | 2 | 2 | 2 | 4 | 4 | 6 by 5 | 5.0 |
| 115 | 0.3.0 | | defect | **Build text holding no `border` mark in `losslessMarkdownToAdf`, and have `adfToLosslessMarkdown` carry text holding one.** | 2 | 1 | 2 | 3 | 3 | 4 by 3 | 4.6 |
| 91 | 0.3.0 | | | **Spell an emphasis once across the code spans, an emphasis or link once across the hard breaks, dates, emoji, inline cards, mentions and statuses between two nodes it marks, and an emphasis across inline media, in both writers.** | 3 | 2 | 2 | 5 | 5 | 6 by 6 | 3.6 |
@@ -69,14 +69,17 @@
## Details
### 160. Make the block-text tree and the batched mark fallbacks read as easily as the code before items 33 and 109, with no switch only tests use in production signatures.
### 160. Make the block-text tree and the batched mark fallbacks read as easily as the code before items 33 and 109, and keep every switch only tests use out of production signatures.
PR #106's paired ruling read worse than c7bbb04: both seats ruled Locality and Shape worse, the
maintainer chose to merge it and file this. Make each place they named easier to read:
`writtenText` and `Shape` in `block-text.ts` (a stack walk and eight hand-kept measures),
`FallbacksPerPass` threaded through `emitInlineLine` and `reduceInline` only for two properties,
`holdsOnlyRuleCharacters` holding BlockText's rule in the grammar, and `markerOpening` repeating
the marker regexes' `[`. No output may change, and the timings of items 33, 109 and 158 must hold.
`unspellableRuns`'s pair sweep in `line-escaping.ts`, which needs CommonMark's flanking rules open
beside it, `takenThisPass` and `checkedCarry` in `inline-line.ts`, `tryListItemLines` reading
`block-text.ts`'s measures, `holdsOnlyRuleCharacters` holding BlockText's rule in the grammar, and
`markerOpening` repeating the marker regexes' `[`. No output may change, and the timings the cost
entries under `CHANGELOG.md` → `## Unreleased` give for both writers must hold.
### 150. Read `adfToHtml`'s HTML in `htmlToAdf` as a browser serializes it too — a raw `"` in text, `&nbsp;`, `\r\n` line endings — so a page's `innerHTML` or a form post reads back.