[48] Sketch for "start-of-match" boundary solution - #51
Closed
TomStrepsil wants to merge 1 commit into
Closed
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR sketches a proof-of-concept fix for issue #48 in RegexSearchStrategy, where ^/\b/\B (and ^ under /m) can produce false positives when exec() is run against truncated substrings during streaming scanning. It introduces a one-code-unit “preceding context” state and re-verifies matches that start at slice index 0 using a sticky clone of the regex, plus adds targeted regression tests and documentation updates.
Changes:
- Add
precedingChartoRegexSearchStrategystate and implementfindCompleteMatch()to re-verify boundary-sensitive candidates. - Add a sticky clone (
stickyMatchRegex) and a construction-time gate to skip the verification path when boundary assertions can’t be present. - Add tests covering false-positive elimination and regression cases; update the regex strategy README with a detailed explanation and new limitation notes.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/search-strategies/regex/search-strategy.ts | Adds precedingChar state + verification loop using a sticky regex to prevent start-boundary false positives. |
| src/search-strategies/regex/search-strategy.test.ts | Adds issue #48 repro/regression coverage around start-boundary “slice illusion” behavior. |
| src/search-strategies/regex/README.md | Documents the start-boundary verification approach and clarifies remaining end-boundary limitations. |
Comments suppressed due to low confidence (1)
src/search-strategies/regex/search-strategy.ts:92
- This doc comment also lists
^twice (and omits\\B). It should reflect the actual set of assertions being guarded so the behavior is clear.
* candidate whose success at position 0 of its own exec'd slice depends on
* `^`/`\b`/`^` (`/m`) treating a truncated slice's edge as the true stream
* edge. Rejected candidates are re-verified against real preceding context
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * The last UTF-16 code unit actually emitted so far — as match or | ||
| * non-match content — or `undefined` if nothing has been emitted yet | ||
| * (i.e. we are still at the true start of the stream). This is the one | ||
| * piece of context `^` (no `m`), `\b`, and `^` under `/m` ever need to |
Owner
Author
|
Closed as unplanned |
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.
Issue
relates to #48
This PR is a proof-of-concept sketch, raised for posterity.
Despite it resolving the stated issue, any potential solution to the complentary issue #49 (that must be solved for this fix to be useful) would fundamentally break the "yield as early as possible" intent of the library, needlessly requiring any matches that touch the end of a chunk to be deferred until the next chunk arrives, or the end of the stream is met. The latter case currently gets processed via
flush(), the interface of which only currently supports outputting plain "non match" strings.Boundary matching represents the same family of problem as the documented Lookbehinds and Negative lookaheads, such that without a regex state machine (thus a fundamentally different approach than the
regex-partial-matchthat underpins the search strategy), no assumption can be made about when such buffering should take place, since support of disjunctions etc. prevent naive analysis of the regex source to detect when a boundary is "next atomic match" within a partially matched string.Use-cases for genuinely matching just the very start or very end of a stream are undefined.
\band\Bhave more obvious utility, but seem somewhat incompatible with timely yielding streams.Details
RegexSearchStrategyre-slices the remaining haystack via.substring()every time it advances past a match. Native^(nom),\b, and\Bevaluate relative to that substring's own index 0, not the true stream's start — so a truncated slice's artificial edge gets mistaken for a real one, producing confirmed false-positive matches. This reproduces with zero chunking at all (/\bOLD/.exec("OLDOLD".substring(3))wrongly matches), so it isn't only a chunk-boundary problem — it's inherent to how the scan loop re-slices after every match.The fix retains one real character of trailing context (
state.precedingCharacross a chunk boundary, or the real character already in the haystack for a mid-buffer position) and re-verifies any candidate landing at a slice's index 0 against it, using a sticky-anchored clone of the pattern forced to start exactly past that character. A rejected candidate resumes the search one code point later rather than abandoning it, so a genuinely valid match later in the same haystack isn't lost. Patterns that can never invoke^/\b/\Bskip the check entirely (checked once, at construction) — benchmarked to matter: an early, ungated version of this cost up to +57% on match-dense content before the gate was added.src/search-strategies/regex/search-strategy.ts—precedingCharstate, the construction-time gate,findCompleteMatch's verify/retry loopsrc/search-strategies/regex/search-strategy.test.ts— repro cases proving the false positives are gone, regression guards proving legitimate cross-chunk/mid-buffer matches still work, and coverage for every branch added (each verified by disabling it and confirming a test fails)src/search-strategies/regex/README.md— an addendum to "How It Works" explaining the mechanism, plus updated Limitations/Supported Features entriesSemantic Version Impact
Checklist
[Unreleased]section inCHANGELOG.md