0

Suppose I have following pattern:

throw "some message";

which I'd like to replace with:

throw new Error("some message");

So, "some message" part should not be changed (or copied as is), and everything around it should be changed with arbitrary strings.

Is there any way to do that in Emacs?

4
  • Seems like this question should be a duplicate, but I don't have time now to search for that... Anyone else?
    – Drew
    Oct 23, 2019 at 13:58
  • 1) What is the major mode of the buffer? 2) Do you always throw literal strings in the original version or do you sometimes format them?
    – Tobias
    Oct 23, 2019 at 15:30
  • @Tyler: Capturing a group is here only a small problem compared to finding the right buffer-substring.
    – Tobias
    Oct 23, 2019 at 17:28
  • 1
    @Tobias the accepted answer indicates that capture groups was the crux of the issue. I think you've answered a much more complex question than the one OP actually asked ;)
    – Tyler
    Oct 23, 2019 at 18:33

3 Answers 3

2

You can use M-x replace-regexp or M-x query-replace-regexp. e.g.:

C-M-% throw \(".*?"\); RET throw new Error(\1); RET

If "some message" might contain escaped double-quotes like "whoops, \"foo\" happened" then the pattern needs to be more complex; something more like:

throw \(""\|".*?[^\]"\);

And if there might be embedded newlines as well, . doesn't match those, so you would need:

throw \(""\|"\(?:.\|<NEWLINE>\)*?[^\]"\);

Where in place of <NEWLINE> you would need to insert a literal newline character via quoted-insert by typing C-qC-j

To learn more about this, start reading here:

C-hig (emacs)Regexps RET

3
  • Your second suggestion fails on throw "first" /* " second"; */ " third"; which is valid C++. Might occur in code where one wants to keep some old version.
    – Tobias
    Oct 23, 2019 at 15:49
  • And there are still more cases where your construct fails. Test it on my example C++ code and compare the result to the that one of my-replace-throw.
    – Tobias
    Oct 23, 2019 at 16:07
  • Sure. My answer handles exactly the special cases that I've discussed: escaped double-quotes and newlines (which I thought were likely possibilities) for cases which look reasonably like the example in the question. C++ wasn't mentioned anywhere, but I agree, this answer absolutely doesn't handle any other arbitrary language rules for the buffer it's used in. It does only what I've described.
    – phils
    Oct 23, 2019 at 20:08
2

What you need to use is regexp groups. In short, if you put something inside parenthesis in your regexp, you can reuse what's inside the parenthesis when replacing by using \1 (or \n if you have multiple groups). In your case, it would be throw "\(.*\)" -> throw new Error("\1"). The package pcre2el makes it easier to work with regexp groups, you can also check visual regexp steroid if you want to visualize your regexp.

4
  • I don't want to down-vote a new user, but as-is this answer wouldn't work, and would do the wrong thing in certain cases even if the syntax was what you had intended, and is generally pretty confusing / lacking in context, IMO. Perhaps you could spend some time reviewing and editing it?
    – phils
    Oct 23, 2019 at 11:41
  • The comment about pcre2el is a bit misleading, please clarify what it does and why it's relevant here.
    – Stefan
    Oct 23, 2019 at 11:53
  • Actually, despite all the critique, mentioning regexp groups was really helpful. Thanks! Oct 23, 2019 at 15:03
  • n.b. Stefan has fixed the fundamental errors in the pattern and replacement that I had alluded to. I would still question the use of a greedy match here, though -- it depends on the use-case of course, but I'd argue that a non-greedy pattern is usually what you want in cases like this.
    – phils
    Oct 23, 2019 at 20:20
2

The following example of stupid but valid C++ code shows that automatic replacement of throw EXPRESSION; by throw new Error(EXPRESSION); is not possible in a roboust way without exploiting lexical information from the buffer.

The comments in the source code indicate the problems.

#include <string>
int main() {

    try {
        throw /* some comment */
            "some message";
    } catch(char* s) {
        throw; // re-throw: must not be replaced
    }

    throw std::string("one ") + std::string("two"); // expression

    std::string str = "Please \"throw\" the ball;"; /* stuff after throw must not be replaced */

    /* Here we throw in some comment. */ // must not be replaced

    throw "first" /* " second"; */ " third";

    throw "";
    int i = 1;
    int j = ( i < 1 ? ( throw "Wrong number ", 0 ) : 2 );
    return 0;
}

/*
Local Variables:
compile-command: "g++ /temp/test.cc"
tab-indent-mode: nil
End:
 */

The following commented source code handles all those problems. It handles at least in this simple example all occurences of throw correctly.

(defvar my-replace-throw-expr-delimiter-re "[;,)]")

(defun my-replace-throw ()
  "Replace
throw EXPRESSION;
by
throw new Error(EXPRESSION);"
  (interactive)
  (goto-char (point-min))
  (let (beg end)
    (while
        (progn
          (while (and (setq beg (re-search-forward "\\_<throw\\_>" nil 'noErr))
                      (or (nth 8 (syntax-ppss)) ;; "throw" is part of comment or string
                          (progn
                            (forward-comment most-positive-fixnum) ;; skip all comments and whitespace
                            (looking-at-p my-replace-throw-expr-delimiter-re))))) ;; not a re-throw
          beg)
      ;; we found a throw with syntactically relevant argument
      ;; now we collect all the sexps that belong to the argument
      (setq beg (point))
      (while
          (progn
            (forward-sexp)
            (forward-comment most-positive-fixnum) ;; skip all comments and whitespace
            (null (looking-at-p my-replace-throw-expr-delimiter-re)))) ;; until we find the end of the throw argument
      (cl-assert (looking-at-p my-replace-throw-expr-delimiter-re) nil
                 "Expected end of expression.")
      (let ((str (buffer-substring beg (point))))
        (delete-region beg (point))
        (insert
         (format "new Error(%s)" str))))))

Calling M-x `my-replace-throw` RET on the above C++ code gives the following result:

#include <string>
int main() {

    try {
        throw /* some comment */
            new Error("some message");
    } catch(char* s) {
        throw; // re-throw: must not be replaced
    }

    throw new Error(std::string("one ") + std::string("two")); // expression

    std::string str = "Please \"throw\" the ball;"; /* stuff after throw must not be replaced */

    /* Here we throw in some comment. */ // must not be replaced

    throw new Error("first" /* " second"; */ " third");

    throw new Error("");
    int i = 1;
    int j = ( i < 1 ? ( throw new Error("Wrong number "), 0 ) : 2 );
    return 0;
}

/*
Local Variables:
compile-command: "g++ /temp/test.cc"
tab-indent-mode: nil
End:
 */

Not the answer you're looking for? Browse other questions tagged or ask your own question.