我正在尝试定义一个\addtohook
将材料添加到另一个宏的宏\hook
,类似于 LaTeX \addto@hook
,但\hook
带有一个参数。
因此,\hook
在一系列\addtohook
调用之后,if 在开始时为空
\addtohook{foo}
\addtohook{bar}
\addtohook{baz}
的预期含义\hook
应该是
#1->\dosomething {#1}{foo}\dosomething {#1}{bar}\dosomething {#1}{baz}.
我的实施方法:
\def\addtohook#1{%
\edef\hook##1{%
\unexpanded\expandafter{\hook{#1}}%
\noexpand\dosomething{##1}{#1}%
}%
}
旧\hook
定义应扩展一次,用占位符 替换参数#1
。然后添加使用\hook
的参数和 的参数的其他材料\addtohook
。
我的问题是,在第二次调用 之后,占位符不知何故变得“不同步” \addtohook
。执行下面的示例代码时,这一点表现得最为明显:
> \hook=macro:
#1->\dosomething {#1}{foo}.
#1->\dosomething {bar}{foo}\dosomething {#1}{bar}.
#1->\dosomething {bar}{foo}\dosomething {baz}{bar}\dosomething {#1}{baz}.
在第二次调用宏之后,旧定义中的占位符消失并被的参数#1
替换。\addtohook
我认为可以通过在该行中添加更多转义来轻松修复该问题\unexpanded\expandafter{\hook{#1}}
,即\unexpanded\expandafter{\hook{##1}}
,但这似乎已经是也很多:
> \hook=macro:
#1->\dosomething {#1}{foo}.
#1->\dosomething {##1}{foo}\dosomething {#1}{bar}.
#1->\dosomething {##1}{foo}\dosomething {##1}{bar}\dosomething {#1}{baz}.
我正在寻找具有以下限制的解决方案:
- 没有
expl3
解决方案。 - 不使用临时文件。
- 不使用其他额外包(如果可能的话)。
\hook
应该定义为宏,因此不使用令牌寄存器(如果可能的话)。
还请注意,以下示例是一个非常简化的版本,我的实际用例要复杂得多(涉及多个参数,动态创建新的钩子,额外的\def
嵌套)。因此,如果我必须在发布答案后稍微修改问题,请耐心等待。
用于测试的 MWE:
\documentclass{article}
\def\hook#1{}
\def\dosomething#1#2{...}
\def\addtohook#1{%
\edef\hook##1{%
\unexpanded\expandafter{\hook{##1}}%
\noexpand\dosomething{##1}{#1}%
}%
\show\hook
}
\addtohook{foo}
\addtohook{bar}
\addtohook{baz}
\begin{document}
\end{document}
编辑:\unexpanded
感谢大家的精彩回答,它们帮助我理解了代码中哪里出了问题。我并没有特别意识到哈希加倍和令牌寄存器的微妙之处。
我决定接受 egreg 的回答(尽管我明确要求非expl3
解决方案;-)),因为对我来说,这个想法似乎是最简单、最干净的解决方案,并且它不需要包含任何包。
为了完整起见,这里是用纯 e-TeX 代码重新实现的想法的本质:
\def\hooktl{}
\long\def\addtohook#1{%
% the hook's replacement text
\long\edef\hooktl{%
\unexpanded\expandafter{\hooktl}%
\unexpanded{\dosomething{##1}{#1}}%
}%
% the hook itself
\long\def\temp##1{%
\def\hook####1{##1}%
}%
\expandafter\temp\expandafter{\hooktl}%
\show\hook
}
\addtohook{foo}
\addtohook{bar (#1)}
\addtohook{...\def\dosomething##1##2{\#1 = ##1, \#2 = ##2 (redefined)\par}}
\addtohook{baz}
\long\def\dosomething#1#2{\#1 = #1, \#2 = #2\par}
\hook{xxx}
\bye
控制台输出的内容
> \hook=macro:
#1->\dosomething {#1}{foo}.
#1->\dosomething {#1}{foo}\dosomething {#1}{bar (#1)}.
#1->\dosomething {#1}{foo}\dosomething {#1}{bar (#1)}
\dosomething {#1}{...\def \dosomething ##1##2{\#1 = ##1, \#2 = ##2 (redefined)\par }}.
#1->\dosomething {#1}{foo}\dosomething {#1}{bar (#1)}
\dosomething {#1}{...\def \dosomething ##1##2{\#1 = ##1, \#2 = ##2 (redefined)\par }}
\dosomething {#1}{baz}.
输出如下
答案1
在此代码中,我维护一个(本地)标记列表,其中包含逐步更新的替换文本;在每个命令之后,也会更新\addtohook
的内部版本。\hook
您还可以使用可选参数动态更改双参数宏。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\addtohook}{O{\dosomething}m}
{
\tl_put_right:Nn \l__siracusa_hook_tl { #1{##1}{#2} }
\cs_set:NV \__siracusa_hook:n \l__siracusa_hook_tl
}
\NewExpandableDocumentCommand{\hook}{m}
{
\__siracusa_hook:n { #1 }
}
\cs_set:Nn \__siracusa_hook:n { }
\cs_generate_variant:Nn \cs_set:Nn { NV }
\tl_new:N \l__siracusa_hook_tl
\ExplSyntaxOff
\newcommand{\dosomething}[2]{\#1=#1,\#2=#2\par}
\newcommand{\dosomethingelse}[2]{\#1=#1(!),\#2=#2\par}
\begin{document}
\addtohook{foo} \hook{x}
\addtohook{bar} \hook{y}
\addtohook[\dosomethingelse]{baz} \hook{z}
\end{document}
答案2
这里是对的无耻复制,最小实现。我省略了对输入宏的所有健全性检查,以使代码保持在合理的数量。这假设:宏(即要添加到的钩子)已定义,是宏,有参数(否则修补可以简单地用 和 完成),可用于etoolbox
\apptocmd
\edef
\unexpanded
\scantokens
而不改变含义(即,其所有标记都具有与修补时生效的标记相同的 catcode),并且要修补的文本中的任何参数标记都是不是catcode 6. 简而言之,\etb@hooktocmd
(第 1357 行etoolbox.sty
) 中的所有内容都通过了。
也就是说,实际的附加过程开始了。首先,它定义一个临时文件\etb@resrvda
,将宏(正在修补的宏)分成三部分:其前缀、其参数文本和其替换文本。当您这样做时,\meaning\mymacro
TeX 会扩展为(catcode 10 和 12)标记:
⟨prefixes⟩macro:⟨parameter text⟩->⟨replacement text⟩
其中是和 David 最喜欢的 的组合,或⟨prefixes⟩
为空。和具有其通常含义。 上面的宏可以用 重新定义。 正如您所猜想的,这将用于重新定义它,并在其后附加新文本。 临时宏如下所示:\long
\protected
\outer
⟨parameter text⟩
⟨replacement text⟩
⟨prefixes⟩\def\mymacro⟨parameter text⟩{⟨replacement text⟩}
% V --- catcode 12 -- V
\def\etb@resrvda#1macro:#2->#3&{#1\def\etb@resrvda #2{#3⟨text-to-be-inserted⟩}}%
% ^ not a macro
其中标记为 的所有内容catcode 12
都是 的扩展\detokenize{⟨text-to-be-inserted⟩}
。还要注意 的\etb@resrvda
定义内部\etb@resrvda
(标记为 的not a macro
)不是宏,而是显示的字符标记(IE, \string\etb@resrvda
)然后它在以下位置使用该宏:
\edef\etb@resrvda{\etb@resrvda\meaning⟨macro-to-patch⟩&}
⟨macro-to-patch⟩
如上所述,它将拆分为:
⟨prefixes⟩\def\etb@resrvda⟨parameter text⟩{⟨replacement text⟩⟨text-to-be-inserted⟩}
catcode 12 中的所有内容。之后,\scantokens
使用适当的集合来重新标记它并执行定义。
这不是一个极其复杂的过程(尽管我把它描绘得如此复杂),但也不是一件小事,它包含许多细节,需要花费大量代码,而且没有经过验证,无法确定宏是否可以“干净地”修补等等。
前置标记的过程相同,只是标记的顺序发生了变化。修补也类似,但在两者之间,你有一个分隔宏,它将要修补的宏一分为二。
现在,具体到您的情况:etoolbox
尝试\(patch|appto|preto)cmd
确保全部#
使用 catcode 12 读入以避免常见的#
-duplication 问题。但是,您将修补放在宏中,因此冻结了 catcode#
并etoolbox
出现投诉。当我删除所有这些时,修补会默默失败。为避免这种情况,您需要在不同的 catcode 设置下定义\addtohook
,其中#
(或使用时使用的任何参数字符\addtohook
)是 catcode 12。我将宏定义为具有/
作为参数字符。
这是您的代码:
\documentclass{article}
%%% Code stolen from etoolbox.sty
\makeatletter
\protected\def\apptocmd{%
\begingroup
\@makeother\#%
\etb@hooktocmd}
\long\def\etb@hooktocmd#1#2{%
\endgroup
\begingroup
\edef\etb@resrvda{%
\def\noexpand\etb@resrvda####1\detokenize{macro}:####2->####3&{%
####1\def\string\etb@resrvda\space####2{####3\detokenize{#2}}}%
\edef\noexpand\etb@resrvda{%
\noexpand\etb@resrvda\meaning#1&}}%
\etb@resrvda
\etb@patchcmd@scantoks\etb@resrvda
\let#1\etb@resrvda
\let\etb@resrvda\etb@undefined}
\def\etb@patchcmd@scantoks#1{%
\edef\etb@resrvda{\endgroup
\endlinechar\m@ne
\unexpanded{\makeatletter\scantokens}{#1}%
\endlinechar\the\endlinechar\relax
\catcode\number`\@=\the\catcode`\@\relax}%
\etb@resrvda}
\makeatother
%%%
\def\hook#1{hello}
\def\dosomething#1#2{.(#1).[#2].}
\begingroup
\catcode`/=6
\catcode`#=12
\gdef\addtohook/1{%
\apptocmd\hook
{\dosomething{#1}{/1}}%
\show\hook
}
\endgroup
\addtohook{foo}
\addtohook{bar}
\addtohook{baz}
\begin{document}
\texttt{\meaning\hook}
\hook{hey}
\end{document}
输出为:
总而言之,我建议加载etoolbox
;-)
答案3
在查看了您的 MWE 进行测试后,我认为您对 LaTeX 感到满意。
连续哈希值的加倍和减半可能会引发问题:
在扩展传递定义的宏时⟨balanced text⟩
,(La)TeX 会将两个连续的哈希值合并为一个,即连续哈希值的数量将减半。
例如,随着\def\temp{######}
,收益率扩大\temp
:###
。
当或期间发生 时,内的哈希值⟨balanced text⟩
将会\unexpanded
加倍。\unexpanded
\edef
\xdef
如果令牌寄存器的内容在或期间通过 -expansion⟨balanced text⟩
传递,则令牌寄存器内容的哈希值将会加倍。\the
\edef
\xdef
您尝试过:
\def\addtohook#1{%
\edef\hook##1{%
\unexpanded\expandafter{\hook{#1}}%
\noexpand\dosomething{##1}{#1}%
}%
}
这将在以前构成 的标记集中⟨replacement text⟩
替换\hook
宏参数#1
,例如foo
。
并且您可能会得到\addtohook
参数的不必要的扩展。
您可以尝试:
\def\addtohook#1{%
\edef\hook##1{%
\unexpanded\expandafter{%
\hook{##1}\dosomething{##1}{#1}%
}%
}%
}
但这样你就得到了不受欢迎的哈希加倍:根据上述定义,例如尝试
\def\hook#1{\dosomething{#1}{start}}%
\addtohook{\def\bal#1{#1}}
\show\hook
\addtohook{foo}
\show\hook
\addtohook{bar}
\show\hook
\addtohook{baz}
\show\hook
\addtohook{\def\bat#1{#1}}
\show\hook
\csname stop\endcsname % stop a LaTeX run
\bye % stop a plain TeX run
看看你得到了什么。
您无法轻易摆脱这种哈希加倍陷阱,因为 e(La)TeX's \unexpanded
/ (La)TeX's内部或 无法知道哈希是否来自 的参数并因此形成另一个 -instance 的第二个参数的标记,因此应该加倍,或者该哈希是否作为参数提供以获得形成前一个定义文本的那组标记,因此不应该加倍。\the⟨token register⟩
\edef
\xdef
\addtohook
\dosomething
\hook
\hook
这个陷阱的要点是:
\newtoks\mytoks
%
\def\test#1{#1##1####1}%
\show\test
%
\mytoks\expandafter{\test{#1}}%
\edef\test#1{\the\mytoks}%
\show\test
%
\def\test#1{#1##1####1}%
\edef\test#1{\unexpanded\expandafter{\test{#1}}}%
\show\test
%
\csname stop\endcsname % stop a LaTeX run
\bye % stop a plain TeX run
第一个\show
结果看起来还不错:
> \test=macro:
#1->#1##1####1.
第二个和第三个\show
产生的结果看起来不太对劲,因为之后的第一个哈希值->
被翻倍了:
> \test=macro:
#1->##1##1####1.
原因是:
在\test
第二次和第三次之前的赋值中\show
,定义文本中连续哈希的数量在扩展时会减半\test
,并且属于的那个哈希#1
将被标记序列#
,替换1
:
在 之后\def\test#1{#1##1####1}
,\mytoks\expandafter{\test{#1}}%
结果为:\mytoks{#1#1##1}%
因为第二和第三个哈希序列减半,而第一个哈希序列在此扩展级别上形成参数,因此被\test
的参数中的标记序列替换,即#1
。在接下来的\edef
-assignment 期间,所有源自标记寄存器内容的哈希都将加倍。
在 之后\def\test#1{#1##1####1}
, \unexpanded\expandafter{\test{#1}}%
结果为: \unexpanded{#1#1##1}%
因为第二和第三个哈希序列减半,而第一个哈希序列在此扩展级别上形成参数,因此被\test
的参数中的标记序列替换,即#1
。由于\unexpanded
在 期间执行\edef
,因此源自执行的所有哈希\unexpanded
都将加倍。
因此我建议采取不同的路线:
做类似这样的事(有点伪代码):
\def\addtohook#1{%
\def\hook##1{%
Within the sequence
( Expansion of \hook{<reserved token>1} + \dosomething{<reserved token>1}{#1} )
have every hash doubled and every instance of <reserved token> replaced by a single hash.
}%
}%
当然你还需要检查是否\hook
已经定义。
这是我在下面的例子中实现的。在下面的例子中,eTeX 扩展是实现可靠检查的必要条件,用于查明单个标记是否是类别代码 6(参数)的显式字符标记/用于查明单个标记是否是显式哈希字符标记。该测试的要点是:应用于\string
哈希,您将获得类别代码 12(其他)的单个显式字符标记。将 eTeX 应用于\detokenize
哈希,您将获得两个这样的标记,因为\detokenize
哈希值加倍。
下面的示例\romannumeral
大量使用了 -expansion:-expansion 的要点\romannumeral
是它\romannumeral
本身会触发大量扩展工作,但如果在完成所有扩展工作后发现一个非正数,则不会默默地传递任何标记。此\romannumeral
功能非常方便,因为它意味着在许多情况下,单个\expandafter
-chain“命中”\romannumeral
足以触发多个扩展步骤。您只需要确保扩展工作会产生一个标记序列,其前导标记是,例如0
和[space]
。因为\romannumeral
该序列将形成0
非正数,因此该序列将被默默丢弃,而标记流中其后面的任何内容都将保留在原处。
我在回答这个问题时详细阐述了这一点如何知道附加到 csname 宏时的 expandafter 数量?
\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}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no exclamation mark which is not nested
%% in braces:
%%.............................................................................
%% \UD@CheckWhetherNoExclam{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no exclamation mark>}%
%% {<Tokens to be delivered in case that argument
%% contains exclamation mark>}%
%%
\newcommand\UD@GobbleToExclam{}\long\def\UD@GobbleToExclam#1!{}%
\newcommand\UD@CheckWhetherNoExclam[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToExclam#1!}%
}%
%%-----------------------------------------------------------------------------
%% \addtohook@reservedFork grabs the first thing behind a
%% a token-sequence of pattern !!\addtohook@reserved!
%%.............................................................................
\newcommand\addtohook@reservedFork{}
\long\def\addtohook@reservedFork#1!!\addtohook@reserved!#2#3!!!!{#2}%
%%-----------------------------------------------------------------------------
%% Check whether argument consists only of the token \addtohook@reserved
%%.............................................................................
\newcommand\UD@CheckWhetherAddtohook@reserved[1]{%
\romannumeral0%
\UD@CheckWhetherNoExclam{#1}{%
\addtohook@reservedFork
%Case #1 is empty/has no tokens:
!#1!\addtohook@reserved!{\UD@Exchange{ }{\expandafter}\UD@secondoftwo}%
%Case #1 = \addtohook@reserved:
!!#1!{\UD@Exchange{ }{\expandafter}\UD@firstoftwo}%
%Case #1 = something else without exclamation-mark:
!!\addtohook@reserved!{\UD@Exchange{ }{\expandafter}\UD@secondoftwo}%
!!!!%
}{%
%Case #1 = something else with exclamation-mark:
\UD@Exchange{ }{\expandafter}\UD@secondoftwo
}%
}%
%%-----------------------------------------------------------------------------
%% 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}}%
}%
%%=============================================================================
%% \DoubleEveryHashAndReplaceAddtohook@reserved{<argument>}%
%%
%% Each explicit catcode-6(parameter)-character-token of the <argument>
%% will be doubled. Each instance of \addtohook@reserved will be replaced
%% by a single hash.
%%
%% You obtain the result after two expansion-steps, i.e.,
%% in expansion-contexts you get the result after "hitting"
%% \DoubleEveryHashAndReplaceAddtohook@reserved 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\DoubleEveryHashAndReplaceAddtohook@reserved[1]{%
\romannumeral0\UD@DoubleEveryHashAndReplaceAddtohook@reservedLoop{#1}{}%
}%
\newcommand\UD@DoubleEveryHashAndReplaceAddtohook@reservedLoop[2]{%
\UD@CheckWhetherNull{#1}{ #2}{%
\UD@CheckWhetherLeadingSpace{#1}{%
\expandafter\UD@DoubleEveryHashAndReplaceAddtohook@reservedLoop
\expandafter{\UD@removespace#1}{#2 }%
}{%
\UD@CheckWhetherBrace{#1}{%
\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\expandafter\expandafter\expandafter{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral0%
\expandafter\UD@DoubleEveryHashAndReplaceAddtohook@reservedLoop
\romannumeral0%
\UD@ExtractFirstArgLoop{#1\UD@SelDOm}{}%
}{#2}}%
{\expandafter\UD@DoubleEveryHashAndReplaceAddtohook@reservedLoop
\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}}{%
% something whose stringification yields a single space
\UD@secondoftwo
}{% explicit space of catcode 6
\UD@firstoftwo
}%
}{% something whose stringification has a leading space
\UD@secondoftwo
}%
}{%
\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@DoubleEveryHashAndReplaceAddtohook@reservedLoop
\expandafter{\UD@firstoftwo{}#2}{#3#1#1}%
}{% no hash
\UD@CheckWhetherAddtohook@reserved{#1}{%
\expandafter\UD@DoubleEveryHashAndReplaceAddtohook@reservedLoop
\expandafter{\UD@firstoftwo{}#2}{#3##}%
}{%
\expandafter\UD@DoubleEveryHashAndReplaceAddtohook@reservedLoop
\expandafter{\UD@firstoftwo{}#2}{#3#1}%
}%
}%
}%
%%=============================================================================
% \addtohook{<name of hook-macro which processes one argument>}{%
% <tokens to add to hook>%
% }%
%
% adds the sequence `\dosomething{#1}{<tokens to add to hook>}` to the
% definition-text of the macro whose name is
% <name of hook-macro which processes one argument>.
%
% That nacro must be defined to process one non-optional argument.
%------------------------------------------------------------------------------
\newcommand\addtohook[2]{%
\expandafter\long
\expandafter\def
\csname #1\expandafter\endcsname
\expandafter##%
\expandafter1%
\expandafter{%
\romannumeral0%
\UD@Exchange{ }{%
\expandafter\expandafter
\expandafter \expandafter
\expandafter\expandafter
\expandafter
}%
\expandafter\DoubleEveryHashAndReplaceAddtohook@reserved
\expandafter{%
\romannumeral0%
\expandafter\ifx\csname #1\endcsname\relax
\expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo
\fi
{ }%
{%
\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\csname#1\endcsname{\addtohook@reserved1}%
}%
\dosomething{\addtohook@reserved1}{#2}%
}%
}%
}%
\makeatother
\addtohook{hook}{\def\bal#1{#1}}
\show\hook
\addtohook{hook}{foo}
\show\hook
\addtohook{hook}{bar}
\show\hook
\addtohook{hook}{baz}
\show\hook
\addtohook{hook}{\def\bat#1{#1}}
\show\hook
\stop % stop the LaTeX-run without a document-environment
答案4
假设\hook
定义为
\long\gdef\hook#1{\doSomething{#1}{foo}}
您可以创建一个\oneargappend
与 类似的宏\g@addto@macro
,但对于采用一个参数的宏:
\long\gdef\oneargappend#1#2{%
\expandafter\def\expandafter#1\expandafter##\expandafter1\expandafter{%
#1{##1}#2%
}%
}
\g@addto@macro
(和的另一个区别\oneargappend
是后者将支持\long
、\global
等。)
这样你就可以做到,
\global\long\oneargappend\hook{\doSomething{#1}{bar}}
这样当您调用时\meaning\hook
您就会得到以下结果。
\long macro:#1->\doSomething {#1}{foo}\doSomething {#1}{bar}
为了完整性,如果你想对带有两个参数的宏执行相同的操作,则必须使用,
\long\gdef\twoargsappend#1#2{%
\expandafter\def\expandafter#1\expandafter##\expandafter1\expandafter##\expandafter2\expandafter{%
#1{##1}{##2}#2%
}%
}
如果您想处理更多论点,依此类推。