3

Assume a token sequence:

Token 1: Explicit hash character of category code 6.
Token 2: Explicit 1 of category code 12.
Token 3: Explicit hash character of category code 6.
Token 4: Control word token \relax.
Token 5: Explicit hash character of category code 6.
Token 6: Explicit 2 of category code 12.

Something like:
#6,112,#6,\relaxcontrol word token,#6,212

How can you get a \message on the screen, based on this token-sequence, where hashes are not doubled, which on the screeen looks like this:
#1#\relax #2?

Is there a method in traditional TeX for transforming hashes of catcode 6 which does not double them?

  • Welcome to TeX.SX! Hashes, yes, by changing their catcode. Parameter tokens (catcode 6) no, because TeX doubles them (although they are still a single parameter token). – Phelype Oleinik Dec 14 '19 at 3:58
  • Only hashes or explicit catcode-6-character-tokens in general? If the latter, I don't see a reliable method for detecting them with traditional methods, but if you have eTeX-extensions, "hitting" them with \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... – Ulrich Diez Dec 14 '19 at 14:16
  • @UlrichDiez Spaces of catcode 10 can be grabbed with \futurelet together with the fact that spaces are not skipped after single character control sequences (see also the implementation of \@ifnextchar in LaTeX). – Henri Menke Dec 15 '19 at 6:31
  • @HenriMenke I know those things. You can check for spaces via space-delimited arguments. E.g., my second answer to Stringify input - \string on token list contains a loop \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{}}... – Ulrich Diez Dec 15 '19 at 10:06
3

You can search for # and replace it with a catcode 12 hash mark, then display it.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\hmessage}{m}
 {
  \tl_set:Nn \l__jewdokija_hmessage_tl { #1 }
  \regex_replace_all:nnN { \cP\# } { \cO\# } \l__jewdokija_hmessage_tl
  \iow_term:V \l__jewdokija_hmessage_tl
 }
\tl_new:N \l__jewdokija_hmessage_tl
\cs_generate_variant:Nn \iow_term:n { V }

\ExplSyntaxOff

\hmessage{#1#\relax#2}

The terminal will display

> pdflatex jewdokija.tex
This is pdfTeX, Version 3.14159265-2.6-1.40.20 (TeX Live 2019) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
(./jewdokija.tex
LaTeX2e <2019-10-01> patch level 3
(/usr/local/texlive/2019/texmf-dist/tex/latex/base/article.cls
Document Class: article 2019/10/25 v1.4k Standard LaTeX document class
(/usr/local/texlive/2019/texmf-dist/tex/latex/base/size10.clo))
(/usr/local/texlive/2019/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
(/usr/local/texlive/2019/texmf-dist/tex/latex/l3kernel/expl3.sty
(/usr/local/texlive/2019/texmf-dist/tex/latex/l3kernel/expl3-code.tex
(/usr/local/texlive/2019/texmf-dist/tex/latex/l3kernel/l3deprecation.def))
(/usr/local/texlive/2019/texmf-dist/tex/latex/l3backend/l3backend-pdfmode.def))
)
#1#\relax #2
| improve this answer | |
2

You can detokenize your string without hash doubling by round-tripping through Lua. As a bonus this is fully expandable if your string is fully expandable.

\documentclass{article}

\begin{document}

\message{\directlua{tex.sprint(-2, "\luaescapestring{#1#\relax#2}")}}

\end{document}
$ lualatex test.tex
This is LuaTeX, Version 1.11.2 (TeX Live 2020/dev) 
 restricted system commands enabled.
(./test.tex
LaTeX2e <2019-10-01> patch level 3

luaotfload | main : initialization completed in 0.140 seconds
(/opt/texlive/2019/texmf-dist/tex/latex/base/article.cls
Document Class: article 2019/10/25 v1.4k Standard LaTeX document class
(/opt/texlive/2019/texmf-dist/tex/latex/base/size10.clo)) (./test.aux)
#1#\relax #2 (./test.aux))
 384 words of node memory still in use:
   2 hlist, 1 vlist, 1 rule, 2 glue, 3 kern, 1 glyph, 5 attribute, 44 glue_spec
, 5 attribute_list, 2 write nodes
   avail lists: 2:7,3:1,4:1,5:4,7:1,9:1

warning  (pdf backend): no pages of output.
Transcript written on test.log.
| improve this answer | |
0

In case besides the hash-doubling you also don't like (La)TeX's automatic insertion of spaces behind control word tokens that get written unexpanded, the routine from my first answer can be altered to have every token "stringified, not just explicit character tokens of category code 1 (begin group) or 2 (end group) or 6 (parameter).

%% Copyright (C) 2019 by Ulrich Diez (eu_angelion@web.de)
%%
%% This work may be distributed and/or modified under the
%% conditions of the LaTeX Project Public Licence (LPPL), either
%% version 1.3 of this license or (at your option) any later
%% version. (The latest version of this license is in:
%% http://www.latex-project.org/lppl.txt
%% and version 1.3 or later is part of all distributions of LaTeX
%% version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included
%% documentation nor for any other part/component of this work.
%% If something breaks, you usually may keep the pieces.

%%\errorcontextlines=10000

\documentclass{article}

\makeatletter
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo,
%%    \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%%    \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%%    \UD@CheckWhetherLeadingSpace, \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@removespace{}\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>
\newcommand\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>}%
\newcommand\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 starts with a 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>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@@CheckWhetherLeadingSpace.#1 }{}}%
}%
\@ifdefinable\UD@@CheckWhetherLeadingSpace{%
  \long\def\UD@@CheckWhetherLeadingSpace#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}
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  { #1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------    
%% In case an argument's first token is an opening brace, stringify that and
%% add another opening brace before that and remove everything behind the 
%% matching closing brace:
%% \UD@StringifyOpeningBrace{{Foo}bar} yields {{Foo}  whereby the second
%% opening brace is stringified:
%%.............................................................................
\newcommand\UD@StringifyOpeningBrace[1]{%
  \romannumeral0%
  \expandafter\UD@ExtractFirstArgLoop\expandafter{%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\expandafter
    \expandafter            {%
    \expandafter\UD@firstoftwo
    \expandafter{%
    \expandafter}%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\string
    \expandafter}%
    \string#1%
  \UD@SelDOm}%
}%
%%-----------------------------------------------------------------------------    
%% In case an argument's first token is an opening brace, remove everything till 
%% finding the corresponding closing brace. Then stringify that closing brace:
%% \UD@StringifyClosingBrace{{Foo}bar} yields: {}bar} whereby the first closing
%% brace is stringified:
%%.............................................................................
\newcommand\UD@StringifyClosingBrace[1]{%
   \romannumeral0\expandafter\expandafter\expandafter
                 \UD@StringifyClosingBraceloop
                 \UD@ExtractFirstArg{#1}{#1}%
}%
\newcommand\UD@CheckWhetherStringifiedOpenBraceIsSpace[1]{%
%% This can happen when character 32 (space) has catcode 1...
  \expandafter\UD@CheckWhetherLeadingSpace\expandafter{%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\UD@secondoftwo
    \expandafter{%
    \expandafter}%
    \expandafter{%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\UD@firstoftwo
    \expandafter{%
    \expandafter}%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\string
    \expandafter}%
    \string#1%
  }%
}%
\newcommand\UD@TerminateStringifyClosingBraceloop[2]{%
  \UD@Exchange{ }{\expandafter\expandafter\expandafter}%
  \expandafter\expandafter
  \expandafter{%
  \expandafter\string      
  \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
  \expandafter#1%
  \string#2%
  }%
}%
\newcommand\UD@StringifyClosingBraceloopRemoveElement[4]{%
  \expandafter\UD@PassFirstToSecond\expandafter{\expandafter
  {\romannumeral0\expandafter\UD@secondoftwo\string}{}%
    \UD@CheckWhetherStringifiedOpenBraceIsSpace{#4}{%
      \UD@Exchange{\UD@removespace}%
    }{%
      \UD@Exchange{\UD@firstoftwo\expandafter{\expandafter}}%
    }{%
      \UD@Exchange{ }{\expandafter\expandafter\expandafter}%
      \expandafter#1%
      \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
      \expandafter
    }%
    \string#4%
  }{\expandafter\UD@StringifyClosingBraceloop\expandafter{#2#3}}%
}%
\newcommand\UD@StringifyClosingBraceloop[2]{%
  \UD@CheckWhetherNull{#1}{%
    \UD@CheckWhetherStringifiedOpenBraceIsSpace{#2}{%
      \UD@TerminateStringifyClosingBraceloop{\UD@removespace}%
    }{%
      \UD@TerminateStringifyClosingBraceloop{\UD@firstoftwo\expandafter{\expandafter}}%
    }%
    {#2}%
  }{%
    \UD@CheckWhetherLeadingSpace{#1}{%
      \UD@StringifyClosingBraceloopRemoveElement
      {\UD@removespace}{\UD@removespace}%
    }{%
      \UD@StringifyClosingBraceloopRemoveElement
      {\UD@firstoftwo\expandafter{\expandafter}}{\UD@firstoftwo{}}%
    }%
    {#1}{#2}%
  }%
}%
%%-----------------------------------------------------------------------------
%% Create message-text from unexpanded brace balanced  set of tokens where
%% hashes are _not_ doubled:
%%
%% \MakeUnexpandedMessageWithSingleHashes{<token 1><token 2>...<token n>}
%%
%% yields:  <if token 1 is explicit character token of catcode 6, then 
%%           stringification+detokenization of token 1, else detokenization 
%%           of token 1>%
%%          <if token 2 is explicit character token of catcode 6, then 
%%           stringification+detokenization of token 2, else detokenization 
%%           of token 2>%
%%          ...
%%          <if token n is explicit character token of catcode 6, then 
%%           stringification+detokenization of token n, else detokenization 
%%           of token n>%
%%
%% whereby "stringification of token" means the result of applying \string
%% to the token in question.
%% Due to \detokenize/\romannumeral-expansion the result is delivered after two
%% \expandafter-chains.
%%.............................................................................
\newcommand\MakeUnexpandedMessageWithSingleHashes[1]{%
  \romannumeral0\StringifyLoop{}{#1}%
}%
%%-----------------------------------------------------------------------------
%% Stringify each token:
%%
%% \romannumeral0\StringifyLoop{}{<token 1><token 2>...<token n>}
%%
%% yields:  <stringification of token 1>%
%%          <stringification of token 2>%
%%          ...
%%          <stringification of token n>%
%%
%% whereby "stringification of token" means the result of applying \string
%% to the token in question.
%% Due to \romannumeral-expansion the result is delivered after two
%% \expandafter-chains.
%%
%% Syntax:  
%%
%% \StringifyLoop{<token 1 or stringification of token 1>%
%%                 ...%
%%                 <token k-1 or stringification of token k-1>}%
%%               {<token k>...<token n>}
%%.............................................................................
\newcommand\StringifyLoop[2]{%
  \UD@CheckWhetherNull{#2}{ #1}{%
    \UD@CheckWhetherBrace{#2}{%
      \expandafter\expandafter\expandafter\UD@Exchange
      \expandafter\expandafter\expandafter{%
        \UD@StringifyClosingBrace{#2}%
      }{%
        \expandafter\StringifyLoop\expandafter{%
          \romannumeral0%
          \expandafter\expandafter\expandafter\UD@Exchange
          \expandafter\expandafter\expandafter{\UD@StringifyOpeningBrace{#2}}{\StringifyLoop{#1}}%
        }%
      }%
    }{%
      \UD@CheckWhetherLeadingSpace{#2}{%
        \expandafter\UD@PassFirstToSecond\expandafter{\UD@removespace#2}{%
          \StringifyLoop{#1 }%
        }%
      }{%
        \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#2}{%
          \expandafter\StringifyLoop\expandafter{%
            \romannumeral0%
            \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\UD@Exchange
            \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter{%
              \expandafter\expandafter\expandafter\string
              \expandafter\UD@Exchange
              \romannumeral0\UD@ExtractFirstArgLoop{#2\UD@SelDOm}{}%
            }%
            { #1}%
          }%
        }%
      }%
    }%
  }%
}%
\makeatother

\begin{document}

\message{^^JHere come the messages^^J======================^^J^^J}%

\message{\MakeUnexpandedMessageWithSingleHashes{#1#\relax#2}}%
\message{^^J^^J}

{%
\catcode`\R=6 %
\message{\MakeUnexpandedMessageWithSingleHashes{R1#\relaxR2 s p a c e s {br{a{c}}es} \else \endcsname}}%
\message{^^J^^J}
}%

\end{document}

enter image description here

| improve this answer | |
0

I can offer a routine \MakeUnexpandedMessageWithSingleHashes with an underlying loop \StringifyBracesAndHashesLoop which processes an argument and stringifies each explicit character token of category code 1 (begin group) or 2 (end group) or 6 (parameter) and then applies eTeX's \detokenize to the result thereof.

%% Copyright (C) 2019 by Ulrich Diez (eu_angelion@web.de)
%%
%% This work may be distributed and/or modified under the
%% conditions of the LaTeX Project Public Licence (LPPL), either
%% version 1.3 of this license or (at your option) any later
%% version. (The latest version of this license is in:
%% http://www.latex-project.org/lppl.txt
%% and version 1.3 or later is part of all distributions of LaTeX
%% version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included
%% documentation nor for any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%
%% For this example to compile properly, LaTeX 2e with eTeX-extensions
%% is required: \UD@CheckWhetherHash and \StringifyBracesAndHashes
%%              require \detokenize.

%%\errorcontextlines=10000

\documentclass{article}

\makeatletter
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo,
%%    \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%%    \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%%    \UD@CheckWhetherLeadingSpace, \UD@CheckWhetherHash (requires eTeX's
%%    \detokenize), \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@removespace{}\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>
\newcommand\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>}%
\newcommand\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 starts with a 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>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@@CheckWhetherLeadingSpace.#1 }{}}%
}%
\@ifdefinable\UD@@CheckWhetherLeadingSpace{%
  \long\def\UD@@CheckWhetherLeadingSpace#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}%
  }%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument (which mut be a single token!!!!) is an
%% explicit character token of category code 6(parameter)
%%.............................................................................
%% \UD@CheckWhetherHash{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case <argument which is to
%%                       be checked> is an explicit character token of category
%%                       code 6>}%
%%                     {<Tokens to be delivered in case <argument which is to
%%                       be checked> is not an explicit character token of
%%                       category code 6>}%
\newcommand\UD@CheckWhetherHash[1]{%
  \romannumeral0%
  \expandafter\UD@CheckWhetherLeadingSpace\expandafter{\string#1}{%
    \UD@@CheckWhetherHash{\UD@removespace}{#1}%
  }{%
    \UD@@CheckWhetherHash{\UD@firstoftwo\expandafter{\expandafter}}{#1}%
  }%
  {%
    \UD@firstoftwo\expandafter{} \UD@secondoftwo
  }%
}%
\newcommand\UD@@CheckWhetherHash[2]{%
  \expandafter\expandafter\expandafter\UD@CheckWhetherNull
  \expandafter\expandafter\expandafter{%
  \expandafter#1\string#2}{%
    \expandafter\expandafter\expandafter\UD@CheckWhetherNull
    \expandafter\expandafter\expandafter{%
    \expandafter#1\detokenize{#2}}{%
      % Single token
      \UD@firstoftwo\expandafter{} \UD@secondoftwo
    }{% Explicit catcode-6-character-token
      \UD@firstoftwo\expandafter{} \UD@firstoftwo
    }%
  }%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%%   \UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  {AB}
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  { #1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------    
%% In case an argument's first token is an opening brace, stringify that and
%% add another opening brace before that and remove everything behind the 
%% matching closing brace:
%% \UD@StringifyOpeningBrace{{Foo}bar} yields {{Foo}  whereby the second
%% opening brace is stringified:
%%.............................................................................
\newcommand\UD@StringifyOpeningBrace[1]{%
  \romannumeral0%
  \expandafter\UD@ExtractFirstArgLoop\expandafter{%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\expandafter
    \expandafter            {%
    \expandafter\UD@firstoftwo
    \expandafter{%
    \expandafter}%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\string
    \expandafter}%
    \string#1%
  \UD@SelDOm}%
}%
%%-----------------------------------------------------------------------------    
%% In case an argument's first token is an opening brace, remove everything till 
%% finding the corresponding closing brace. Then stringify that closing brace:
%% \UD@StringifyClosingBrace{{Foo}bar} yields: {}bar} whereby the first closing
%% brace is stringified:
%%.............................................................................
\newcommand\UD@StringifyClosingBrace[1]{%
   \romannumeral0\expandafter\expandafter\expandafter
                 \UD@StringifyClosingBraceloop
                 \UD@ExtractFirstArg{#1}{#1}%
}%
\newcommand\UD@CheckWhetherStringifiedOpenBraceIsSpace[1]{%
%% This can happen when character 32 (space) has catcode 1...
  \expandafter\UD@CheckWhetherLeadingSpace\expandafter{%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\UD@secondoftwo
    \expandafter{%
    \expandafter}%
    \expandafter{%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\UD@firstoftwo
    \expandafter{%
    \expandafter}%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\string
    \expandafter}%
    \string#1%
  }%
}%
\newcommand\UD@TerminateStringifyClosingBraceloop[2]{%
  \UD@Exchange{ }{\expandafter\expandafter\expandafter}%
  \expandafter\expandafter
  \expandafter{%
  \expandafter\string      
  \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
  \expandafter#1%
  \string#2%
  }%
}%
\newcommand\UD@StringifyClosingBraceloopRemoveElement[4]{%
  \expandafter\UD@PassFirstToSecond\expandafter{\expandafter
  {\romannumeral0\expandafter\UD@secondoftwo\string}{}%
    \UD@CheckWhetherStringifiedOpenBraceIsSpace{#4}{%
      \UD@Exchange{\UD@removespace}%
    }{%
      \UD@Exchange{\UD@firstoftwo\expandafter{\expandafter}}%
    }{%
      \UD@Exchange{ }{\expandafter\expandafter\expandafter}%
      \expandafter#1%
      \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
      \expandafter
    }%
    \string#4%
  }{\expandafter\UD@StringifyClosingBraceloop\expandafter{#2#3}}%
}%
\newcommand\UD@StringifyClosingBraceloop[2]{%
  \UD@CheckWhetherNull{#1}{%
    \UD@CheckWhetherStringifiedOpenBraceIsSpace{#2}{%
      \UD@TerminateStringifyClosingBraceloop{\UD@removespace}%
    }{%
      \UD@TerminateStringifyClosingBraceloop{\UD@firstoftwo\expandafter{\expandafter}}%
    }%
    {#2}%
  }{%
    \UD@CheckWhetherLeadingSpace{#1}{%
      \UD@StringifyClosingBraceloopRemoveElement
      {\UD@removespace}{\UD@removespace}%
    }{%
      \UD@StringifyClosingBraceloopRemoveElement
      {\UD@firstoftwo\expandafter{\expandafter}}{\UD@firstoftwo{}}%
    }%
    {#1}{#2}%
  }%
}%
%%-----------------------------------------------------------------------------
%% Create message-text from unexpanded brace balanced  set of tokens where
%% hashes are _not_ doubled:
%%
%% \MakeUnexpandedMessageWithSingleHashes{<token 1><token 2>...<token n>}
%%
%% yields:  <if token 1 is explicit character token of catcode 6, then 
%%           stringification+detokenization of token 1, else detokenization 
%%           of token 1>%
%%          <if token 2 is explicit character token of catcode 6, then 
%%           stringification+detokenization of token 2, else detokenization 
%%           of token 2>%
%%          ...
%%          <if token n is explicit character token of catcode 6, then 
%%           stringification+detokenization of token n, else detokenization 
%%           of token n>%
%%
%% whereby "stringification of token" means the result of applying \string
%% to the token in question.
%% Due to \detokenize/\romannumeral-expansion the result is delivered after two
%% \expandafter-chains.
%%.............................................................................
\newcommand\MakeUnexpandedMessageWithSingleHashes[1]{%
  \detokenize\expandafter{\romannumeral0\StringifyBracesAndHashesLoop{}{#1}}%
}%
%%-----------------------------------------------------------------------------
%% Stringify explicit character tokens of catcode 1 or 2 or 6:
%%
%% \romannumeral0\StringifyBracesAndHashesLoop{}{<token 1><token 2>...<token n>}
%%
%% yields:  <if token is explicit character token of catcode 1/2/6, then 
%%           stringification of token 1, else token 1>%
%%          <if token is explicit character token of catcode 1/2/6, then 
%%           stringification of token 2, else token 2>%
%%          ...
%%          <if token is explicit character token of catcode 1/2/6, then 
%%           stringification of token n, else token n>%
%%
%% whereby "stringification of token" means the result of applying \string
%% to the token in question.
%% Due to \romannumeral-expansion the result is delivered after two
%% \expandafter-chains.
%%
%% Syntax:  
%%
%% \StringifyBracesAndHashesLoop{<token 1 or stringification of token 1>%
%%                               ...%
%%                               <token k-1 or stringification of token k-1>}%
%%                              {<token k>...<token n>}
%%.............................................................................
\newcommand\StringifyBracesAndHashesLoop[2]{%
  \UD@CheckWhetherNull{#2}{ #1}{%
    \UD@CheckWhetherBrace{#2}{%
      \expandafter\expandafter\expandafter\UD@Exchange
      \expandafter\expandafter\expandafter{%
        \UD@StringifyClosingBrace{#2}%
      }{%
        \expandafter\StringifyBracesAndHashesLoop\expandafter{%
          \romannumeral0%
          \expandafter\expandafter\expandafter\UD@Exchange
          \expandafter\expandafter\expandafter{\UD@StringifyOpeningBrace{#2}}{\StringifyBracesAndHashesLoop{#1}}%
        }%
      }%
    }{%
      \UD@CheckWhetherLeadingSpace{#2}{%
        \expandafter\UD@PassFirstToSecond\expandafter{\UD@removespace#2}{%
          \StringifyBracesAndHashesLoop{#1 }%
        }%
      }{%
        \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#2}{%
          \expandafter\StringifyBracesAndHashesLoop\expandafter{%
            \romannumeral0%
            \expandafter\UD@CheckWhetherHash\romannumeral0\UD@ExtractFirstArgLoop{#2\UD@SelDOm}{%
              \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\UD@Exchange
              \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter{%
                \expandafter\expandafter\expandafter\string
                \expandafter\UD@Exchange
                \romannumeral0\UD@ExtractFirstArgLoop{#2\UD@SelDOm}{}%
              }%
            }{%
              \expandafter\expandafter\expandafter\UD@Exchange
              \expandafter\expandafter\expandafter{%
                \expandafter\UD@Exchange
                \romannumeral0\UD@ExtractFirstArgLoop{#2\UD@SelDOm}{}%
              }%
            }{ #1}%
          }%
        }%
      }%
    }%
  }%
}%
\makeatother

\begin{document}

\message{^^JHere come the messages^^J======================^^J^^J}%

\message{\MakeUnexpandedMessageWithSingleHashes{#1#\relax#2}}%
\message{^^J^^J}

{%
\catcode`\R=6 %
\message{\MakeUnexpandedMessageWithSingleHashes{R1#\relaxR2 s p a c e s {br{a{c}}es} }}%
\message{^^J^^J}
}%

\end{document}

enter image description here

| improve this answer | |

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.