如何仅在当前范围内将标记添加到宏定义的前面和后面?

如何仅在当前范围内将标记添加到宏定义的前面和后面?

我面临的问题是,在没有参数的宏定义前面添加和附加一些标记。

我发现在 LaTeX2e 内核中有一个\g@addto@macro用于将标记附加#2到控制序列定义的宏#1

> \g@addto@macro=\long macro:
#1#2->\begingroup \toks@ \expandafter {#1#2}\xdef #1{\the \toks@ }\endgroup .
l.2 \show\g@addto@macro

似乎使用令牌寄存器\toks@是为了避免需要加倍哈希值。
似乎所有分配都在引入的本地范围内进行,\begingroup..\endgroup以避免更改是\toks@永久性的。
似乎\xdef用于使重新定义比本地范围的关闭持续更长时间。

这样,重新定义就是全局的。

有没有一种方法可以将标记附加和添加到不带参数的宏的定义中,从而使所讨论的宏的重新定义不是全局的,而是局限于当前范围,并且临时寄存器不会在当前范围内永久改变?

答案1

LaTeX 内核版本仅使用 TeX90 原语,正如问题中所观察到的,避免出现#token问题非常棘手在这种情况下,保持所有临时变量不变。(我本来想说全局赋值是传统 TeX 中唯一安全的方法,但我注意到Ulrich Diez 找到了方法

使用 e-TeX,我们可以使用可扩展原语\unexpanded,它充当匿名 toks,因此不需要分组。LaTeX 使用它etoolbox包,但概念在原语中足够简单:

\protected\long\def\appto#1#2{%
  \edef#1{%
    \unexpanded\expandafter{#1#2}%
  }%
}
\protected\long\def\preto#1#2{%
  \edef#1{%
    \unexpanded{#2}%
    \unexpanded\expandafter{#1}%
  }%
}

(这些etoolbox版本涵盖了未定义的情况#1,但是否需要取决于用例和您认为适用的语义!)

答案2

[由于 Bruno Le Floch 的反对,我认为有必要对我的答案进行重大修改:]

您可以使用两个暂存令牌寄存器。一个用于\edefing,另一个用于重置其自身和用于\edefing 的寄存器:

\documentclass{article}

\makeatletter
%
% \Addtohook{<control sequence>}{<tokens to prepend>}{<tokens to append>}
% =======================================================================
%
% <control sequence> is a control sequence that does not take any arguments.
% \Addtohook _within_the_current_scope_ prepends to the definition of that
% control sequence the <tokens to prepend> and appends the definition of
% that control sequence the <tokens to append>.
\newcommand\Addtohook[3]{%
  \@temptokena\expandafter{%
              \expandafter\@temptokena
              \expandafter{%
              \the\expandafter\@temptokena
                  \expandafter}%
                  \expandafter\toks@
                  \expandafter{%
                  \the\toks@}%
  }%
  \toks@{#2}%
  \toks@\expandafter{\the\expandafter\toks@#1#3}%
  \edef#1{\the\toks@}%
  \the\@temptokena
}%

%\makeatother

\newcommand\myhook{\def\Middlepiece##1{Argument Middlepiece:##1}}%

\begin{document}

\ttfamily
\@temptokena{tEST tEST}%
\toks@{Test Test}%
\string\the\string\@temptokena=\the\@temptokena

\string\the\string\toks@=\the\toks@

\string\myhook=\meaning\myhook

\begingroup
\Addtohook{\myhook}%
          {\def\Frontpiece#1{Argument Frontpiece:#1}}%
          {\def\Tailpiece#1{Argument Tailpiece:#1}}%

\string\myhook=\meaning\myhook

\string\the\string\@temptokena=\the\@temptokena

\string\the\string\toks@=\the\toks@
\endgroup

\string\myhook=\meaning\myhook

\end{document}

我最喜欢的方式(由 Bruno Le Floch 提出)是\edef仅使用一个临时令牌寄存器。

\documentclass{article}

\makeatletter
%
% Paraphernalia:
% ==============
\newcommand\UD@exchange[2]{#2#1}%
%
% \Addtohook{<control sequence>}{<tokens to prepend>}{<tokens to append>}
% =======================================================================
%
% <control sequence> is a control sequence that does not take any arguments.
% \Addtohook _within_the_current_scope_ prepends to the definition of that
% control sequence the <tokens to prepend> and appends the definition of
% that control sequence the <tokens to append>.
\newcommand\Addtohook[3]{%
  \expandafter\UD@exchange
  \expandafter{%
  \expandafter\toks@
  \expandafter{%
  \the\toks@}}{%
  \toks@\expandafter\expandafter
        \expandafter{%
        \expandafter\UD@exchange
        \expandafter{#1#3}{#2}}\edef#1{\the\toks@}}%
}%

%\makeatother

\newcommand\myhook{\def\Middlepiece##1{Argument Middlepiece:##1}}%

\begin{document}

\ttfamily
\@temptokena{tEST tEST}%
\toks@{Test Test}%
\string\the\string\@temptokena=\the\@temptokena

\string\the\string\toks@=\the\toks@

\string\myhook=\meaning\myhook

\begingroup
\Addtohook{\myhook}%
          {\def\Frontpiece#1{Argument Frontpiece:#1}}%
          {\def\Tailpiece#1{Argument Tailpiece:#1}}%

\string\myhook=\meaning\myhook

\string\the\string\@temptokena=\the\@temptokena

\string\the\string\toks@=\the\toks@
\endgroup

\string\myhook=\meaning\myhook

\end{document}

到目前为止所展示的方法并没有保留\long根据所定义的宏的状态\long

这可以通过检查是否在正确的位置\meaning包含短语来实现:\long

\documentclass{article}

\makeatletter
%
% Paraphernalia:
% ==============
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
%
%------------------------------------------------------------------------------
% 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>
%
% (\romannumeral expansion was introduced by me in order to overcome the
%  concerns and worries about improperly balanced \if..\else..\fi constructs.)
\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 argument has a first token whose meaning starts with the
% phrase \long macro:-> :
%.............................................................................
% \UD@CheckWhetherLeadingLong{<argument which is to be checked>}%
%                            {<Tokens to be delivered in case <argument which
%                              is to be checked> has 1st token whose meaning
%                              has leading phrase \long macro:-> >}%
%                            {<Tokens to be delivered in case <argument which
%                              is to be checked> has no 1st token whose
%                              meaning has leading phrase \long macro:-> >}%
\begingroup
\def\UDtempa#1{%
  \endgroup
  \newcommand\UD@CheckWhetherLeadingLong[1]{%
    \romannumeral0\UD@CheckWhetherBrace{##1}%
    {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
    {\expandafter\expandafter\expandafter\UD@secondoftwo
     \expandafter\string\expandafter{%
     \expandafter\UD@CheckWhetherLeadingLongB\meaning##1.#1}{}}%
  }%
  \newcommand\UD@CheckWhetherLeadingLongB{}%
  \long\def\UD@CheckWhetherLeadingLongB##1#1{%
    \UD@CheckWhetherNull{##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}%
  }%
}%
\begingroup
\edef\UDtempa{%
  \string\long\UD@firstoftwo{ }{}%
  \string m\string a\string c\string r\string o\string:\string-\string>%
}%
\expandafter\endgroup\expandafter\UDtempa\expandafter{\UDtempa}%
%-----------------------------------------------------------------------------
% Within the current scope prepend and append some tokens to the definition
% of a control sequence that does not take arguments:
%.............................................................................
% \Addtohook{<control sequence>}{<tokens to prepend>}{<tokens to append>}
\newcommand\Addtohook[3]{%
    \expandafter\UD@exchange\expandafter{%
      \expandafter\toks@\expandafter{\the\toks@}%
    }{%
      \toks@\expandafter\expandafter\expandafter{%
      \expandafter\UD@exchange\expandafter{#1#3}{#2}}%
      \UD@CheckWhetherLeadingLong{#1}{\long}{}\edef#1{\the\toks@}%
    }%
}%

%\makeatother

\newcommand\myhook{\def\Middlepiece##1{Argument Middlepiece:##1}}%

\newcommand*\myhookB{\def\Middlepiece##1{Argument Middlepiece:##1}}%

\begin{document}

\ttfamily
\@temptokena{tEST tEST}%
\toks@{Test Test}%

Appending to \string\long-macro:

\string\the\string\@temptokena=\the\@temptokena

\string\the\string\toks@=\the\toks@

\string\myhook=\meaning\myhook

\begingroup
\Addtohook{\myhook}%
          {\def\Frontpiece#1{Argument Frontpiece:#1}}%
          {\def\Tailpiece#1{Argument Tailpiece:#1}}%

\string\myhook=\meaning\myhook

\string\the\string\@temptokena=\the\@temptokena

\string\the\string\toks@=\the\toks@
\endgroup

\string\myhook=\meaning\myhook

\hrulefill

Appending to non-\string\long-macro:

\string\the\string\@temptokena=\the\@temptokena

\string\the\string\toks@=\the\toks@

\string\myhookB=\meaning\myhookB

\begingroup
\Addtohook{\myhookB}%
          {\def\Frontpiece#1{Argument Frontpiece:#1}}%
          {\def\Tailpiece#1{Argument Tailpiece:#1}}%

\string\myhookB=\meaning\myhookB

\string\the\string\@temptokena=\the\@temptokena

\string\the\string\toks@=\the\toks@
\endgroup

\string\myhookB=\meaning\myhookB

\end{document}

问题:是否有一种方法可以附加不带参数的宏的定义,以防在此期间该宏定义的某些标记已被重新定义\outer?(我现在还没有找到答案。)

IE

\newcommand\myhook{\def\Middlepiece##1{Argument Middlepiece:##1}}%
\outer\def\Middlepiece{This is outer now!}%
...
\Addtohook{\myhook}%
          {\def\Frontpiece#1{Argument Frontpiece:#1}}%
          {\def\Tailpiece#1{Argument Tailpiece:#1}}%

答案3

\preto对于简单情况,您可以使用符合标准范围规则的包\appto来向无参数宏添加标记。etoolbox

\documentclass{article}
\usepackage{etoolbox}

\begin{document}

\newcommand{\foo}{FOO}

\foo

\begingroup\preto\foo{AAA}\foo\endgroup

\foo

\end{document}

将打印

哈哈
哈哈
哈哈

如果您有更复杂的东西,例如在命令中附加或添加代码#\def则可以使用regexpatch

\documentclass{article}

\usepackage{regexpatch}

\begin{document}

\newcommand{\foo}[1]{FOO#1FOO}

\texttt{\meaning\foo}\par
\foo{x}

\bigskip

\begingroup
\xpretocmd\foo{\def\AAA##1{AAA##1AAA}}{}{}
\texttt{\meaning\foo}\par
\foo{x} \AAA{y}
\endgroup

\bigskip

\texttt{\meaning\foo}\par
\foo{x}

\end{document}

在此处输入图片描述

注意自然语法:您想要双#字符,因此您输入##1

还有\xapptocmd用于附加的内容。

答案4

使用xpatch包裹。它提供命令来将内容添加到宏主体的前面和后面,以及替换标记。通过将替换放入组中,它保持本地状态。

下面的文档是根据其下方的代码排版的。

在此处输入图片描述

\documentclass{article}
\usepackage{xpatch}
\begin{document}

\section{Some section to be referenced}\label{sec}

This is a normal reference to section~\ref{sec}.
\bgroup
\xpretocmd\ref{***}{}{}%
\xapptocmd\ref{+++}{}{}%
This is a strange reference to section~\ref{sec}.
\egroup
This is a normal reference to section~\ref{sec}.

\end{document}

相关内容