Repository navigation
Conversation
`find_code_spans` walked the rest of the text for every opening run of backticks without an exact closing run. The failure path also returned `start + max_ticks` although `start` was already past the opening ticks, which could split the next run and lose its code span, and the fallback to a shorter closing run was abandoned when a longer run appeared later. Record the runs of a text once, by length, and use the largest length up to the opening which still has a later run. This is the pairing of the regex replaced in Python-Markdown#1618, and an opening now costs no more than its own ticks. Runs are held as distances from the end of the text as that does not change when earlier spans are replaced with placeholders.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
When an opening run of backticks has no closing run of the same length,
BacktickInlineProcessor.find_code_spanspairs the backticks wrongly in two ways, and it rescans the rest of the text in Python for each such run.The failure path returns
start + max_ticks, butstartis already past the opening ticks, somax_ticksmore characters get skipped. When those are the first ticks of the next run, that run is split and its code span is lost. The fallback to a shorter closing run is also given up as soon as any longer run appears later in the text. Both came in with #1669, and neither matches the regex used before #1618:The split runs are also what makes the first input below quadratic. Fixing only the skip is not enough though, because an escaped first tick gives the same rescan: in
\``` the opening is one tick long but the scan sees a run of two, so no opening ever closes.handleMatchnow records the runs of a text once, by length, andfind_code_spans` takes the largest length up to the opening that still has a later run. That is the scan once approach mentioned in #1654, and an opening now costs no more than its own ticks. Runs are stored as distances from the end of the text because replacing an earlier span with a placeholder does not change those.markdown.markdown(s * n)at 80 KB on CPython 3.11, in seconds:What is left on the escaped inputs is the string splice in
__applyPatternthat every repeated inline match pays, so they still grow faster than linear at larger sizes.\*has no backticks and takes 0.97 s at 80 KB and 3.46 s at 160 KB, against 0.58 s and 2.09 s for ````. I did not change that here.Output is byte-identical to a build using the pre-#1618 regex on 1,998,100 documents: every string up to length 10 over backtick, backslash,
aand space, plus 600,000 random documents, with no extensions and withextra. Master differs on 67,338 of them. Both cases from #1617 that had to stay the same are unchanged, and each is also in that comparison.find_code_spansnow relies onself.runs, whichhandleMatchfills whenm.posis 0, so calling it directly no longer works without that step.test_end_in_earlier_text_is_ignoredcovers the reset.test_many_unmatched_openingsis timing only: it passes on master in 18 s and here in 0.6 s.AI Assistance Disclosure
No AI tools were used in preparing this PR.
AI tools were used and I have disclosed which ones, and have fully reviewed and verified their output.
AI tools were use to assist with the following:
Checklist