3

How would you expand a macro twice in LaTeX 2 (or 2epsilon). I am aware of the question What is the preferred way of expanding twice in expl3?, but I'm curious as to how you'd do it in LaTeX.

I'd like to point out that I suspect that if you actually need to do this in your code, you'd probably want to reformulate your code and not find a way to do this. However, I'm still curious for educational purposes.

Below is my (failing) attempt

\documentclass{article}
\makeatletter
% Command for printing stuff to the error log
\def\inspect#1{\@latex@warning{\string#1:\meaning#1}}
\makeatother
\def\a{3}
\def\b{2\a}
\def\c{1\b}
% I want to obtain a macro containing "12\a" from using only \c
% Expand once
\edef\expandedOnce{\unexpanded\expandafter{\c}}
\inspect\expandedOnce
%% ^ produces 1\b in the error log
\expandafter\expandafter\edef\expandafter\expandedTwice{\unexpanded\expandafter{\expandedOnce}}
\inspect\expandedTwice
%% ^ Also produces 1\b in the error log, but I'd want it to
%% produce 12\a
\begin{document}
~
\end{document}
  • 2
    you haven't really defined what expanding twice means, normally i'd take it to mean \expandafter\expandafter\expandafter\c but 1 isn't expandable so that gives you 1\b you say you want 12\a but do you want to expand all tokens in \c by one step, or just the first expandable token. (clearly you don't mean the first token, as I note above.) what would you want if the definition of \c was \def\c{1\b1\b} ? – David Carlisle Nov 24 '18 at 21:38
  • Good question! As I note in my original question, I don't have a specific use case. I'm mostly curious about expanding all tokens in \c by one step though. – Andreas Storvik Strauman Nov 24 '18 at 21:40
  • @DavidCarlisle, for the case when the definition is \def\c{1\b1\b}, I then guess I'd want the result to be 12\a12\a because then, since 1 can't be expanded further, it stays as a 1 and \b is 2\a after one expansion. – Andreas Storvik Strauman Nov 24 '18 at 21:43
  • @DavidCarlisle No, wait. That's not twice. – Andreas Storvik Strauman Nov 24 '18 at 21:45
  • 1
    I posted an answer that I think matches what you ask for but it is not at all the expansion order that tex would use. You are expanding everything in the list first by one step, then expanding everything in the resulting list, TeX would fully expand the first token in as many steps as it took before expanding the second token so tokens at teh end of the original list may not be exanded until many expansion steps, or in fact may never be expanded at all. – David Carlisle Nov 24 '18 at 21:51
4

Newer new answer

The following expands every expandable token once and if that token needs arguments they are supplied (doesn't work for delimited arguments like with \def\foo#1.{#1}). Note that this is not necessarily how TeX would expand things. I created it mostly because I was curious how one could do it.

\documentclass[]{article}

\newcommand\foo{\bazA}
\newcommand\bazA{\bazB}
\newcommand\bazB{bar}

\def\a{3}
\def\b{2\a}
\makeatletter
\def\c{1\b 9}
\makeatother

\def\afterfi#1\fi{\fi#1}
\def\afterelsefi#1\else#2\fi{\fi#1}
\def\afterorfi#1\or#2\fi{\fi#1}
\def\afterfiB\fi#1#2{\fi#2}
\def\afterelsefiA\else#1\fi#2#3{\fi#2}
\makeatletter
\newcommand\ifempty[1]%>>>
  {%
    \if\relax\detokenize{#1}\relax
      \afterelsefiA
    \else
      \afterfiB
    \fi
  }%<<<
\newcommand\ifdigit[1]%>>>
  {%
    \ifx1#1\afterelsefiA
    \else\ifdigit@b2#1%
    \else\ifdigit@b3#1%
    \else\ifdigit@b4#1%
    \else\ifdigit@b5#1%
    \else\ifdigit@b6#1%
    \else\ifdigit@b7#1%
    \else\ifdigit@b8#1%
    \else\ifdigit@b9#1%
    \else\ifdigit@b0#1%
    \else\afterfiB
    \fi
  }%<<<
\newcommand\ifdigit@b[2]%>>>
  {%
    \fi\ifx#1#2\afterelsefiA
  }%<<<
\def\q@stop{\q@stopError}
\def\q@mark{\q@markError}
\long\def\expandingloop@a#1#%>>>
  {%
    \expandingloop@b#1\q@stop
  }%<<<
\long\def\expandingloop@b#1%>>>
  {%
    \ifx\q@stop#1%
      \afterelsefi\expandingloop@c
    \else
      \afterfi\expandingloop@d#1%
    \fi
  }%<<<
\newcommand\expandingloop@c[1]%>>>
  {%
    \ifempty{#1}
      {{}\expandingloop@a}
      {%
        \ifx\q@stop#1%
        \else
          \afterfi{\expandingloop@a#1{\q@stop}}\expandingloop@a%
        \fi
      }%
  }%<<<
\newcommand\expandingloop@d[1]%>>>
  {%
    \ifcase\testargs{#1}
      \afterorfi\unexpanded\expandafter{#1}\expandingloop@b%
    \or\afterorfi\expandingloop@d@i{#1}%
    \or\afterorfi\expandingloop@d@ii{#1}%
    \or\afterorfi\expandingloop@d@iii{#1}%
    \or\afterorfi\expandingloop@d@iv{#1}%
    \or\afterorfi\expandingloop@d@v{#1}%
    \or\afterorfi\expandingloop@d@vi{#1}%
    \or\afterorfi\expandingloop@d@vii{#1}%
    \or\afterorfi\expandingloop@d@viii{#1}%
    \or\afterfi\expandingloop@d@ix{#1}%
    \or\DelimitedArgumentError
    \fi
  }%<<<
\newcommand\expandingloop@d@group[3]%>>>
  {%
    \ifx\q@stop#3%
      \OutOfArgumentsError
    \else
      \afterfi\expandingloop@d@group@a{#1}{#2{#3}}%
    \fi
  }%<<<
\def\expandingloop@d@group@a#1#2#3#%>>>
  {%
    #1{#2}#3\q@stop
  }%<<<
\newcommand\expandingloop@d@exec[1]%>>>
  {%
    \unexpanded\expandafter{#1}\expandingloop@b
  }%<<<
\newcommand\expandingloop@d@i[2]%>>>
  {%
    \ifx\q@stop#2%
      \afterelsefi\expandingloop@d@group{\expandingloop@d@exec}{#1}%
    \else
      \afterfi\expandingloop@d@exec{#1#2}%
    \fi
  }%<<<
\newcommand\def@expandingloop@d@[2]%>>>
  {%
    \expandafter\edef\csname expandingloop@d@#1\endcsname##1##2%
      {%
        \unexpanded{\ifx\q@stop}##2%
          \unexpanded{\afterelsefi\expandingloop@d@group}%
          \expandafter\noexpand\csname expandingloop@d@#2\endcsname{##1}%
        \unexpanded{\else
          \afterfi}%
          \expandafter\noexpand\csname expandingloop@d@#2\endcsname{##1##2}%
        \noexpand\fi
      }%
  }%<<<
\def@expandingloop@d@{ix}{viii}
\def@expandingloop@d@{viii}{vii}
\def@expandingloop@d@{vii}{vi}
\def@expandingloop@d@{vi}{v}
\def@expandingloop@d@{v}{iv}
\def@expandingloop@d@{iv}{iii}
\def@expandingloop@d@{iii}{ii}
\def@expandingloop@d@{ii}{i}
\newcommand\singleallexpand[1]%>>>
  {%
    \edef#1{\expandafter\expandingloop@a#1{\q@stop}}%
  }%<<<
\newcommand\testargs[1]%>>>
  {%
    \expandafter\testargs@a\meaning#1->\q@mark\q@stop%
  }%<<<
\long\def\testargs@a#1->#2#3\q@stop%>>>
  {%
    \ifx\q@mark#2%
      \afterelsefi0%
    \else
      \afterfi\testargs@b#1\q@stop
    \fi
  }%<<<
\long\def\testargs@b#1:#2\q@stop%>>>
  {%
    \ifempty{#2}
      {0}
      {\testargs@c#2\q@stop}%
  }%<<<
\begingroup
\catcode`\#=12
\def\zz{\endgroup\def\myhashtag{#}}
\zz
\long\edef\testargs@c#1#2#3\q@stop%>>>
  {%
    \noexpand\ifx\myhashtag#1%
      \noexpand\ifdigit{#2}
        {%
          \noexpand\ifempty{#3}
            {\noexpand\afterelsefi#2}
            {\noexpand\afterelsefi\noexpand\testargs@c#3\noexpand\q@stop}%
        }
        {10}% macros don't take >9 arguments so this is a great error flag
    \noexpand\else
      10% macros don't take >9 arguments so this is a great error flag
    \noexpand\fi
  }%<<<
\makeatother

\newcommand\Meaning[1]{\texttt{\meaning#1}}

\begin{document}
\noindent
For \verb|\foo|:

\let\tmp\foo
Unexpanded:
\Meaning\tmp

\singleallexpand\tmp
Expanded once:
\Meaning\tmp

\singleallexpand\tmp
Expanded twice:
\Meaning\tmp

\noindent
For \verb|\c|:

\let\tmp\c
Unexpanded:
\Meaning\tmp

\singleallexpand\tmp
Expanded once:
\Meaning\tmp

\singleallexpand\tmp
Expanded twice:
\Meaning\tmp
\end{document}

New answer

The following expands every token in \tmp (which is what you want, I hope) in a more reliable way. I didn't test it thoroughly though. It should only work for contents that don't take arguments.

\documentclass[]{article}

\newcommand\foo{\bazA}
\newcommand\bazA{\bazB}
\newcommand\bazB{bar}

\def\a{3}
\def\b{2\a}
\def\c{1{\b}}

\def\afterfi#1\fi{\fi#1}
\makeatletter
\def\q@stop{\q@stop}
\def\expandingloop@a#1#%
  {%
    \expandingloop@b#1\q@stop
    \expandingloop@c
  }
\def\expandingloop@b#1%
  {%
    \ifx\q@stop#1%
    \else
      \afterfi\unexpanded\expandafter{#1}\expandingloop@b
    \fi
  }
\newcommand\expandingloop@c[1]
  {%
    \ifx\q@stop#1%
    \else
      \afterfi{\expandingloop@a#1{\q@stop}}\expandingloop@a%
    \fi
  }
\newcommand\singleallexpand[1]
  {%
    \edef#1{\expandafter\expandingloop@a#1{\q@stop}}%
  }
\makeatother

\newcommand\Meaning[1]{\texttt{\meaning#1}}


\begin{document}
\noindent
For \verb|\foo|:

\let\tmp\foo
Unexpanded:
\Meaning\tmp

\singleallexpand\tmp
Expanded once:
\Meaning\tmp

\singleallexpand\tmp
Expanded twice:
\Meaning\tmp

\noindent
For \verb|\c|:

\let\tmp\c
Unexpanded:
\Meaning\tmp

\singleallexpand\tmp
Expanded once:
\Meaning\tmp

\singleallexpand\tmp
Expanded twice:
\Meaning\tmp
\end{document}

enter image description here


Old answer

Both of the following only expand the first token.

You can use \edef\fooA{\unexpanded\expandafter\expandafter\expandafter{\fooB}} to define \fooA to be the same as a \fooB expanded twice.

Doing stuff with a temporary macro one could do something like the following.

\documentclass[]{article}

\newcommand\foo{\bazA}
\newcommand\bazA{\bazB}
\newcommand\bazB{bar}

\newcommand\singleexpand[1]
  {%
    \edef#1{\unexpanded\expandafter\expandafter\expandafter{#1}}%
  }

\newcommand\Meaning[1]{\texttt{\meaning#1}}


\begin{document}
\let\tmp\foo
Unexpanded:
\Meaning\tmp

\singleexpand\tmp
Expanded once:
\Meaning\tmp

\singleexpand\tmp
Expanded twice:
\Meaning\tmp
\end{document}

Being evil when the argument contains a group (but it works in the minimal example, everything else is a matter of adding an infinite number of tests):

\def\afterfi#1\fi{\fi#1}
\def\expandingloop#1#2\endexpandingloop
  {%
    \unexpanded\expandafter{#1}%
    \if\relax\detokenize{#2}\relax
    \else
      \afterfi\expandingloop#2\endexpandingloop
    \fi
  }
\newcommand\singleallexpand[1]
  {%
    \edef#1{\expandafter\expandingloop#1\endexpandingloop}%
  }
  • Sorry. I really should have explicitly said that it's not only the first expandable token. – Andreas Storvik Strauman Nov 24 '18 at 21:45
5

I get

> \zz=macro:
->12\a 12\a .

on the terminal from etex (or pdftex) from

\def\a{3}
\def\b{2\a}
\def\c{1\b1\b}
\def\afterfi#1\fi{\fi#1}
\def\foo#1{\ifx\relax#1\else\afterfi\expandafter\unexpanded\expandafter{#1}\foo\fi}
\edef\zz{\expandafter\foo\c\relax}

\show\zz

\bye

Note that this is the expansion order that you asked for, as far as I can tell but is not the order that TeX would use normally so it isnt really "expanding twice"

Consider

\def\a{\b} \def\b#1{}  \def\c{zzzzz}
\def\z{\a\c}

by your definition I think you want to expand \a and \c once in the first step so getting a "first expansion" of \z as \b zzzzz then on a second step expand \b so get zzzz.

However TeX would fully expand the first token at each stage, so in the first step get \b\c then in the second step get an empty list. \c would never be expanded at all by TeX.

1

I think a reliable algorithm for expanding outgoing from an arbitrary set of tokens at most k times is not possible:

Expanding at most one time is already a problem:

You'd need an algorithm which recursively iterates on a ⟨list of not yet expanded tokens⟩ and maintains a ⟨list of already expanded tokens⟩ as follows:


Step 1:

Check whether the ⟨list of not yet expanded tokens⟩ is empty.

If so: Deliver the ⟨list of already expanded tokens⟩.

If not so:

  • In case the first element/the first token of the ⟨list of not yet expanded tokens⟩ is not expandable, remove it from the ⟨list of not yet expanded tokens⟩ and add it at the end of the ⟨list of already expanded tokens⟩.

    In case the first element/the first token of the ⟨list of not yet expanded tokens⟩ is expandable, expand it and remove the result of that expansion from the ⟨list of not yet expanded tokens⟩ and add the result of that expansion at the end of the ⟨list of already expanded tokens⟩

  • Repeat Step 1.

One crucial point hereby is "removing the result of that expansion from the ⟨list of not yet expanded tokens⟩ and adding the result of that expansion at the end of the ⟨list of already expanded tokens⟩".

That point is crucial because it implies that after expansion you need to detect which tokens belong to the result of that expansion and which tokens were already in the ⟨list of not yet expanded tokens⟩ before that expansion took place.

Assume, e.g., a macro which is defined as follows: \def\macro#1#2#3{\A\B\C}
, while the ⟨list of not yet expanded tokens⟩ holds the following content:
\macro \A\B\C Whatsoever

Now you need to have \macro expanded once. After that, the ⟨list of not yet expanded tokens⟩ holds the following content:
\A\B\C Whatsoever.

Now you need to move the result from expanding \macro towards the ⟨list of already expanded tokens⟩.

In this case the result of expanding \macro is formed by the tokens \A\B\C.

How to detect that these \A\B\C are the replacement-text/the expansion-result from expanding \macro and thus are not the same as the \A\B\C that before expansion were in the ⟨list of not yet expanded tokens⟩?

(Parsing the result of \meaning with every expandable token for finding out whether it is a macro that processes arguments does not deliver reliable information about the parameter-texts of macros because with \meaning you don't have reliable information about the category-codes of tokens / about whether a set of delivered characters denotes a set of character tokens (of whatsoever category code) or a control sequence...)

Another crucial point is that you'd need to iterate "token-wise" while macros usually work "argument-wise".


In your special case, where you already know about definitions and tokens delivered by expansion, you can, e.g., do:

\def\a{3}
\def\b{2\a}
\def\c{1\b}
% You wish to obtain a macro containing "12\a" from using only \c


% \expandaf-% \expandaf-%
% ter-      % ter-      %
% chain 1   % chain 2   %
%    |      %    |      %
\expandafter\expandafter
\expandafter            \def
\expandafter\expandafter
\expandafter            \expandedTwice
\expandafter\expandafter
\expandafter            {%
\expandafter\expandafter
\c}

\expandafter-chain 1 delivers:

% \expandaf-%
% ter-      %
% chain 2   %
%    |      %
\expandafter
            \def
\expandafter
            \expandedTwice
\expandafter
            {%
\expandafter
1\b}

\expandafter-chain 2 delivers:

\def
\expandedTwice
{%
12\a}

(In case you are not familiar to \expandafter:

\expandafter is an expandable primitive which works on the next and on the next but one token:

In case the next but one token is expandable, the replacement-text of
\expandafter⟨next token⟩⟨next but one token⟩
will be
⟨next token⟩⟨top-level-expansion of next but one token⟩.

In case the next but one token is not expandable, the replacement-text of
\expandafter⟨next token⟩⟨next but one token⟩
will be
⟨next token⟩⟨next but one token⟩.

In other words: (La)TeX considers the \expandafter-work done and removes the \expandafter from the token-stream when top-level-expanding the next-but-one token is finished.
That's why you can have \expandafter-chains for "hopping" over tokens that shall not be expanded.

E.g., if you have \def\foo{bar}, and do
\expandafter 1\expandafter 2\expandafter 3\foo
, you will get
123bar:

Carrying out the first \expandafter causes the second \expandafter to be carried out. Hereby "carrying out the second \expandafter" is considered an aspect of carrying out the first \expandafter.
Carrying out the second \expandafter in turn causes the third \expandafter to be carried out. Hereby "carrying out the third \expandafter" is considered an aspect of carrying out the second \expandafter. The third \expandafter causes \foo to be top-level-expanded.
When the top-level-expansion of \foo is delivered, (La)TeX will consider the expansion-work of the third \expandafter done.
This expansion-work was initiated by the second \expndafter. As the expansion-work initiated by the second \expandafter is done, now the expansion-work of the second \expandafter is done. The expansion-work of the second \expandafter was initiated by the first \expandafter.
As the expansion-work initiated by the first \expandafter is done, now the expansion-work of the first \expandafter is done.)


But if you have, e.g.,

\catcode`\(=1 %
\catcode`\)=2 %
\def\a{3}
\def\b{2\a}
\def\c{11111(1)11111\b}

, and wish to obtain 11111(1)111112\a—parentheses still of catcode 1 / 2 —outgoing from \c, this will probably turn out an interesting task.


By the way 1: The methods of choice for obtaining the result of expansion highly depend on the context:

Within a "pure expansion context", i.e., e.g., within \csname..\endcsname or within \write{...} or within \edef{..} you cannot have LaTeX define temporary macros/you cannot have LaTeX perform whatsoever assignments (with the exception of the result of \csname..\endcsname locally assigning the meaning of the \relax-primitive in case the control-sequence-token constructed is undefined).

By the way 2: \edef/ \xdef is not reliable in all situations.
E.g., look at:

\edef\test{%
  Where does the assignment end? Here? \iffalse{\fi}%
  {\iffalse}\fi Or here?%
}%
\par
\meaning\test
0

I'm fairly certain that the following will do expansion of the macros as described in the original question: (In hindsight, using semicolon as termination char might not be the best design choice). Maybe I'm missing something as to why this will not work?

\documentclass{article}
\makeatletter
\def\a{3}
\def\b{2\a}
\def\c{1\b}
\def\@iterator{%
    % Expects to be followed by a list of tokens terminated
    % by a semicolon. If the next character is not a semicolon
    % then consider it a token to expand.
    % If the next character is a semicolon then we're done iterating
    % and can finalising procedures
    \@ifnextchar;\@finishIter\@processnext%
}
\newcommand\tmp@exptok{}
\def\@processnext#1{%
    % Proces one token and add it to our macro containing
    % \tmp@exptok contains all the tokens expanded once.
    % So we're now adding this next token.
    \xdef\tmp@exptok{\unexpanded\expandafter{\tmp@exptok}\unexpanded\expandafter{#1}}\@iterator%
}
\def\@finishIter;{\global\let\expandedResult\tmp@exptok\gdef\tmp@exptok{}}
\newcommand\expandtwice[2]{%
    % First do one expansion
    \@iterator#1;%
    % Now all tokens are expanded once and contained in \expandedResult
    % Now we reapply it to re-expand every token once again
    \expandafter\@iterator\expandedResult;%
    % Now store the result in the macro given by the user
    \let#2\expandedResult%
}
\makeatother
\expandtwice{\c\c}{\cTwiceExpanded}
% \c\c -> {1\b}{1\b} -> 1{2\a}1{2\a}
\begin{document}
    \texttt{\meaning\cTwiceExpanded}%<- now contains 12\a12\a
\end{document}

Edit: There are of course some special cases like unexpandable macros like \textbf which would fail (could potentially be fixed though, by looking for \protect after the expansion). Also if you used group brackets ({}), a problem would arise. The latter one probably has a solution with checking the next character for \bgroup or something similar.

Edit 2: To fix the grouping problem:

\def\@itergroup{%
    \expandafter\@iterator\@firstofone
}
\def\@iterator{%
    \@ifnextchar\bgroup{\@itergroup}{\@ifnextchar;\@finishIter\@processnext}%
}

Also, as the OP, I'll be honest and say I haven't yet taken the time to understand how most of the other provided answers work, but I will do so in the near future and select an answer.

  • @UlrichDiez Yup. You're right. – Andreas Storvik Strauman Nov 26 '18 at 20:52
  • Your \@processnext is not suitable for arbitrary sets of tokens: In case one of the tokens processed by \@processnextis a macro that processes arguments, \@processnext needs to fetch these arguments for the \xdef, too... – Ulrich Diez Nov 26 '18 at 21:27
  • @AndreasStorvikStrauman take a look at my answer, which checks for arguments and fetches them (unfortunately it is only working for macros with undelimited arguments, so still not working for arbitrary tokens). Perhaps if I completely change the approach from being fully expandable inside of an \edef I could work that out. – Skillmon Nov 27 '18 at 11:39
  • @Skillmon I will. Sorry, just very hectic these days. I really want to understand it before I accept it :) – Andreas Storvik Strauman Dec 3 '18 at 8:44

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.