宏定义内的双重哈希值?

宏定义内的双重哈希值?

假设一个宏,它没有根据 定义\long,并且没有根据 定义\outer,并且没有根据 定义,\protected并且不处理参数,并且其顶层扩展仅产生不是的标记,其中\outer一些是类别代码 6(参数)的显式字符标记:

\def\macro{This is some hashes: ##}

是否有一些\edef————技巧可以重新定义以提供完全相同的一组标记\unexpanded,但类别代码 6(参数)的显式字符标记加倍,就像您定义的那样\expanded\macro

\def\macro{This is some hashes: ####}

假设构成替换文本的标记\macro只能通过扩展才能获得\macro


我自己对此事的处理方式是使用\romannumeral扩展驱动的递归循环来迭代参数,将类别代码 6(参数)的每个显式字符标记增加四倍。

(增加四倍而不是增加一倍,因为扩展\macro意味着将连续哈希的数量减半......)

我不认为我的日常生活\QuadrupleEveryHash很优雅:

  • 作为副作用,该例程确实通过匹配 catcode 1 和 2 的花括号对来替换匹配的 catcode 1 和 2 的显式字符标记对。(我想这在大多数情况下不会是问题,因为通常花括号是类别代码 1/2 的唯一字符......)

  • 该例程需要\detokenize从 e-TeX 扩展中检查给定的标记是否为哈希。(\string#产生单个标记,而产生两个连续的标记……)#12\detokenize{#}#12

  • 每次找到左括号时,\romannumeral就会启动另一个由扩展驱动的例程实例,如果替换文本包含\macro大量括号嵌套,则会对语义嵌套造成损害。

  • \macro该例程仅在的替换文本不包含\outer-tokens(可能就是的情况)时才有效\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

在此处输入图片描述

答案1

每种方法\def...{body}都会将正文内部表示中的井号数量减半。每种方法\toks<num>={body}都会保留井号数量。每种方法\write都会\scantokens将井号数量翻倍。

\def\macro{This is some hashes: ##} % we have one # in internal representation

\scantokens\expandafter{\expandafter\toks\expandafter0\expandafter{\macro}} 
% \scantotkens does \write, we have two internal #, \toks0 keeps two #.

\edef\newmacro{\the\toks0}  % this keeps two # in internal representation

\message{\meaning\macro, \meaning\newmacro} % \meaning doubles the #, so we see:
%             ##              ####
% because there is:
%             #               ##     in internal representation

\bye

答案2

您可以\edef使用\unexpanded

\documentclass{article}
\de\documentclass{article}
\def\safedef#1#2{\edef#1{\unexpanded{#2}}}
\begin{document}
\def\macro{This is some hashes: ##}
\safedef\Macro{This is some hashes: ##}
\typeout{\macro -\Macro}
\end{document}

节目

这是一些哈希:##-这是一些哈希:####

答案3

expl3

\documentclass{article}

\ExplSyntaxOn
\cs_new_protected:Npn \doublehashes #1 #2
 {
  \tl_set_eq:NN \l_tmpa_tl #2
  \regex_replace_all:nnN { \cP. } { \cP\#\cP\# } \l_tmpa_tl
  \tl_set_eq:NN #1 \l_tmpa_tl
 }
\ExplSyntaxOff

\def\macro{This is some hashes: ####}

\doublehashes\Macro\macro

\frenchspacing

\begin{document}

\texttt{\string\macro=\meaning\macro}

\texttt{\string\Macro=\meaning\Macro}

\end{document}

在此处输入图片描述

当然,您知道,它\meaning会为在替换文本中找到的每个井号打印两个井号。因此\macro有两个井号,并且\Macro有四个井号。

答案4

一种etl基于的方法。这将类别代码 1 和 2 标记规范化为括号 ( {}),否则应符合标准。

\documentclass{article}

\usepackage{etl}

\ExplSyntaxOn
\cs_generate_variant:Nn \etl_token_replace_all_deep:nNn { V }
\NewDocumentCommand \doublehashes { m m }
  { \cs_set_nopar:Npe #1 { \etl_token_replace_all_deep:VNn #2 ## { #### } } }
\ExplSyntaxOff

\def\macro{This is some hashes: ####}
\doublehashes\Macro\macro

\begin{document}
\texttt{\string\macro=\meaning\macro}

\texttt{\string\Macro=\meaning\Macro}
\end{document}

在此处输入图片描述

相关内容