Compile capturing regexes with the backtracking matcher
irregex builds a POSIX leftmost-longest DFA for a pattern when it can, and jolt
used it for everything. For a pattern with an alternation whose branches have
capturing groups, that DFA leaks a non-participating branch's group: e.g.
#"(?:([0-9])|([0-9])r([0-9]+))" on "2r11" left group 1 = "2" instead of nil, so
tools.reader (rewrite-clj's dep) misread 2r1100 as 2 and 16rFF as 16.
java.util.regex is itself a leftmost-first backtracking engine, so compile a
capturing pattern with irregex's backtracking matcher ('backtrack): its submatch
semantics match the JVM and it clears a losing branch's group. Non-capturing
patterns keep the DFA — with no groups to read, its whole-match result is all a
caller sees, and it avoids backtracking's worst case. The submatch count comes
from a first cheap compile; a capturing pattern recompiles once and caches.
This clears the last rewrite-clj parser-test failure (now 772/0/0). Corpus rows
for the alternation-group case and the radix read. make test green.
This commit is contained in:
parent
e2d842b073
commit
7c4f9bb974
2 changed files with 17 additions and 2 deletions
|
|
@ -159,10 +159,23 @@
|
|||
;; A jolt regex value: the source string (for printing / str) + the compiled
|
||||
;; irregex. regex? recognizes it; the printer renders #"source".
|
||||
(define-record-type regex-t (fields source irx) (nongenerative jolt-regex-v1))
|
||||
;; A capturing pattern is compiled with irregex's BACKTRACKING matcher ('backtrack),
|
||||
;; not its DFA. java.util.regex is itself a leftmost-first backtracking engine, so
|
||||
;; this matches the JVM's submatch semantics; irregex's DFA is POSIX leftmost-longest
|
||||
;; and, worse, leaks a non-participating alternation group's capture (e.g.
|
||||
;; #"(?:([0-9])|([0-9])r([0-9]+))" on "2r11" left group 1 = "2"), which broke
|
||||
;; tools.reader's number reader. Non-capturing patterns keep the fast DFA — with no
|
||||
;; groups to read, its whole-match result is all a caller sees. The count comes from
|
||||
;; a first cheap compile; a capturing pattern is recompiled once (patterns compile
|
||||
;; once and cache in the regex-t).
|
||||
(define (jolt-regex source)
|
||||
(let-values (((opts pat) (regex-parse-flags source)))
|
||||
(make-regex-t source
|
||||
(apply irregex (translate-prop-classes (escape-class-shorthand-dash pat)) opts))))
|
||||
(let* ((p (translate-prop-classes (escape-class-shorthand-dash pat)))
|
||||
(irx (apply irregex p opts)))
|
||||
(make-regex-t source
|
||||
(if (> (irregex-num-submatches irx) 0)
|
||||
(apply irregex p 'backtrack opts)
|
||||
irx)))))
|
||||
(define (jolt-regex? x) (regex-t? x))
|
||||
(define (jolt-re-pattern x) (if (regex-t? x) x (jolt-regex x)))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue