8

I want to concatenate the “source code” of two macros which share the same argument signature. How do I grab (i.e. expand once) the macro without TeX searching for the arguments which the macro requests?

\def\first#1#2thing{\one \two}
\def\second#1#2thing{\three \four}

% looking for: \def\union#1#2thing{\one \two \three \four}
\edef\union#1#2thing{\expandonce\first \expandonce\second}

As you can see, the arguments are not used in this case, but the signature must stay the same. So even better would be to do something like this:

\edef\union\argumentpatternof{\first}{\codeof\first\codeof\second}

[this section is not normative to the question... ;-]

I plan to use this to join two styles in TikZ. Like \tikzset{every label/.append style with style={other style}} or \tikzset{every label/.style={every label, other style}} or \tikzset{every label/.ecode=\pgfkeysgetvalue{/tikz/every label/.@cmd}\pgfkeysgetvalue{/tikz/other style/.@cmd}} if any of those would actually work...

  • 2
    What you want to do is usually called "patching" a definition. This is not a trivial task (see here for a very brief explanation of how it works). I suggest you take a look at the \apptocmd command from etoolbox. – Phelype Oleinik Sep 4 '19 at 19:03
  • In general you can not, from within tex, obtain the argument signature, there is a duplicate somewhere I will see if I can find it... – David Carlisle Sep 4 '19 at 19:31
  • There is no way which is reliable in all situations. You can parse the result of \meaning\macro but that does not provide sufficient information on category codes. Besides this with meaning you, e.g., cannot reliably decide whether a phrase \@name<space> is due to a control word token \@name or due to a token sequence control symbol token\@, n, a, m, e, space token. – Ulrich Diez Sep 4 '19 at 21:57
  • @PhelypeOleinik \apptocmd looks interesting! – Robert Siemer Sep 5 '19 at 0:02
7

It is possible to recover the parameter text, provided no fancy change of category codes is involved.

The two macros are supposed to have the same nonempty parameter text.

\documentclass{article}
\usepackage{xparse}

\def\first#1#2thing{first(#1)(#2)}
\def\second#1#2thing{\mbox{second(#1)(#2)}}

\ExplSyntaxOn

\tl_new:N \l__siemer_para_tl
\tl_new:N \l__siemer_repl_first_tl
\tl_new:N \l__siemer_repl_second_tl
\exp_args_generate:n { NNNV }

\NewDocumentCommand{\makeunion}{mmm}
 {% #1 = new command name, #2 = first macro, #3 = second macro
  % #2 and #3 are assumed to have the same parameter text
  \tl_set_rescan:Nnx \l__siemer_para_tl { } { \cs_argument_spec:N #2 }
  \tl_set_rescan:Nnx \l__siemer_repl_first_tl { } { \cs_replacement_spec:N #2 }
  \tl_set_rescan:Nnx \l__siemer_repl_second_tl { } { \cs_replacement_spec:N #3 }
  \tl_put_right:NV \l__siemer_repl_first_tl \l__siemer_repl_second_tl
  \exp_args:NNNNV \exp_last_unbraced:NNo \cs_new:Npn #1 \l__siemer_para_tl \l__siemer_repl_first_tl
 }

\ExplSyntaxOff

\makeunion\union\first\second

\begin{document}

\first XYthing

\second XYthing

\union XYthing

\texttt{\meaning\union}

\end{document}

enter image description here

Some explanations: with \tl_set_rescan:Nnx we store the replacement text with the correct category codes; note that \cs_argument_spec:N only returns a string (all characters of category code 12, except spaces that keep category code 10). The same for the replacement texts.

|improve this answer|||||
  • Same problem here... e.g. \first might be something else now... I need the 'source code' of \first! – Robert Siemer Sep 4 '19 at 20:56
  • @RobertSiemer Now the code for the “union” macro bears no reference to the components, it just concatenates their replacement texts. – egreg Sep 4 '19 at 21:47
  • Probably should check both arg specs are the same, also could use the etoolbox approach of checking that catcode hell is not obvious (and bail out if it is): perhaps overkill ... – Joseph Wright Sep 8 '19 at 7:40
  • @JosephWright I left it as an exercise. ;-) – egreg Sep 8 '19 at 8:03
4

As David noted in a comment, TeX cannot figure out the argument signature, you have to provide it. But assuming you can, the concatenation is straightforward. I introduce \concat with 4 arguments:

  1. the name of the concatenated macro,

  2. the argument signature of both macros to be concatenated,

  3. The first macro of the concatenation,

  4. the second macro of the concatenation.

The MWE. Here, \third is created as the concatenation of \first and \second, both of which share the argument signature of #1#2thing.

\documentclass{article}
\usepackage[T1]{fontenc}
\def\first#1#2thing{one\{#1\}two\{#2\}}
\def\second#1#2thing{three\{#1\}four\{#2\}}

\def\concat#1#2#3#4{\def#1#2{#3#2#4#2}}
\begin{document}
\concat\third{#1#2thing}\first\second

\meaning\third

\first abthing

\second abthing

\third abthing

\third\today\textit{t}thing
\end{document}

enter image description here

|improve this answer|||||
  • I don't think your solution expands the macros...: \meaning\third should not show anything about \first or '\second`, but about one two three and four... – Robert Siemer Sep 4 '19 at 20:49
  • \first and \second might not be what they were any more. – Robert Siemer Sep 4 '19 at 20:51
  • @RobertSiemer You are correct. That is a limitation of this approach. – Steven B. Segletes Sep 5 '19 at 10:18
4

As long as the ⟨parameter text⟩ which the macros have in common is known to you, you can probably do:

\long\def\exchange#1#2{#2#1}%
\long\def\exchangeafterexpand#1#2{%
  \romannumeral0%
  \expandafter\exchange\expandafter{#1}{\exchange{ }{\expandafter}#2}%
}%

\def\first#1#2thing{first-one: #1 first-two: #2}
\def\second#1#2thing{second-one: #1 second-two: #2}

\expandafter\expandafter\expandafter\exchange
\expandafter\expandafter\expandafter{%
  \expandafter\expandafter\expandafter{%
      \exchangeafterexpand{\second{#1}{#2}thing}{\first{#1}{#2}thing}%
  }%
}{\def\union#1#2thing}%

\show\first

\show\second

\show\union

\bye

enter image description here

Be aware that this does probably not work out with macros defined in terms of LaTeX's \DeclareRobustCommand and also probably does not work out with macros that process optional arguments defined in terms of LaTeX's \newcommand. Reason: Such macros actually are mechanisms where the user-level command performs some test (with \DeclareRobustCommand the definition of the token \protect is tested; with \newcommand the presence of an optional argument is tested) before calling an internal command whose name is derived from the name of the user-level-command.

Also be aware that this approach of expanding with arguments passed in parameter-syntax {#1}/{#2} does only work out as long as the definition texts of \first and/or \second do themselves not not contain definitions of other macros where arguments are denoted by ##1, ##2, i.e., by pairs of hashes. This is because pairs of hashes will be reduced to a single hash during expansion which we don't want.

Therefore if eTeX extensions are available I suggest another route:

\def\union#1#2thing{%
     Within the sequence 
        ⟨ Expansion of \first{⟨reserved token⟩1}{⟨reserved token⟩2}thing +
          Expansion of \second{⟨reserved token⟩1}{⟨reserved token⟩2}thing
        ⟩
     have every hash doubled and every instance of ⟨reserved token⟩ replaced by a single hash.
   }%
}%

This is what I implemented in the example below. This time I used LaTeX. With the example below eTeX-extensions are a requirement for implementing a reliable check for finding out whether a single token is an explicit character token of category code 6 (parameter) / for finding out whether a single token is an explicit hash character token. The gist of that test is: Apply \string to a hash and you get a single explicit character token of category code 12(other) . Apply eTeX's \detokenize to a hash and you get two such tokens because \detokenize doubles hashes.

The example below uses \romannumeral-expansion a lot: The gist of \romannumeral-expansion is that \romannumeral itself triggers a lot of expansion work but does silently not deliver any token in case after all that expansion work it finds a number which is not positive. This \romannumeral-feature is handy because it implies that in many situations a single \expandafter-chain "hitting" \romannumeral is sufficient for triggering several expansion-steps. You only need to ensure that the expansion work results in a token sequence whose leading tokens are, e.g., 0 and [space]. For \romannumeral that sequence will form the number 0 which is not positive and therefore that sequence will silently be discarded while anything behind it in the token-stream will be left in place.

I elaborated on that in my answer to the question "How can I know the number of expandafters when appending to a csname macro?".

\documentclass{article}

\makeatletter
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo,
%%    \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%%    \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%%    \UD@CheckWhetherLeadingSpace, \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has leading
%%                        catcode-1-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has no leading
%%                        catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@firstoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is a
%%                               space-token>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is not
%%                               a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
  {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
  {\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter}\expandafter\expandafter
   \expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no exclamation mark which is not nested 
%% in braces:
%%.............................................................................
%% \UD@CheckWhetherNoExclam{<Argument which is to be checked>}%
%%                         {<Tokens to be delivered in case that argument
%%                           contains no exclamation mark>}%
%%                         {<Tokens to be delivered in case that argument
%%                           contains exclamation mark>}%
%%
\newcommand\UD@GobbleToExclam{}\long\def\UD@GobbleToExclam#1!{}%
\newcommand\UD@CheckWhetherNoExclam[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToExclam#1!}%
}%
%%-----------------------------------------------------------------------------
%%  \Parameterchar@reservedFork grabs the first thing behind a
%%  a token-sequence of pattern  !!\Parameterchar@reserved!
%%.............................................................................
\newcommand\Parameterchar@reservedFork{}
\long\def\Parameterchar@reservedFork#1!!\Parameterchar@reserved!#2#3!!!!{#2}%
%%-----------------------------------------------------------------------------
%% Check whether argument consists only of the token \Parameterchar@reserved
%%.............................................................................
\newcommand\UD@CheckWhetherParameterchar@reserved[1]{%
  \romannumeral0%
  \UD@CheckWhetherNoExclam{#1}{%
    \Parameterchar@reservedFork
    %Case #1 is empty/has no tokens:
      !#1!\Parameterchar@reserved!{\UD@Exchange{ }{\expandafter}\UD@secondoftwo}%
    %Case #1 = \Parameterchar@reserved:
      !!#1!{\UD@Exchange{ }{\expandafter}\UD@firstoftwo}%
    %Case #1 = something else without exclamation-mark:
      !!\Parameterchar@reserved!{\UD@Exchange{ }{\expandafter}\UD@secondoftwo}%
      !!!!%
  }{%
    %Case #1 = something else with exclamation-mark:
    \UD@Exchange{ }{\expandafter}\UD@secondoftwo
  }%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%%   \UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  {AB}
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  { #1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%=============================================================================
%% \DoubleEveryHashAndReplaceParameterchar@reserved{<argument>}%
%%
%%   Each explicit catcode-6(parameter)-character-token of the <argument> 
%%   will be doubled. Each instance of \Parameterchar@reserved will be replaced
%%   by a single hash.
%%
%%   You obtain the result after two expansion-steps, i.e., 
%%   in expansion-contexts you get the result after "hitting" 
%%   \DoubleEveryHashAndReplaceParameterchar@reserved by two \expandafter.
%%   
%%   As a side-effect, the routine does replace matching pairs of explicit
%%   character tokens of catcode 1 and 2 by matching pairs of curly braces
%%   of catcode 1 and 2.
%%   I suppose this won't be a problem in most situations as usually the
%%   curly braces are the only characters of category code 1 / 2...
%%
%%   This routine needs \detokenize from the eTeX extensions.
%%-----------------------------------------------------------------------------
\newcommand\DoubleEveryHashAndReplaceParameterchar@reserved[1]{%
   \romannumeral0\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop{#1}{}%
}%
\newcommand\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop[2]{%
  \UD@CheckWhetherNull{#1}{ #2}{%
    \UD@CheckWhetherLeadingSpace{#1}{%
       \expandafter\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop
       \expandafter{\UD@removespace#1}{#2 }%
    }{%
      \UD@CheckWhetherBrace{#1}{%
        \expandafter\expandafter\expandafter\UD@PassFirstToSecond
        \expandafter\expandafter\expandafter{%
        \expandafter\UD@PassFirstToSecond\expandafter{%
            \romannumeral0%
            \expandafter\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop
            \romannumeral0%
            \UD@ExtractFirstArgLoop{#1\UD@SelDOm}{}%
        }{#2}}%
        {\expandafter\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop
         \expandafter{\UD@firstoftwo{}#1}}%
      }{%
        \expandafter\UD@CheckWhetherHash
        \romannumeral0\UD@ExtractFirstArgLoop{#1\UD@SelDOm}{#1}{#2}%
      }%
    }%
  }%
}%
\newcommand\UD@CheckWhetherHash[3]{%
  \expandafter\UD@CheckWhetherLeadingSpace\expandafter{\string#1}{%
    \expandafter\expandafter\expandafter\UD@CheckWhetherNull
    \expandafter\expandafter\expandafter{%
    \expandafter\UD@removespace\string#1}{%
      \expandafter\expandafter\expandafter\UD@CheckWhetherNull
      \expandafter\expandafter\expandafter{%
      \expandafter\UD@removespace\detokenize{#1}}{%
        % Something whose stringification yields a single space
        \UD@secondoftwo
      }{% Explicit space of catcode 6
        \UD@firstoftwo
      }%
    }{% Something whose stringification has a leading space
      \UD@secondoftwo
    }%
  }{%
    \expandafter\expandafter\expandafter\UD@CheckWhetherNull
    \expandafter\expandafter\expandafter{%
    \expandafter\UD@firstoftwo
    \expandafter{\expandafter}\string#1}{%
      \expandafter\expandafter\expandafter\UD@CheckWhetherNull
      \expandafter\expandafter\expandafter{%
      \expandafter\UD@firstoftwo
      \expandafter{\expandafter}\detokenize{#1}}{%
        % no hash
        \UD@secondoftwo
      }{% hash
        \UD@firstoftwo
      }%
    }{% no hash
      \UD@secondoftwo
    }%
  }%
  {% hash
    \expandafter\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop
    \expandafter{\UD@firstoftwo{}#2}{#3#1#1}%
  }{% no hash
    \UD@CheckWhetherParameterchar@reserved{#1}{%
      \expandafter\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop
      \expandafter{\UD@firstoftwo{}#2}{#3##}%
    }{%
      \expandafter\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop
      \expandafter{\UD@firstoftwo{}#2}{#3#1}%
    }%
  }%
}%
%%=============================================================================
\newcommand\UD@TripleExpandAndConcatSecondAndThird[3]{%
   \romannumeral0\expandafter\UD@PassFirstToSecond\expandafter{%
     \romannumeral0%
     \expandafter\UD@Exchange\expandafter{%
       \romannumeral0%
       \UD@Exchange{ }{\expandafter\expandafter\expandafter
                       \expandafter\expandafter\expandafter\expandafter}%
       #3%
     }{%
       \UD@Exchange{ }{\expandafter\expandafter\expandafter
                       \expandafter\expandafter\expandafter\expandafter}#2%
     }%
   }{ #1}%
}%
%%=============================================================================

\def\first#1#2thing{first-one: #1 first-two: #2 \def\firsttest##1{##1}}
\def\second#1#2thing{second-one: #1 second-two: #2 \def\secondtest##1{##1}}

\UD@TripleExpandAndConcatSecondAndThird{%
  \def\union#1#2thing%
}{%
  \expandafter\DoubleEveryHashAndReplaceParameterchar@reserved
  \expandafter{\first{\Parameterchar@reserved1}{\Parameterchar@reserved2}thing}%
}{%
  \expandafter\DoubleEveryHashAndReplaceParameterchar@reserved
  \expandafter{\second{\Parameterchar@reserved1}{\Parameterchar@reserved2}thing}%
}%

\show\first

\show\second

\show\union

\stop

enter image description here

Of course this does work out only with macros whose ⟨definition text⟩ does not contain the token \Parameterchar@reserved and where replacing arbitrary explicit character tokens of category code 1 (begin group) and matching arbitrary explicit character tokens of category code 2 (end group) by explicit left-curly-brace-character tokens of category code 1 (begin group) and matching explicit right-curly-brace-character tokens of category code 2 (end group) does not matter.

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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