Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Emacs: regular expression replacing to change case (in scripts)

This is related to Emacs: regular expression replacing to change case

My additional problem is that I need to script the search-replace but the "\,()" solution works (for me) only when used interactively (emacs 24.2.1). Inside a script it gives the error: "Invalid use of \' in replacement text".

I usually write a "perform-replace" to some file to be loaded when needed. Something like:

(perform-replace "<\\([^>]+\\)>" "<\\,(downcase \1)>" t t nil 1 nil (point-min) (point-max))

It should be possible to call a function to generate the replacement (pg 741 of the emacs lisp manual), but I've tried many variations of the following with no luck:

(defun myfun ()
    (downcase (match-string 0)))

(perform-replace "..." (myfun . ()) t t nil)

Can anyone help?

like image 923
Matteo Gamboz Avatar asked Dec 04 '12 15:12

Matteo Gamboz


2 Answers

Constructs like \,() are only allowed in interactive calls to query-replace, which is why Emacs complains in your case.

The documentation of perform-replace mentions that you should not use it in elisp code and proposes a better alternative, upon which we can build the following code:

(while (re-search-forward "<\\([^>]+\\)>" nil t)
  (replace-match (downcase (match-string 0)) t nil))

If you still want to interactively query the user about the replacements, using perform-replace like you did is probably the right thing to do. There were a few different problems in your code:

  1. As stated in the elisp manual the replacement function must take two arguments (the data you provide in the cons cell and the number of replacements already made).

  2. As stated in the documentation of query-replace-regexp (or the elisp manual), you need to ensure that case-fold-search or case-replace is set to nil so that the case pattern is not transferred to the replacement.

  3. You need to quote the cons cell (myfun . nil), otherwise it will be interpreted as a function call and evaluated too early.

Here is a working version:

(let ((case-fold-search nil))
  (perform-replace "<\\([^>]+\\)>"
                   `(,(lambda (data count)
                       (downcase (match-string 0))))
                   t t nil))
like image 110
François Févotte Avatar answered Nov 07 '22 15:11

François Févotte


C-h f perform-replace says:

Don't use this in your own program unless you want to query and set the mark
just as `query-replace' does.  Instead, write a simple loop like this:

  (while (re-search-forward "foo[ \t]+bar" nil t)
    (replace-match "foobar"))

Now the "<\\,(downcase \1)>" needs to be replaced by an Elisp expression that builds the proper string, such as (format "<%s>" (downcase (match-string 1))).

If you do need the query and stuff, then you might like to try: C-M-% f\(o\)o RET bar \,(downcase \1) baz RET and then C-x RET RET to see what arguments were constructed during the interactive call.

You'll see discover (even better if you click on replace.el in C-h f perform-replace to see the source code of the function), that the replacements argument can take the form (FUNCTION . ARGUMENT). More specifically, the code includes a comment giving some details:

;; REPLACEMENTS is either a string, a list of strings, or a cons cell
;; containing a function and its first argument.  The function is
;; called to generate each replacement like this:
;;   (funcall (car replacements) (cdr replacements) replace-count)
;; It must return a string.
like image 2
Stefan Avatar answered Nov 07 '22 13:11

Stefan