Assume a macro which is not defined in terms of \long
and which is not defined in terms of \outer
and which is not defined in terms of \protected
and which does not process arguments and whose toplevel-expansion only yields tokens which are not \outer
whereof some are explicit character tokens of category code 6(parameter):
\def\macro{This is some hashes: ##}
Is there some \edef
-\unexpanded
-\expanded
-trickery for redefining \macro
to deliver exactly the same set of tokens but explicit character tokens of category code 6(parameter) doubled as if you had defined
\def\macro{This is some hashes: ####}
?
Assume that the tokens that form the replacement-text of \macro
are obtainable only by expanding \macro
.
My own approach on the matter is a \romannumeral
-expansion-driven recursive loop for iterating on the argument, quadrupling each explicit character-token of category code 6 (parameter).
(Quadrupling instead of doubling because expanding \macro
implies halving the amount of consecutive hashes...)
I don't take my routine \QuadrupleEveryHash
for very elegant:
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...)
The routine needs
\detokenize
from the e-TeX extensions for checking whether a given token is a hash. (\string#
yields a single#12
-token while\detokenize{#}
yields two consecutive#12
-tokens...)Each time an opening brace is found, another
\romannumeral
-expansion-driven instance of the routine is started which takes its toll on the semantic nest in case the replacement text of\macro
contains a lot of brace-nesting.the routine only works as long as
\macro
's replacement text does not contain\outer
-tokens as could be the case with\def\macro{\foo and hash ##.}\outer\def\foo{Now foo is outer.}
.
\catcode`\@=11
%%=============================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo,
%% \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%% \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%% \UD@CheckWhetherLeadingSpace, \UD@ExtractFirstArg
%%=============================================================================
\long\def\UD@firstoftwo#1#2{#1}%
\long\def\UD@secondoftwo#1#2{#2}%
\long\def\UD@PassFirstToSecond#1#2{#2{#1}}%
\long\def\UD@Exchange#1#2{#2#1}%
\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>
\long\def\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}\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
{\UD@firstoftwo\expandafter{} \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>}%
\long\def\UD@CheckWhetherBrace#1{%
\romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\UD@firstoftwo\expandafter{} \UD@firstoftwo}%
{\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument's first token is an explicit
%% 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>}%
\long\def\UD@CheckWhetherLeadingSpace#1{%
\romannumeral0\UD@CheckWhetherNull{#1}%
{\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
{\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\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}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%% \UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% \UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%.............................................................................
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\long\def\UD@ExtractFirstArg#1{%
\romannumeral0%
\UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\long\def\UD@ExtractFirstArgLoop#1{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{ #1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%=============================================================================
%% \QuadrupleEveryHash{<argument>}%
%%
%% Each explicit catcode-6(parameter)-character-token of the <argument>
%% will be quadrupled.
%%
%% You obtain the result after two expansion-steps, i.e.,
%% in expansion-contexts you get the result after "hitting"
%% \QuadrupleEveryHash 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.
%%-----------------------------------------------------------------------------
\long\def\QuadrupleEveryHash#1{%
\romannumeral0\UD@QuadrupleEveryHashLoop{#1}{}%
}%
\long\def\UD@QuadrupleEveryHashLoop#1#2{%
\UD@CheckWhetherNull{#1}{ #2}{%
\UD@CheckWhetherLeadingSpace{#1}{%
\expandafter\UD@QuadrupleEveryHashLoop
\expandafter{\UD@removespace#1}{#2 }%
}{%
\UD@CheckWhetherBrace{#1}{%
\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\expandafter\expandafter\expandafter{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral0%
\expandafter\UD@QuadrupleEveryHashLoop
\romannumeral0%
\UD@ExtractFirstArgLoop{#1\UD@SelDOm}{}%
}{#2}}%
{\expandafter\UD@QuadrupleEveryHashLoop
\expandafter{\UD@firstoftwo{}#1}}%
}{%
\expandafter\UD@CheckWhetherHash
\romannumeral0\UD@ExtractFirstArgLoop{#1\UD@SelDOm}{#1}{#2}%
}%
}%
}%
}%
\long\def\UD@CheckWhetherHash#1#2#3{%
\expandafter\UD@CheckWhetherLeadingSpace\expandafter{\string#1}{%
% The very edge case of probably having a space of catcode 6:
\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}}{%
% no hash
\UD@secondoftwo
}{% hash
\UD@firstoftwo
}%
}%
}{%
% The case of probably having a non-space of catcode 6:
\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@QuadrupleEveryHashLoop
\expandafter{\UD@firstoftwo{}#2}{#3#1#1#1#1}%
}{% no hash
\expandafter\UD@QuadrupleEveryHashLoop
\expandafter{\UD@firstoftwo{}#2}{#3#1}%
}%
}%
%%\catcode`\@=12
%%=============================================================================
\tt\frenchspacing
1234567890123456789012345678901234567890123456789012345678901234567890
\def\macro{This is some hashes: ##}
\expandafter\def
\expandafter\Macro
\expandafter{%
\romannumeral0\UD@Exchange{ }{%
\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter\expandafter
}%
\expandafter\QuadrupleEveryHash\expandafter{\macro}%
}%
\string\macro: \meaning\macro
\string\Macro: \meaning\Macro
\bigskip
Now the edge case of spaces of category code 6(parameter) being part of \string\macro's\break
replacement-text:
\bigskip
1234567890123456789012345678901234567890123456789012345678901234567890
\begingroup
\catcode`\ =6\relax%
\gdef\macro{This Is Some Hashes: ##}%
\endgroup%
\expandafter\def
\expandafter\Macro
\expandafter{%
\romannumeral0\UD@Exchange{ }{%
\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter\expandafter
}%
\expandafter\QuadrupleEveryHash\expandafter{\macro}%
}%
\string\macro: X\meaning\macro X
\string\Macro: X\meaning\Macro X
\bye