CaptureMatcher

halotukozak.regex.CaptureMatcher
See theCaptureMatcher companion object
final class CaptureMatcher

Whole-string capture-span extraction for patterns containing Regex.Group nodes - the "Phase B" this library's issue #18 tracks (Phase A, Regex.Group itself, only recognizes groups structurally; nothing extracted their matched spans until this).

Backed by Pike's VM: a Thompson-construction NFA compiled directly from a parsed Regex tree (unerased - unlike Subset.of, this needs every Regex.Group node, not none of them), executed as a priority-ordered set of "threads" (one per live NFA position) that all advance one input code point at a time, each carrying its own capture-register array - the technique RE2/Go's regexp/Rust's regex crate actually ship for general (non-"one-pass") captures. Guaranteed O(programSize × inputLength): at most one thread per program instruction survives each step (an equal-or-lower-priority duplicate reaching an already-live instruction is dropped), so there's no repeat of catastrophic-backtracking blowup - the same complexity discipline Subset.isEmpty's visited-state BFS already relies on.

This is a genuinely separate engine from Subset/TokenMatcher, not an extension of either - Subset.of erases every Group before deriving through a pattern specifically so its derivative algebra never has to reason about captures at all. Regex.scala's only changes for this feature are mechanical: Regex.Alt's parts became an order-preserving Vector instead of a Set (see its own doc comment for why - every no-backtracking submatch algorithm, Pike's VM included, needs leftmost-first alternation priority, which a Set can't record) with equals/hashCode overridden back to Set-like semantics so Subset's ACI-normalization/termination story is completely unaffected. Subset.scala/TokenMatcher.scala are untouched.

Whole-string match only (mirrors this library's only existing matching precedent, RegexConformanceTest's private matches helper) - no find/replace/split, and captures aren't wired into TokenMatcher's compiled DFA. Both are tracked separately (issue #53 for the former; a TokenMatcher follow-up for the latter).

Attributes

Companion
object
Source
Capture.scala
Graph
Supertypes
class Object
trait Matchable
class Any

Members list

Value members

Concrete methods

def matchWhole(input: CharSequence): Option[MatchResult]

Matches input from start to end. None if the pattern doesn't accept the whole string.

Matches input from start to end. None if the pattern doesn't accept the whole string.

Attributes

Source
Capture.scala
def unapplySeq(input: CharSequence): Option[Seq[Option[String]]]

Lets a CaptureMatcher be used as a pattern-match extractor - case myMatcher(g1, g2) => ... - mirroring scala.util.matching.Regex's own unapplySeq, except a group that never participated in the match comes back as None rather than scala.util.matching.Regex's null-in-a-List (see MatchResult.group for why that distinction exists - the same reasoning applies here). Positional only, one element per numbered group 1..N - group 0 (the whole match) is never included, the same convention scala.util.matching.Regex uses - MatchResult.group(name) (via matchWhole) still covers named-group access, which unapplySeq's purely positional binding can't express.

Lets a CaptureMatcher be used as a pattern-match extractor - case myMatcher(g1, g2) => ... - mirroring scala.util.matching.Regex's own unapplySeq, except a group that never participated in the match comes back as None rather than scala.util.matching.Regex's null-in-a-List (see MatchResult.group for why that distinction exists - the same reasoning applies here). Positional only, one element per numbered group 1..N - group 0 (the whole match) is never included, the same convention scala.util.matching.Regex uses - MatchResult.group(name) (via matchWhole) still covers named-group access, which unapplySeq's purely positional binding can't express.

Attributes

Source
Capture.scala