我面临的问题是,在没有参数的宏定义前面添加和附加一些标记。
我发现在 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 的反对,我认为有必要对我的答案进行重大修改:]
您可以使用两个暂存令牌寄存器。一个用于\edef
ing,另一个用于重置其自身和用于\edef
ing 的寄存器:
\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}