10

I thought I was finally understanding expansion, but then I wrote something along the following lines, and it has me a bit stumped.

\documentclass{article}
\usepackage{xcolor}
\usepackage{etoolbox}

\makeatletter

\def\ae@test#1{\textcolor{red}{\fbox{#1}}}
\edef\aetest#1{<a bunch of expandable material>\expandonce\ae@test{#1}<more expandable material>}

\makeatother

\begin{document}

\aetest{Hello}

\end{document}

I suspect the issue has something to do with the need to look ahead for the arguments, but I don't even know how to test that theory.

So, I have two questions:

  • What's actually happening that the expansion isn't working as I would like to think it should?
  • How do I write my macro \aetest so that it does what I want? Namely,

    \aetest{<arg>}->\textcolor{red}{\fbox{<arg>}}
    

I suppose there might be some trick I could do using tokens (since tokens within an \edef expand just once), but I'm a bit cloudy on that approach since I haven't played much with tokens.

Incidentally, I wrote \ae@test as I did because I'm not interested in the following sort of scenario:

\def\ae@test#1{\textcolor{red}{#1}}

where with \aetest the following would accomplish what I want

\edef\aetest#1{\expandafter\noexpand\ae@test{#1}}

The issue is that I want to expand a macro which may have several fragile elements within it.

I did try something along the following lines

\edef\aetest#1{\unexpanded\expandafter{\ae@test{#1}}}

thinking that \ae@test would thus be able to grab its argument, but still the same issue.

  • Did you solve the issue already in your pdfkeys solution elsewhere? – 1010011010 May 20 '15 at 23:41
  • @1010011010 Not quite. I mean the answers here work. In particular, I've been contemplating asking another question about wipet's solution. But right now I'm trying to figure out what actually works for me. – A.Ellett May 20 '15 at 23:45
  • I'll get into pgfkeys and see if I can get something to work. I'm quite interested in this myself. :-) – 1010011010 May 21 '15 at 9:50
7

The # gets doubled inside \unexpanded. A workaround with \expandafter:

\documentclass{article}
\usepackage{xcolor}

\makeatletter

\def\ae@test#1{\textcolor{red}{\fbox{#1}}}
\expandafter\def\expandafter\aetest\expandafter#\expandafter1\expandafter{%
  \ae@test{#1}%
}
\makeatother

\begin{document}
  \aetest{Hello}
\end{document}

Then \aetest has the following definition:

macro:#1->\textcolor {red}{\fbox {#1}}

Of course in this case, a simple \let would be easier to get the same meaning:

\let\aetest\ae@test

A quite complex way is to replace #1 by a different token and exchange it afterwards:

\usepackage{etoolbox}
\def\ae@test#1{\textcolor{red}{\fbox{#1}}}
\let\myarg\relax
\edef\aetest#1{\expandonce{\ae@test{\myarg/}}}
\patchcmd\aetest{\myarg/}{#1}{}{%
  \errmessage{Patching \noexpand\aetest failed}%
}

The last step needs to be repeated to the number of occurrences of #1 inside the definition text of \ae@test. If the number is unknown, then a loop can be used, which aborts at the first error:

\let\myarg\relax
\edef\aetest#1{\expandonce{\ae@test{\myarg/}}}
\count@=\@ne
\@whilenum\count@>\z@\do{%
  \edef\process@me{%
    \noexpand\patchcmd\noexpand\aetest{\noexpand\myarg/}{\string#1}%
  }\process@me{}{\count@=\z@}%
}

Scratch counter \count@ signalizes the success of the patching. If it is set to one, then the replacement of \myarg to #1 is tried. If the patching failed, because \myarg could not be found anymore, then \count@ is set to zero and the loop is aborted. But it is not detected, if there are other reasons for the patch failure.

The circumvention of \process@me is a trick to prevent doubling of # in the third argument of \patchcmd, since it is used as argument of another command (\@whilenum).

Remark:

  • \myarg/ is used for #1, otherwise \patchcmd is not happy, if a space if following #1.

Improvement (by egreg)

Package regexpatch provides \xpatchcmd with a star form, which replaces all matches without the need to program a loop manually:

\documentclass{article}
\usepackage{xcolor}
\usepackage{etoolbox}
\usepackage{regexpatch}

\makeatletter
\def\ae@test#1{%
  \typeout{\detokenize{** #1 **}}%
  \textcolor{red}{\fbox{#1}}%
}
\let\myarg\relax
\edef\aetest#1{\expandonce{\ae@test{\myarg/}}}
\xpatchcmd*{\aetest}{\myarg/}{#1}{}{%
  \errmessage{Patching \noexpand\aetest failed}%
}
\makeatother

\begin{document}
  \aetest{Hello}
\end{document}  

\myarg/ instead of \myarg is also needed for \xpatchcmd, because a space is following #1 in \ae@test.

  • Ah! The problem of MWE. \let\aetest\ae@test won't work because they're not meant to be equivalent (even though that's the way I wrote the above example). Further, there may be other content within the definition of \aetest that I do want expanded. So while your answer works for this particular case, it doesn't work for a general case. – A.Ellett May 12 '15 at 4:06
  • I've updated the question to better reflect the issue. – A.Ellett May 12 '15 at 4:07
  • Well you've definitely explained the why. – A.Ellett May 12 '15 at 4:20
  • @A.Ellett I have added a more complex version, if the \expandafter trick would become to lengthy. – Heiko Oberdiek May 12 '15 at 4:23
  • Awesome! And an example of using \patch that makes a lot of sense to me. – A.Ellett May 12 '15 at 4:24
5

You can use \unexpanded from eTeX using the following code:

\def\aeAtest#1{\textcolor{red}{\fbox{#1}}}

\def\eonce#1{\unexpanded\expandafter{#1{##1}}}
\edef\tmp{\def\noexpand\OUT##1{expanded before \eonce{\aeAtest} after.}}\tmp

\message{\meaning\OUT}
% \OUT#1 -> expanded before \textcolor {red}{\fbox {#1}} after.

Edit Another usage of the same idea defines the \oedef macro:

\def\eonce#1#2{\unexpanded\expandafter{#1{#2}}}
\def\eodef#1#2{\edef\tmp{\def\noexpand#1####1{#2}}\tmp}

\def\aeAtest#1{\textcolor{red}{\fbox{#1},#1}}
\eodef\aetest{expanded ##1+y before \eonce\aeAtest{#1+x} after.}

\message{\meaning\aetest}
% \aetest#1 -> expanded #1+y before \textcolor {red#1}{\fbox {#1+x},#1+x} after.

Unfortunately, the \eodef macro has a curious syntax: the parameter can't be presented in the parameter mask and this parameter must be typed as ##1 in "normal" positions but as #1 in the parameter of \eonce.

  • Very nice solution. It would probably get more attention if you kept your macro names more in line with the choices I made: your \aeAtest=\ae@test, your \OUT=\aetest. – A.Ellett May 12 '15 at 13:22
  • @A.Ellett The name \ae@test is somewhat impractical for me because I must to do catcode dancing when such name occurs. – wipet May 12 '15 at 14:13
  • I'm intrigued. Why bothered by something that seems as prevalent as \makeatletter? Again, intrigued and just curious. – A.Ellett May 12 '15 at 14:23
  • @A.Ellett First of all, I must to define \makeatletter because plain TeX doesn't define it (and I don't use LaTeX). – wipet May 12 '15 at 14:43
  • Aaaah. I get it now. I hadn't noticed that your answers are always in TeX and not LaTeX. Sloppy me for not noticing. :-) – A.Ellett May 12 '15 at 15:09
3

To work around the expansion issues point to by @HeikoOberdiek, I've come up with the following work around

\edef\aetest#1{\noexpand\def\noexpand\ae@tmp{#1}%%
    \unexpanded\expandafter{\ae@test{\ae@tmp}}}

though I'll have to test it out a bit before I'm sure that it really does accomplish what I want.

  • 1
    The macros inside \ae@test will see \ae@tmp instead of #1. Most macros won't care much about this. But some require, that #1 is not hidden inside a macro, examples: \def\ae@test#1{\selectlanguage{#1}} with package babel. Then babel will complain about an unknown language ae@tmp. Another example, package url: \def\ae@test#1{\url{#1}} will print \ae@tmp as URL instead of the URL given in #1. – Heiko Oberdiek May 12 '15 at 4:37
2

A solution with expl3:

\documentclass{article}
\usepackage{xcolor}
\usepackage{expl3}

\ExplSyntaxOn
\cs_new_protected:Npn \ae_test:n #1
 {
  \textcolor{red}{\fbox{#1}}~
  \textcolor{green}{\fbox{#1}}~
  \textcolor{blue}{\fbox{#1}}
 }

\group_begin:
\tl_set:No \l_tmpa_tl { \ae_test:n { #1 } }
\use:x
 {
  \group_end:
  \cs_new:Npn \exp_not:N \aetest ##1
   {
    something~expandable~
    \exp_not:V \l_tmpa_tl
   }
 }
\ExplSyntaxOff

\begin{document}

\raggedright

\texttt{\meaning\aetest}

\bigskip

\aetest{Hello}

\end{document}

enter image description here

Explanation: in \l_tmpa_tl, we get the replacement text of \ae_test:n with ##1 in place of #1. So we just need to expand once this token list inside another (temporary) definition. The \use:x trick provides this without the need of defining another temporary macro. I used \group_begin: and \group_end: not to leave \l_tmpa_tl modified, but it's not necessary.

  • How is it that you're able to set \l_tmpa_tl and not get an error about an illegal parameter (for the #1)? – A.Ellett May 12 '15 at 15:13
  • @A.Ellett Because \tl_set:Nn \l_tmpa_tl {...} is not just \def\l_tmpa_tl{...}, but \cs_set_nopar:Npx \l_tmpa_tl { \exp_not:o { ... } }, so the # are doubled before the token list is set. Similarly, \tl_set:Nn does \cs_set_nopar:Npx #1 { \exp_not:n { #2 } } – egreg May 12 '15 at 15:24
  • I guess my homework for my summer break will be to thoroughly reread the TeX Book. I feel like I keep getting tripped up on the same few concepts. :*( – A.Ellett May 12 '15 at 15:43
0

This is too long for a comment I suspect, so I'll just put it here. Something that helped me a lot but I don't see it mentioned anywhere is the following. It might be really obvious, but it might also not.

Take the situation as follows. You have your two macros, one which you want to expand, the other which must hold the expanded definition:

\def\expandthis#1{\textcolor{red}{\fbox{#1}}}
\def\macrowithexpandeddefinition#1{\expandthis{#1}}

In order to get what you want, you have to work your way backwards (it will be apparent soon what I mean with this). This requires some knowledge about the idea of tokens, which I'm going to assume you're familiar with. The trickery has to happen in the line with \def\macrowithexpandeddefinition#1. In order to expand \expandthis once, we just have to reach the escape character \ of that macro without expanding anything before that.

Step 1: jump over the last token before \expandthis, which is a brace {:

\def\macrowithexpandeddefinition#1\expandafter{\expandthis{#1}}
                                   ^^^^^^^^^^

Step 2: We must now reach the first \expandafter. There last token before it is 1:

\def\macrowithexpandeddefinition#\expandafter1\expandafter{\expandthis{#1}}
                                  ^^^^^^^^^^

Step 3 and follows: The last token before that is the # (hash) token, continue:

\def\macrowithexpandeddefinition\expandafter#\expandafter1\expandafter{\expandthis{#1}}
                                 ^^^^^^^^^^

And repeat with the last two tokens \macrowithexpandeddefinition and \def respectively:

\expandafter\def\expandafter\macrowithexpandeddefinition\expandafter#\expandafter1\expandafter{\expandthis{#1}}

You can repeat these steps for any macro you like for fine grained expansion control, but it won't make the code very readable. Also note that what I end up with is exactly the same as Heiko's "workaround".

  • I appreciate the comment/answer. But there was never any difficulty understanding Heiko's answer. The problem is that my MWE was a bit misleading since this approach really doesn't work in the scenario I'm actually working on. – A.Ellett May 12 '15 at 13:35
  • @A.Ellett I'm interested. Do you have a self-contained file that I can see where you're trying to get something done but this approach doesn't work? – 1010011010 May 12 '15 at 13:49
  • The matter most recently came up when I was writing an answer to another question. In particular, I wanted to be able to create a user interface to change the comment formatting macro \ae@question@row@comment@formatting so that it could depend on the row. For this I would have to expand it each time I use it while setting up the table. If I waited until the moment of executing the table, then only the most recent version would come into play. More details later. For now I've got to run. – A.Ellett May 12 '15 at 13:59
  • @A.Ellett Do I understand correctly that you want to have an extra key which controls the row colour? – 1010011010 May 12 '15 at 15:23
  • No quite. I was going to create a key in which the user could define the formatting for the comment. I was going to set it up to require the key to take the body of a macro definition requiring at most one argument. Then the formatting could be anything ranging from setting color, boldness of font, boxing the content, or virtually anything achievable using a macro. Once I understood that the OP was very new to LaTeX, I chose not to confuse the issue. At that point I started experimenting on my own with handling such macros and came across the current situation about \unexpanded and #1 – A.Ellett May 12 '15 at 15:39

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.