josephwright / etoolbox

Tool-box for LaTeX programmers using e-TeX
LaTeX Project Public License v1.3c
41 stars 7 forks source link

Handle hash symbols properly in \eappto #38

Open wise0704 opened 3 years ago

wise0704 commented 3 years ago

Currently, \eappto (and \epreto, \xappto, etc.) requires hash symbols # to be doubled, which is definitely an unintended behaviour since the command to expand cannot have arguments in the first place.

Consider the following examples. Handling of hash symbols in \appto works as it should:

\appto\testA{
    \def\lazydef#1{do something with #1}}
\testA
\meaning\lazydef % macro:#1->do something with #1

However, to achieve the same thing with \eappto, you need to double the hash symbols:

\eappto\testB{
    \unexpanded{\def\lazydef#1}{do something with ##1}}
\testB
\meaning\lazydef % macro:#1->do something with #1

That would only make sense if #1 had a meaning within the second argument of \eappto.

\appto\testC{what would #1 mean here?}
\testC % what would ##1 mean here?
\eappto\testD{what would #1 mean here?} % ! Illegal parameter number in definition of \testD.

Obviously, it cannot have any meaning, since testD cannot have any arguments to be used with \eappto in the first place.

josephwright commented 3 years ago

I'm afraid we can't use \expanded, as it's not part of the e-TeX extensions - etoolbox has never gone beyond those. Perhaps we can find a way to do things more traditionally?

I'm also not really sure that hash symbols are intended to be supported anyway - \(e)appto, etc. are about adding to hooks, so I'd expect to be adding 'simple tokens'.

wise0704 commented 3 years ago

I'm afraid we can't use \expanded, as it's not part of the e-TeX extensions - etoolbox has never gone beyond those. Perhaps we can find a way to do things more traditionally?

Well, that's very unfortunate, as \expandonce{\expanded{#2}} was the only solution I could ever come up with.

So after hours of googling, I just came across this SE answer which sadly and ironically comes up with \edef\macro{\unexpanded\expandafter{\expanded{#1}}} (and it's suggested by you, haha).

The second answer in that same post suggests an alternative "for less recent TeX engines where [...] \expanded is not available", where he introduces his \DoubleEveryHash command. While searching for other solutions, I coincidentally came across his own post where he asks for a better implementation of his \QuadrupleEveryHash command. The answer suggests \scantokens, so I finally came up with a solution without \expanded, that is

\newrobustcmd{\eappto}[2]{%
  \ifundef{#1}
    {\edef#1{\scantokens{#2\noexpand}}}
    {\edef#1{\expandonce#1\scantokens{#2\noexpand}}}}

Now, Ulrich Diez mentions in that answer the side effect of \scantokens being catcode changing in the middle. But I don't think that applies here since the argument #2 is grabbed and scanned immediately with supposedly no action in between. I'm not sure if there are any other possible side effects. Would this implementation be okay?

wise0704 commented 3 years ago

I'm also not really sure that hash symbols are intended to be supported anyway - \(e)appto, etc. are about adding to hooks, so I'd expect to be adding 'simple tokens'.

Sorry, I missed this part. While most of the usage would be for simple tokens, still the current implementation does support hash symbols for \appto. In my opinion, I think it's best to match the behaviours of \appto and \eappto, regardless of the input.

wise0704 commented 3 years ago

Well on a second look, the above method doesn't work when # appears after the expansion of the argument. That was silly.

From the documentations, \expanded expands in the exact same way as \write, so I tried to figure out if there is any clever way to trick \write to expand into the input stream, or see if any other macro mimics the behaviour of \write, but with no success.

The \use:e macro from expl3 does \exp:w \__exp_e:nn { #1 } { } when \expanded is not available, which is \romannumeral with extra hacks (I'm sure you know already). So

\edef#1{\expandonce#1\expandonce{\romannumeral\__exp_e:nn{#2}{}}}

is exactly the behaviour we want, but I'm not sure about your opinion on using expl3 (or reimplementing \__exp_e:nn when \expanded is not defined) just for \eappto is. (Although most engines should have \expanded by now so a fallback macro being slow can be acceptable...?)