宏字符“#”在变成字母时会加倍吗?

宏字符“#”在变成字母时会加倍吗?

如果我创建了一个接受一个参数的命令,并且该参数应该被允许将未转义的默认宏 char:#作为参数,然后将其视为字母:

\documentclass{article}
\makeatletter
\def\allowhash#1{
  {\toks@{#1}\catcode`\#11\relax\scantokens\expandafter{\the\toks@}}%
}
\makeatother
\begin{document}
\allowhash{One hash: #, not two}
\end{document}

上面的代码可以正常工作,只是会打印以下内容: One hash: ##, not two。这是预期的行为吗?如何修复此问题?

答案1

让我们看看你的代码:

\documentclass{article}
\makeatletter
\def\allowhash#1{
  {\toks@{#1}\catcode`\#11\relax\scantokens\expandafter{\the\toks@}}%
}
\makeatother
\begin{document}
\allowhash{One hash: #, not two}
\end{document}

LaTeX 确实获取了 的参数\allowhash,从而根据正常类别代码机制对构成该参数的标记进行标记化。因此,LaTeX 确实获取了类别代码为 6(参数)的显式哈希字符标记作为 的\allowhash参数的组成部分。

在扩展的时候\allowhash,这个 token 就成为 内容的一部分\toks@

因此\toks@包含类别代码 6(参数)的显式哈希字符标记。

由于\expandafter...\the-trickery,此类别代码为 6(参数)的显式哈希字符标记最终成为了的组成⟨general text⟩部分\scantokens

\scantokens模拟将标记从其自身写入⟨general text⟩文件并从文件中读回它们,从而根据当前类别代码制度对事物进行标记。

写入文件或屏幕时,类别代码 6(参数)的显式字符标记会加倍。

\scantokens因此,哈希值因未扩展的写入部分而加倍。

我只能即兴地提供一个例程\ReplaceEveryHash,它接受一个参数并用其字符串化替换参数的每个显式 catcode-6-character-token - 这种机制不仅对显式 catcode-6-hashes 起作用,而且对所有显式 catcode-6-character-token 起作用。

\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}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\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}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@firstoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\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}%
  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\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}
%%.............................................................................
\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}}%
}%
%%=============================================================================
%% \ReplaceEveryHash{<argument>}%
%%
%%   Each explicit catcode-6(parameter)-character-token of the <argument> 
%%   will be replaced by its stringification.
%%
%%   You obtain the result after two expansion-steps, i.e., 
%%   in expansion-contexts you get the result after "hitting" 
%%   \ReplaceEveryHash 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.
%%-----------------------------------------------------------------------------
\newcommand\ReplaceEveryHash[1]{%
   \romannumeral0\UD@ReplaceEveryHashLoop{#1}{}%
}%
\newcommand\UD@ReplaceEveryHashLoop[2]{%
  \UD@CheckWhetherNull{#1}{ #2}{%
    \UD@CheckWhetherLeadingSpace{#1}{%
       \expandafter\UD@ReplaceEveryHashLoop
       \expandafter{\UD@removespace#1}{#2 }%
    }{%
      \UD@CheckWhetherBrace{#1}{%
        \expandafter\expandafter\expandafter\UD@PassFirstToSecond
        \expandafter\expandafter\expandafter{%
        \expandafter\UD@PassFirstToSecond\expandafter{%
            \romannumeral0\expandafter\UD@ReplaceEveryHashLoop
            \romannumeral0\UD@ExtractFirstArgLoop{#1\UD@SelDOm}{}%
        }{#2}}%
        {\expandafter\UD@ReplaceEveryHashLoop
         \expandafter{\UD@firstoftwo{}#1}}%
      }{%
       \expandafter\UD@CheckWhetherHash
       \romannumeral0\UD@ExtractFirstArgLoop{#1\UD@SelDOm}{#1}{#2}%
      }%
    }%
  }%
}%
\newcommand\UD@CheckWhetherHash[3]{%
  \expandafter\UD@CheckWhetherLeadingSpace\expandafter{\string#1}{%
    \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}}{%
        \expandafter\UD@ReplaceEveryHashLoop
        \expandafter{\UD@firstoftwo{}#2}{#3#1}%
      }{%
        \expandafter\expandafter\expandafter\UD@PassFirstToSecond
        \expandafter\expandafter\expandafter{\expandafter\UD@Exchange
        \expandafter{\string#1}{#3}}{%
          \expandafter\UD@ReplaceEveryHashLoop
          \expandafter{\UD@firstoftwo{}#2}%
        }%
      }%
    }{%
      \expandafter\UD@ReplaceEveryHashLoop
      \expandafter{\UD@firstoftwo{}#2}{#3#1}%
    }%
  }{%
    \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}}{%
        \expandafter\UD@ReplaceEveryHashLoop
        \expandafter{\UD@firstoftwo{}#2}{#3#1}%
      }{%
        \expandafter\expandafter\expandafter\UD@PassFirstToSecond
        \expandafter\expandafter\expandafter{\expandafter\UD@Exchange
        \expandafter{\string#1}{#3}}{%
          \expandafter\UD@ReplaceEveryHashLoop
          \expandafter{\UD@firstoftwo{}#2}%
        }%
      }%
    }{%
      \expandafter\UD@ReplaceEveryHashLoop
      \expandafter{\UD@firstoftwo{}#2}{#3#1}%
    }%
  }%
}%
%----------------------------------------------------------------------

\newcommand\allowhash[1]{\ReplaceEveryHash{#1}}

\newcommand\allowanddetokenizehash[1]{%
  \detokenize\expandafter\expandafter\expandafter{\ReplaceEveryHash{#1}}%
}%

\makeatother

\begin{document}
\allowhash{One hash: #, not two}

\begingroup
\frenchspacing
\ttfamily \allowanddetokenizehash{One hash: #, not two. This time in braces:{#{#{#{#}}}#}}
\endgroup

For comparison the effect of \verb|\detokenize| without prior hash-replacing:

\begingroup
\frenchspacing
\ttfamily \detokenize{One hash: #, not two. This time in braces:{#{#{#{#}}}#}}
\endgroup

\end{document}

在此处输入图片描述

答案2

在此处输入图片描述

\documentclass{article}
\makeatletter
\def\allowhash{%%%%
    \bgroup\everyeof{\egroup}\catcode35=11\relax\scantokens}%
\makeatother
\begin{document}
\allowhash{One hash: #, not two}

\end{document}

或者按照评论中的要求在宏中

在此处输入图片描述

\documentclass{article}
\makeatletter
\def\allowhash{%%%%
    \bgroup\catcode35=11\relax\afterassignment\egroup\gdef\foo}%
\makeatother
\begin{document}
\allowhash{One hash: #, not two}

[\foo]  [\foo]
\end{document}

答案3

\scantokens动作时,#字符已经加倍,因为它们被吸收作为宏的参数。

使用l3regex模块expl3会更容易:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\allowhash}{m}
 {
  \tl_set:Nn \l_tmpa_tl { #1 }
  \regex_replace_all:nnN { \cP\# } { \cO\# } \l_tmpa_tl
  \tl_use:N \l_tmpa_tl
 }
\ExplSyntaxOff

\begin{document}

\allowhash{One hash: #, not two}

\end{document}

在此处输入图片描述

您可以轻松添加对在宏中保存令牌列表的支持。

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\allowhash}{om}
 {
  \tl_set:Nn \l_tmpa_tl { #2 }
  \regex_replace_all:nnN { \cP\# } { \cO\# } \l_tmpa_tl
  \IfNoValueTF { #1 }
   { \tl_use:N \l_tmpa_tl }
   { \tl_set_eq:NN #1 \l_tmpa_tl }
 }

\ExplSyntaxOff

\begin{document}

\allowhash{One hash: #, not two}

\allowhash[\foo]{One hash: #, not two}

\texttt{\meaning\foo}

\end{document}

在此处输入图片描述

(图片中似乎有双倍空格,但这只是由于\nonfrenchspacing。)

相关内容