Function: replace-match

replace-match is a function defined in search.c.

Signature

(replace-match NEWTEXT &optional FIXEDCASE LITERAL STRING SUBEXP)

Documentation

Replace text matched by last search with NEWTEXT.

Leave point at the end of the replacement text.

If optional second arg FIXEDCASE is non-nil, do not alter the case of the replacement text. Otherwise, maybe capitalize the whole text, or maybe just word initials, based on the replaced text. If the replaced text has only capital letters and has at least one multiletter word, convert NEWTEXT to all caps. Otherwise if all words are capitalized in the replaced text, capitalize each word in NEWTEXT.

If optional third arg LITERAL is non-nil, insert NEWTEXT literally. Otherwise treat \ as special:
  \& in NEWTEXT means substitute original matched text.
  \N means substitute what matched the Nth \(...\).
       If Nth parens didn't match, substitute nothing.
  \\ means insert one \.
  \? is treated literally
       (for compatibility with query-replace-regexp).
  Any other character following \ signals an error.
Case conversion does not apply to these substitutions.

If optional fourth argument STRING is non-nil, it should be a string to act on; this should be the string on which the previous match was done via string-match. In this case, replace-match creates and returns a new string, made by copying STRING and replacing the part of STRING that was matched (the original STRING itself is not altered).

The optional fifth argument SUBEXP specifies a subexpression; it says to replace just that subexpression with NEWTEXT, rather than replacing the entire matched text. This is, in a vague sense, the inverse of using \N in NEWTEXT;
\N copies subexp N into NEWTEXT, but using N as SUBEXP puts
NEWTEXT in place of subexp N. This is useful only after a regular expression search or match, since only regular expressions have distinguished subexpressions.

Other relevant functions are documented in the regexp group.

Probably introduced at or before Emacs version 17.

Shortdoc

;; regexp
(replace-match "new")
    e.g. => nil

Source Code

// Defined in /usr/src/emacs/src/search.c
// Skipping highlighting due to helpful-max-highlight.
{
  enum { nochange, all_caps, cap_initial } case_action;
  ptrdiff_t pos, pos_byte;
  bool some_multiletter_word;
  bool some_lowercase;
  bool some_uppercase;
  bool some_nonuppercase_initial;
  int c, prevc;
  ptrdiff_t sub;
  ptrdiff_t opoint, newpoint;

  CHECK_STRING (newtext);

  if (! NILP (string))
    CHECK_STRING (string);

  /* Most replacement texts don't contain any backslash directives in
     the replacements.  Check whether that's the case, which will
     enable us to take the fast path later.  */
  if (NILP (literal)
      && !memchr (SSDATA (newtext), '\\', SBYTES (newtext)))
    literal = Qt;

  case_action = nochange;	/* We tried an initialization */
				/* but some C compilers blew it */

  ptrdiff_t num_regs = search_regs.num_regs;
  if (num_regs <= 0)
    error ("`replace-match' called before any match found");

  sub = !NILP (subexp) ? check_integer_range (subexp, 0, num_regs - 1) : 0;
  ptrdiff_t sub_start = search_regs.start[sub];
  ptrdiff_t sub_end = search_regs.end[sub];
  eassert (sub_start <= sub_end);

  /* Check whether the text to replace is present in the buffer/string.  */
  if (! (NILP (string)
	 ? BEGV <= sub_start && sub_end <= ZV
	 : 0 <= sub_start && sub_end <= SCHARS (string)))
    {
      if (sub_start < 0)
	xsignal2 (Qerror,
		  build_string ("replace-match subexpression does not exist"),
		  subexp);
      args_out_of_range (make_fixnum (sub_start), make_fixnum (sub_end));
    }

  if (NILP (fixedcase))
    {
      /* Decide how to casify by examining the matched text. */
      ptrdiff_t last;

      pos = sub_start;
      last = sub_end;

      if (NILP (string))
	pos_byte = CHAR_TO_BYTE (pos);
      else
	pos_byte = string_char_to_byte (string, pos);

      prevc = '\n';
      case_action = all_caps;

      /* some_multiletter_word is set nonzero if any original word
	 is more than one letter long. */
      some_multiletter_word = 0;
      some_lowercase = 0;
      some_nonuppercase_initial = 0;
      some_uppercase = 0;

      while (pos < last)
	{
	  if (NILP (string))
	    {
	      c = FETCH_CHAR_AS_MULTIBYTE (pos_byte);
	      inc_both (&pos, &pos_byte);
	    }
	  else
	    c = fetch_string_char_as_multibyte_advance (string,
							&pos, &pos_byte);

	  if (lowercasep (c))
	    {
	      /* Cannot be all caps if any original char is lower case */

	      some_lowercase = 1;
	      if (SYNTAX (prevc) != Sword)
		some_nonuppercase_initial = 1;
	      else
		some_multiletter_word = 1;
	    }
	  else if (uppercasep (c))
	    {
	      some_uppercase = 1;
	      if (SYNTAX (prevc) != Sword)
		;
	      else
		some_multiletter_word = 1;
	    }
	  else
	    {
	      /* If the initial is a caseless word constituent,
		 treat that like a lowercase initial.  */
	      if (SYNTAX (prevc) != Sword)
		some_nonuppercase_initial = 1;
	    }

	  prevc = c;
	}

      /* Convert to all caps if the old text is all caps
	 and has at least one multiletter word.  */
      if (! some_lowercase && some_multiletter_word)
	case_action = all_caps;
      /* Capitalize each word, if the old text has all capitalized words.  */
      else if (!some_nonuppercase_initial && some_multiletter_word)
	case_action = cap_initial;
      else if (!some_nonuppercase_initial && some_uppercase)
	/* Should x -> yz, operating on X, give Yz or YZ?
	   We'll assume the latter.  */
	case_action = all_caps;
      else
	case_action = nochange;
    }

  /* Do replacement in a string.  */
  if (!NILP (string))
    {
      Lisp_Object before, after;

      before = Fsubstring (string, make_fixnum (0), make_fixnum (sub_start));
      after = Fsubstring (string, make_fixnum (sub_end), Qnil);

      /* Substitute parts of the match into NEWTEXT
	 if desired.  */
      if (NILP (literal))
	{
	  ptrdiff_t lastpos = 0;
	  ptrdiff_t lastpos_byte = 0;
	  /* We build up the substituted string in ACCUM.  */
	  Lisp_Object accum;
	  Lisp_Object middle;
	  ptrdiff_t length = SBYTES (newtext);

	  accum = Qnil;

	  for (pos_byte = 0, pos = 0; pos_byte < length;)
	    {
	      ptrdiff_t substart = -1;
	      ptrdiff_t subend = 0;
	      bool delbackslash = 0;

	      c = fetch_string_char_advance (newtext, &pos, &pos_byte);

	      if (c == '\\')
		{
		  c = fetch_string_char_advance (newtext, &pos, &pos_byte);

		  if (c == '&')
		    {
		      substart = sub_start;
		      subend = sub_end;
		    }
		  else if (c >= '1' && c <= '9')
		    {
		      if (c - '0' < num_regs
			  && search_regs.start[c - '0'] >= 0)
			{
			  substart = search_regs.start[c - '0'];
			  subend = search_regs.end[c - '0'];
			}
		      else
			{
			  /* If that subexp did not match,
			     replace \\N with nothing.  */
			  substart = 0;
			  subend = 0;
			}
		    }
		  else if (c == '\\')
		    delbackslash = 1;
		  else if (c != '?')
		    error ("Invalid use of `\\' in replacement text");
		}
	      if (substart >= 0)
		{
		  if (pos - 2 != lastpos)
		    middle = substring_both (newtext, lastpos,
					     lastpos_byte,
					     pos - 2, pos_byte - 2);
		  else
		    middle = Qnil;
		  accum = concat3 (accum, middle,
				   Fsubstring (string,
					       make_fixnum (substart),
					       make_fixnum (subend)));
		  lastpos = pos;
		  lastpos_byte = pos_byte;
		}
	      else if (delbackslash)
		{
		  middle = substring_both (newtext, lastpos,
					   lastpos_byte,
					   pos - 1, pos_byte - 1);

		  accum = concat2 (accum, middle);
		  lastpos = pos;
		  lastpos_byte = pos_byte;
		}
	    }

	  if (pos != lastpos)
	    middle = substring_both (newtext, lastpos,
				     lastpos_byte,
				     pos, pos_byte);
	  else
	    middle = Qnil;

	  newtext = concat2 (accum, middle);
	}

      /* Do case substitution in NEWTEXT if desired.  */
      if (case_action == all_caps)
	newtext = Fupcase (newtext);
      else if (case_action == cap_initial)
	newtext = Fupcase_initials (newtext);

      return concat3 (before, newtext, after);
    }

  /* Record point.  A nonpositive OPOINT is actually an offset from ZV.  */
  opoint = PT <= sub_start ? PT : max (PT, sub_end) - ZV;

  /* If we want non-literal replacement,
     perform substitution on the replacement string.  */
  if (NILP (literal))
    {
      ptrdiff_t length = SBYTES (newtext);
      unsigned char *substed;
      ptrdiff_t substed_alloc_size, substed_len;
      bool buf_multibyte = !NILP (BVAR (current_buffer, enable_multibyte_characters));
      bool str_multibyte = STRING_MULTIBYTE (newtext);
      bool really_changed = 0;

      substed_alloc_size = (length <= (STRING_BYTES_BOUND - 100) / 2
			    ? length * 2 + 100
			    : STRING_BYTES_BOUND);
      substed = xmalloc (substed_alloc_size);
      substed_len = 0;

      /* Go thru NEWTEXT, producing the actual text to insert in
	 SUBSTED while adjusting multibyteness to that of the current
	 buffer.  */

      for (pos_byte = 0, pos = 0; pos_byte < length;)
	{
	  unsigned char str[MAX_MULTIBYTE_LENGTH];
	  const unsigned char *add_stuff = NULL;
	  ptrdiff_t add_len = 0;
	  ptrdiff_t idx = -1;
	  ptrdiff_t begbyte UNINIT;

	  if (str_multibyte)
	    {
	      c = fetch_string_char_advance_no_check (newtext,
						      &pos, &pos_byte);
	      if (!buf_multibyte)
		c = CHAR_TO_BYTE8 (c);
	    }
	  else
	    {
	      /* Note that we don't have to increment POS.  */
	      c = SREF (newtext, pos_byte++);
	      if (buf_multibyte)
		c = make_char_multibyte (c);
	    }

	  /* Either set ADD_STUFF and ADD_LEN to the text to put in SUBSTED,
	     or set IDX to a match index, which means put that part
	     of the buffer text into SUBSTED.  */

	  if (c == '\\')
	    {
	      really_changed = 1;

	      if (str_multibyte)
		{
		  c = fetch_string_char_advance_no_check (newtext,
							  &pos, &pos_byte);
		  if (!buf_multibyte && !ASCII_CHAR_P (c))
		    c = CHAR_TO_BYTE8 (c);
		}
	      else
		{
		  c = SREF (newtext, pos_byte++);
		  if (buf_multibyte)
		    c = make_char_multibyte (c);
		}

	      if (c == '&')
		idx = sub;
	      else if ('1' <= c && c <= '9' && c - '0' < num_regs)
		{
		  if (search_regs.start[c - '0'] >= 1)
		    idx = c - '0';
		}
	      else if (c == '\\')
		add_len = 1, add_stuff = (unsigned char *) "\\";
	      else
		{
		  xfree (substed);
		  error ("Invalid use of `\\' in replacement text");
		}
	    }
	  else
	    {
	      add_len = CHAR_STRING (c, str);
	      add_stuff = str;
	    }

	  /* If we want to copy part of a previous match,
	     set up ADD_STUFF and ADD_LEN to point to it.  */
	  if (idx >= 0)
	    {
	      begbyte = CHAR_TO_BYTE (search_regs.start[idx]);
	      add_len = CHAR_TO_BYTE (search_regs.end[idx]) - begbyte;
	      if (search_regs.start[idx] < GPT && GPT < search_regs.end[idx])
		move_gap_both (search_regs.start[idx], begbyte);
	    }

	  /* Now the stuff we want to add to SUBSTED
	     is invariably ADD_LEN bytes starting at ADD_STUFF.  */

	  /* Make sure SUBSTED is big enough.  */
	  if (substed_alloc_size - substed_len < add_len)
	    substed =
	      xpalloc (substed, &substed_alloc_size,
		       add_len - (substed_alloc_size - substed_len),
		       STRING_BYTES_BOUND, 1);

	  /* We compute this after the call to xpalloc, because that
	     could cause buffer text be relocated when ralloc.c is used.  */
	  if (idx >= 0)
	    add_stuff = BYTE_POS_ADDR (begbyte);

	  /* Now add to the end of SUBSTED.  */
	  if (add_stuff)
	    {
	      memcpy (substed + substed_len, add_stuff, add_len);
	      substed_len += add_len;
	    }
	}

      if (really_changed)
	newtext = make_specified_string ((const char *) substed, -1,
					 substed_len, buf_multibyte);
      xfree (substed);
    }

  newpoint = sub_start + SCHARS (newtext);

  /* Replace the old text with the new in the cleanest possible way.  */
  replace_range (sub_start, sub_end, newtext, 1, 0, 1, true, true);

  if (case_action == all_caps)
    Fupcase_region (make_fixnum (search_regs.start[sub]),
		    make_fixnum (newpoint),
		    Qnil);
  else if (case_action == cap_initial)
    Fupcase_initials_region (make_fixnum (search_regs.start[sub]),
			     make_fixnum (newpoint), Qnil);

  /* The replace_range etc. functions can trigger modification hooks
     (see signal_before_change and signal_after_change).  Try to error
     out if these hooks clobber the match data since clobbering can
     result in confusing bugs.  We used to check for changes in
     search_regs start and end, but that fails if modification hooks
     remove or add text earlier in the buffer, so just check num_regs
     now. */
  if (search_regs.num_regs != num_regs)
    error ("Match data clobbered by buffer modification hooks");

  /* Put point back where it was in the text, if possible.  */
  TEMP_SET_PT (clip_to_bounds (BEGV, opoint + (opoint <= 0 ? ZV : 0), ZV));
  /* Now move point "officially" to the end of the inserted replacement.  */
  move_if_not_intangible (newpoint);

  signal_after_change (sub_start, sub_end - sub_start, SCHARS (newtext));
  update_compositions (sub_start, newpoint, CHECK_BORDER);

  return Qnil;
}