The tokcycle
package processes its input token-by-token according to user-defined directives. It has environment forms and macro forms. In either form, #
tokens are captured as cat-6, a \catSIXtrue
flag is set, but they are momentarily converted to cat-12 to be processed by the Character directive.
Here, I will just tell the character directive to output the (now cat-12 #
) token it gets, regardless of whether \catSIXtrue
or not (this is actually the default).
I present three approaches using this technique. In the first, just apply the \message
command as an argument to the \tokcyclexpress
macro. Then, regurgitate the token list \the\cytoks
.
In the second environment approach, wrap the \message{...}
command in \tokencyclexpress...\endtokencyclexpress
.
Finally, in the third approach, I define a new tokcycle
environment \msg...\endmsg
, where you just insert what would otherwise be the \message
argument.
Because the processed #
tokens in the token cycle are now cat-12, they are not doubled.
\documentclass{article}
\usepackage{tokcycle}[2021/03/10]
\begin{document}
Direct Macro Method:
\tokcyclexpress{\message{#1#\relax#2}}
\the\cytoks
Direct Environment Method:
\tokencyclexpress\message{#1#\relax#2}\endtokencyclexpress
Special environment method:
\xtokcycleenvironment\msg
{\addcytoks{##1}}
{\processtoks{##1}}
{\addcytoks{##1}}
{\addcytoks{##1}}
{}
{\cytoks\expandafter{\expandafter\message\expandafter{\the\cytoks}}}
\msg #1#\relax#2\endmsg
Now go check the log file.
\end{document}
Screen capture of log file:

\string
yields a single character token while "hitting" them with\detokenize
yields two character tokens. You may need to crank out the case of catcode-6-spaces because stringifying/detokenizing them will yield space-tokens of catcode 10 which cannot be grabbed as non-delimited arguments...\futurelet
together with the fact that spaces are not skipped after single character control sequences (see also the implementation of\@ifnextchar
in LaTeX).\StringifyNAct
for stringifying each token of a macro argument. That loop also makes use of a macro\UD@CheckWhetherLeadingSpace
. Based on that one can derive something which does stringify explicit catcode1/2-characters and explicit hashes only. The result can be nested into\message{\unexpanded{}}
...