]> git.eshelyaron.com Git - emacs.git/commitdiff
Fix replace-regexp-in-string substring match data translation
authorMattias Engdegård <mattiase@acm.org>
Wed, 25 Nov 2020 14:32:08 +0000 (15:32 +0100)
committerMattias Engdegård <mattiase@acm.org>
Thu, 26 Nov 2020 13:20:13 +0000 (14:20 +0100)
For certain patterns, re-matching the same regexp on the matched
substring does not produce correctly translated match data
(bug#15107 and bug#44861).

Using a new builtin function also improves performance since the
number of calls to string-match is halved.

Reported by Kevin Ryde and Shigeru Fukaya.

* lisp/subr.el (replace-regexp-in-string): Translate the match data
using match-data--translate instead of trusting a call to string-match
on the matched string to do the job.
* test/lisp/subr-tests.el (subr-replace-regexp-in-string):
Add test cases.
* src/search.c (Fmatch_data__translate): New internal function.
(syms_of_search): Register it as a subroutine.

lisp/subr.el
src/search.c
test/lisp/subr-tests.el

index 1fb0f9ab7e69fe115f6622db1f2bf28c9bdf48af..e009dcc2b9ad9ea4b4f32945762abdb5d97e3188 100644 (file)
@@ -4546,10 +4546,9 @@ and replace a sub-expression, e.g.
        (when (= me mb) (setq me (min l (1+ mb))))
        ;; Generate a replacement for the matched substring.
        ;; Operate on only the substring to minimize string consing.
-       ;; Set up match data for the substring for replacement;
-       ;; presumably this is likely to be faster than munging the
-       ;; match data directly in Lisp.
-       (string-match regexp (setq str (substring string mb me)))
+        ;; Translate the match data so that it applies to the matched substring.
+        (match-data--translate (- mb))
+        (setq str (substring string mb me))
        (setq matches
              (cons (replace-match (if (stringp rep)
                                       rep
index e7f90949464adb1a3a4cbd9ddb3eca54287200c5..4eb634a3c03567713181a1128e52fa703548b8de 100644 (file)
@@ -3031,6 +3031,23 @@ If optional arg RESEAT is non-nil, make markers on LIST point nowhere.  */)
   return Qnil;
 }
 
+DEFUN ("match-data--translate", Fmatch_data__translate, Smatch_data__translate,
+       1, 1, 0,
+       doc: /* Add N to all string positions in the match data.  Internal.  */)
+  (Lisp_Object n)
+{
+  CHECK_FIXNUM (n);
+  EMACS_INT delta = XFIXNUM (n);
+  if (EQ (last_thing_searched, Qt))   /* String match data only.  */
+    for (ptrdiff_t i = 0; i < search_regs.num_regs; i++)
+      if (search_regs.start[i] >= 0)
+        {
+          search_regs.start[i] = max (0, search_regs.start[i] + delta);
+          search_regs.end[i] = max (0, search_regs.end[i] + delta);
+        }
+  return Qnil;
+}
+
 /* Called from Flooking_at, Fstring_match, search_buffer, Fstore_match_data
    if asynchronous code (filter or sentinel) is running. */
 static void
@@ -3388,6 +3405,7 @@ is to bind it with `let' around a small expression.  */);
   defsubr (&Smatch_end);
   defsubr (&Smatch_data);
   defsubr (&Sset_match_data);
+  defsubr (&Smatch_data__translate);
   defsubr (&Sregexp_quote);
   defsubr (&Snewline_cache_check);
 
index c77be511dc2f676e38eb69d71683e6b9ac1b66b2..67f7fc97496155b516550e71f396d0de6185fd35 100644 (file)
@@ -545,7 +545,11 @@ See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=19350."
                             (match-beginning 1) (match-end 1)))
                   "babbcaacabc")
                  "b<abbc,0,4,1,3>a<ac,0,2,1,1><abc,0,3,1,2>"))
-  )
+  ;; anchors (bug#15107, bug#44861)
+  (should (equal (replace-regexp-in-string "a\\B" "b" "a aaaa")
+                 "a bbba"))
+  (should (equal (replace-regexp-in-string "\\`\\|x" "z" "--xx--")
+                 "z--zz--")))
 
 (provide 'subr-tests)
 ;;; subr-tests.el ends here