Why does this regexpatch command only work once, not twice?

Using l3regex directly instead of \regexpatchcmd it works:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand \addinnertoouter { m }
  {
    \regex_replace_once:nnNTF
      { \c{outercmd} \{ (.*) \} \Z}
      { \c{outercmd} \cB\{ \1 \c{inner} \cB\{ #1 \cE\} \cE\} }
      \test
      { }
      { error }
  }
\ExplSyntaxOff
\begin{document}
\ttfamily

\def\test{\outercmd{\inner{abc}}}
\meaning\test

\addinnertoouter{def}
\meaning\test

\addinnertoouter{ghi}
\meaning\test
\end{document}

enter image description here

(\outer is a (very special) TeX primitive, so it's better if you use something else.)

The issue is that l3regex apparently makes the added def tokens catcode 12 (looks like a bug to me; I'll summon a wizard to check), and when \regexpatchcmd tries to retokenize them they become catcode 11, and \regexpatchcmd thinks (correctly) that the macro cannot be patched. If you don't mind that the inserted tokens be catcode 12 (in this case it probably won't matter), then \regex_replace_once:nnNTF will suit your needs.


Beware that the argument of your \addinnertoouter is still a regular expression, so if you do \addinnertoouter{\textit{jkl}} the output will not be what you expect. You can use the \u feature of l3regex to add an arbitrary token list (without catcode changes, so it will also work with \regexpatchcmd):

\documentclass{article}
\usepackage{regexpatch}
\ExplSyntaxOn
\tl_new:N \l_bers_tmpa_tl
\NewDocumentCommand \addinnertoouter { m }
  {
    \tl_set:Nn \l_bers_tmpa_tl {#1}
    \regexpatchcmd
      \test
      { \c{outercmd} \{ (.*) \} \Z}
      { \c{outercmd} \cB\{ \1 \c{inner} \cB\{ \u{l_bers_tmpa_tl} \cE\} \cE\} }
      { }
      { error }
  }
\ExplSyntaxOff
\begin{document}
\ttfamily

\def\test{\outercmd{\inner{abc}}}
\meaning\test

\addinnertoouter{def}
\meaning\test

\addinnertoouter{ghi}
\meaning\test

\addinnertoouter{\textit{jkl}}
\meaning\test
\end{document}

enter image description here


Phelype Oleinik already described the problem with catcode changes in \regexpatchcmd, so I'm skipping the explanation part here ... The problem can also be fixed by replacing the line

{ \c{outer} \cB\{ \1 \c{inner} \cB\{ #1 \cE\} \cE\} }

by

{ \c{outer} \cB\{ \1 \c{inner} \cB\{ \cL(#1) \cE\} \cE\} }

in your original code, correctly giving

enter image description here


You can use the \u feature of l3regex:

\documentclass{article}
\usepackage{regexpatch}
\newcommand{\addinnertoouter}[1]{%
  \def\berstemp{#1}%
  \regexpatchcmd{\test}
    { \c{outer} \{ (.*) \} \Z}
    { \c{outer} \cB\{ \1 \c{inner} \cB\{ \u{berstemp} \cE\} \cE\} }
    {}{error}
}
\begin{document}
\ttfamily

\def\test{\outer{\inner{abc}}}
\meaning\test

\addinnertoouter{def}
\meaning\test

\addinnertoouter{ghi}
\meaning\test

\end{document}

enter image description here