38

I've been banging my head against this for days. I have a macro which takes a macro name and its arguments. It then re-arranges the arguments a bit and then calls the passed macro with the re-arranged arguments and so ends like this:

#1{\arga}{\argb}{\argc}

where arga ... \argc are the processed arguments. However. I need to have \arga ... \argc fully expanded before the macro stored in #1 is called. I cannot for the life of me work this out after days of playing with \expandafter, \noexpand, etextools etc. I can't use expl3 and I'd really prefer not to use etextools but etoolbox is available. Here is a MWE:

\documentclass{article}
\begin{document}

\def\x#1#2#3#4{%
  \def\arga{#2}%
  \def\argb{#3}%
  \def\argc{#4}%
  #1{\arga}{\argb}{\argc}}

\def\y#1#2#3{\detokenize{#1#2#3}}

\x\y{arg1}{arg2}{arg3}

\end{document}

This results in "\arga \argb \argc" but I want "arg1arg2arg3". \expandafter will do this in \x but not for three arguments in a row. etextools has some macros to do this but I really want to avoid it (it clashes with etoolbox in some ways and I must have etoolbox). I couldn't get the etextools macros to work even when I tried (\ExpandNextTwo etc.)

UPDATE: I realised that in my case, the args can contain robust macros like:

\documentclass{article}
\usepackage{etoolbox}
\begin{document}

\def\x#1#2#3#4{%
  \def\arga{#2}%
  \edef\argb{\ifstrequal{#3}{arg2}{arg2}{}}%
  \def\argc{#4}%
  {\protected@edef\z{\noexpand#1{\arga}{\argb}{\argc}}\z}}

\def\y#1#2#3{\detokenize{#1#2#3}}

\x\y{arg1}{arg2}{arg3}

\end{document}

In which case (using egreg's answer as an example), it isn't fully expanded. As Joseph and egreg mention below, this isn't doable, you just have to use non-robust macros in such cases, for example. I'll let the question stand as it because it is informative.

4
  • Is this your actual use-case? You want/need \detokenize?
    – Werner
    Mar 27, 2013 at 8:15
  • @Werner I think this is a very simplified case: the \defs here are clearly just to illustrate the point, as you don't actually need them in the minimised case :-)
    – Joseph Wright
    Mar 27, 2013 at 8:17
  • I don't need detokenize, it's just there for illustration, as Joseph says.
    – PLK
    Mar 27, 2013 at 9:25
  • Robust macros can't be fully expanded, at least not in the way expected. Almost always they are robust because inside something unexpandable happens (like an assignment). That means you can't get out of them the 'text' or whatever, so it's a was of time worrying!
    – Joseph Wright
    Mar 27, 2013 at 11:53

4 Answers 4

34

The 'classical' approach is to use \expandafter

\documentclass{article}
\begin{document}

\def\x#1#2#3#4{%
  \def\arga{#2}%
  \def\argb{#3}%
  \def\argc{#4}%
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter#1%
    \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
      {\expandafter\expandafter\expandafter\arga\expandafter\expandafter\expandafter}%
        \expandafter\expandafter\expandafter{\expandafter\argb\expandafter}\expandafter
          {\argc}}

\def\y#1#2#3{\detokenize{#1#2#3}}

\x\y{arg1}{arg2}{arg3}

\end{document}

where we need so many of them to expand arg3 then arg2 and finally arg1. (This is what is effectively wrapped up in expl3's \exp_args:Nooo).

The rule of the number of \expandafters we need is 2n – 1, where n is how many tokens we want to expand. So for one token somewhere ahead, we need just one \expandafter in each place to be 'skipped', to expand two tokens (second one then the first one) we need three \expandafters, for three tokens (as in the current case) we need seven \expandafters, and so one. This is easiest to see if you write/print out a short second and cross off the commands as TeX would read them: you'll find everything works out.

With e-TeX available, we can use an \edef and \unexpanded:

\documentclass{article}
\begin{document}

\def\x#1#2#3#4{%
  \def\arga{#2}%
  \def\argb{#3}%
  \def\argc{#4}%
  \begingroup
    \edef\x{%
      \endgroup
      \noexpand#1
        {\unexpanded\expandafter{\arga}}%
        {\unexpanded\expandafter{\argb}}%
        {\unexpanded\expandafter{\argc}}%
    }%
  \x
}    

\def\y#1#2#3{\detokenize{#1#2#3}}

\x\y{arg1}{arg2}{arg3}

\end{document}

(You can do the same without e-TeX using a series of toks, but that gets a bit confusing so I'd not normally do it.)


The question says no expl3, but for contrast the approach using a minimium of the functions it provides would read

\documentclass{article}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn
\def\x#1#2#3#4{
  \def\arga{#2}
  \def\argb{#3}
  \def\argc{#4}
  \exp_args:Nooo#1\arga\argb\argc
}    
\ExplSyntaxOff
\def\y#1#2#3{\detokenize{#1#2#3}}

\x\y{arg1}{arg2}{arg3}

\end{document}

which is I hope a lot more readable. (I'd probably want to use \exp_args:NVVV as we are using 'value stored in a variable', but that function is not pre-defined so I've avoided it here.)

7
  • 2
    Can you detail the rule you used to know how many \expandafters were needed in the classical approach? Thanks.
    – cjorssen
    Mar 27, 2013 at 8:33
  • Regarding the etex approach, I usually use \expandafter\unexpanded\expandafter{. So I don't need the first \expandafter?
    – cjorssen
    Mar 27, 2013 at 8:34
  • 1
    Brilliant, thanks. I just couldn't face working out the infinite \expandafter mantra to do this. The etex trick is nice too. Since this is for biblatex, I don't think we want to use expl3 just yet, unfortunately ...
    – PLK
    Mar 27, 2013 at 8:36
  • @cjorssen No, you don't need it: \unexpanded expands tokens looking for a <filler> (a sequence of space or \relax tokens), so it expands the token after it.
    – egreg
    Mar 27, 2013 at 8:37
  • 1
    @cjorssen There was a question very early in the life of the site about this (where you can save unnecessary tokens). \unexpanded works in the same way as a toks, and the rule there is that TeX will expand until it finds the first brace. Very useful when you want to expand more than once: \unexpanded\expandafter\expandafter\expandafter{\expandafter\foo\baz}, etc.
    – Joseph Wright
    Mar 27, 2013 at 9:03
18

If you really want the arguments to be fully expanded, then

\documentclass{article}
\begin{document}

\def\x#1#2#3#4{%
  \begingroup\edef\z{\endgroup\noexpand#1{#2}{#3}{#4}}\z
}

\def\y#1#2#3{\detokenize{#1#2#3}}

\def\foo{This is foo}

\texttt{\x\y{arg1}{arg2}{arg3\foo}}

\end{document}

will result in printing

arg1arg2arg3This is foo

whereas the \expandafter based solutions would print

arg1arg2arg\foo
7
  • Probably should go with \protected@edef if you are going this way, I'd say
    – Joseph Wright
    Mar 27, 2013 at 8:58
  • @JosephWright It depends, of course, on what the arguments contain and what is to be done with them.
    – egreg
    Mar 27, 2013 at 9:25
  • Hmm, seems that robust macros in the args won't expand in such cases, see updated, MWE above.
    – PLK
    Mar 27, 2013 at 9:31
  • @PLK Robust macros such as \ifstrequal will never be expanded in a \edef; but also the \expandafter method would do essentially the same.
    – egreg
    Mar 27, 2013 at 10:40
  • 1
    @dpatru It's a common idiom for not leaving behind a definition for \z: \endgroup will be executed only after \z has been expanded.
    – egreg
    Jul 16, 2017 at 13:29
1

With functional package, tricky argument expansion can be replaced by intuitive function composition, which is similar to other programmming languages such as Lua.

The evaluation of composite functions is from inside to outside. And \Expand is a predefined function.

\documentclass{article}
\usepackage{functional}
\begin{document}

\PrgNewFunction \x {Nmmm} {%
  #1{\Expand{#2}}{\Expand{#3}}{\Expand{#4}}%
}

\PrgNewFunction \xx {Nmmm} {%
  #1{\Expand{#2}}{\Expand{\StrIfEqTF{#3}{arg2}{\Result{arg2}}{}}}{\Expand{#4}}%
}

\PrgNewFunction \y {mmm} {\detokenize{#1,#2,#3}}

\x\y{arg1}{arg2}{arg3}

\xx\y{arg1}{arg2}{arg3}

\end{document}

enter image description here

1

Only traditional (La)TeX:

\documentclass{article}

\newcommand\Exchange[2]{#2#1}

\makeatletter
\DeclareRobustCommand\x[4]{%
  \begingroup
  \let\protect=\@unexpandable@protect
  \edef\scratchmacro{{#2}{#3}{#4}}%
  % #1 can easily consist of more than a single token when using
  % \Exchange instead of doing "\expandafter-hopping"
  \expandafter\Exchange\expandafter{\scratchmacro}{\endgroup#1}%
}%
\makeatother

% \detokenize doubles hashes.
% Thus expanding \scratchmacro first halves the amount of 8 hashes of catcode 6 and you
% have 4 hashes of catcode 6.
% Then \detokenize doubles the amount of 4 hashes hashes of catcode 6 and you have 8 
% hashes of catcode 12.

\newcommand\y[3]{\detokenize{\y{#1}{#2}{#3}}}

\begin{document}

\def\arga{arg1}%
\def\argb{arg2}%
\def\argc{arg3}%

\texttt{\x\y{\arga}{\argb}{\argc ########}}

\end{document}

enter image description here

Caveats:

  • \x is not fully expandable as assignments are performed and grouping/scoping is used.
  • A scratch-macro is defined from the arguments of \x and afterwards expanded. Expanding the scratch-macro leads to amounts of consecutive hashes provided as components of arguments of \x being halved.

If e-TeX-extensions are available, you can use \unexpanded:

\documentclass{article}

\makeatletter
\DeclareRobustCommand\x[4]{%
  \begingroup
  \let\protect=\@unexpandable@protect
  \edef\scratchmacro{\endgroup\unexpanded{#1}{#2}{#3}{#4}}%
  \scratchmacro
}%
\makeatother

% \detokenize doubles hashes.
% Thus expanding \scratchmacro first halves the amount of 8 hashes of catcode 6 and you
% have 4 hashes of catcode 6.
% Then \detokenize doubles the amount of 4 hashes hashes of catcode 6 and you have 8 
% hashes of catcode 12.

\newcommand\y[3]{\detokenize{\y{#1}{#2}{#3}}}

\begin{document}

\def\arga{arg1}%
\def\argb{arg2}%
\def\argc{arg3}%

\texttt{\x\y{\arga}{\argb}{\argc ########}}

\end{document}

enter image description here

Caveats:

  • \x is not fully expandable as assignments are performed and grouping/scoping is used.
  • A scratch-macro is defined from the arguments of \x and afterwards expanded. Expanding the scratch-macro leads to amounts of consecutive hashes provided as components of arguments of \x being halved.

If you work with an engine where \expanded is available:

\documentclass{article}

\newcommand\Exchange[2]{#2#1}
\makeatletter
\DeclareRobustCommand\x[4]{%
  \begingroup
  \let\protect=\@unexpandable@protect
  \expanded{\endgroup\unexpanded{#1}{#2}{#3}{#4}}%
}%
\makeatother

% \detokenize doubles hashes.
% \detokenize doubles the amount of 8 hashes hashes of catcode 6 and you have 16 
% hashes of catcode 12.

\newcommand\y[3]{\detokenize{\y{#1}{#2}{#3}}}

\begin{document}

\def\arga{arg1}%
\def\argb{arg2}%
\def\argc{arg3}%

\texttt{\x\y{\arga}{\argb}{\argc ########}}

\end{document}

enter image description here

Caveat:

  • \x is not fully expandable as assignments are performed and grouping/scoping is used.

Benefit:

  • No temporary macro is defined from the arguments of \x whose expanding would lead to halving amounts of consecutive hashes provided as components of arguments of \x.

The following variant is fully expandable as it does neither define a scratch-macro nor perform a \let-assignment for taking LaTeX's \protect-mechanism into account nor use grouping/scoping:

\documentclass{article}

\newcommand\x[4]{%
  \expanded{\unexpanded{#1}{#2}{#3}{#4}}%
}%

% \detokenize doubles hashes.
% \detokenize doubles the amount of 8 hashes hashes of catcode 6 and you have 16 
% hashes of catcode 12.

\newcommand\y[3]{\detokenize{\y{#1}{#2}{#3}}}

\begin{document}

\def\arga{arg1}%
\def\argb{arg2}%
\def\argc{arg3}%

\texttt{\x\y{\arga}{\argb}{\argc ########}}

\end{document}

enter image description here

Caveat: This variant does not take the oldschool \protect-mechanism of the LaTeX 2ε-kernel into account and fails with fragile commands.

Benefits:

  • This variant of \x is fully expandable.
  • No temporary macro is defined from the arguments of \x whose expanding would lead to halving amounts of consecutive hashes provided as components of arguments of \x.

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.