当使用 LaTeX 时,我常常发现自己希望以编程方式构建宏,使用一系列指令而不是通常的“模板”意义。
我所说的“以编程方式”是指:不是直接指定宏的代码,而是使用一组指令将代码片段逐步附加到缓冲区,然后发出最终命令将代码“编译”为新宏。大多数其他语言中的等效方法是字符串连接 + “eval”实用程序。
理想情况下,应该存在三条指令:
\AddExpandedToMacro{<code>}
:完全扩展 <code> 并附加到正在构建的宏\AddUnexpandedToMacro{<code>}
:将 <code> 附加到正在构建的宏而不进行扩展;<code> 可能包含命令标记(例如,\foo
)并且理想情况下(如果可能)包含平衡组(例如,{bar}
)和平衡数学模式移位(例如,$baz$
)\AddCharToMacro{<name>}
:将 <name> 指定的特殊字符附加到正在构建的宏;<name> 可以是以下之一:
bg - 开始组('{'),
eg - 结束组('}'),
a - 对齐('&'),
p - 宏的参数('#'),
c - 命令('\')。
指令的名称和语法只是建议。例如,我希望能够简单地编写\AddUnexpandedToMacro{\foo{#1}}
字符串“\foo{#1}”并将其附加到正在构建的宏中,但我怀疑这在 LaTeX 中是不可能的,除非弄乱 catcodes,这反过来又会造成传递参数的各种麻烦。
说明将夹在以下两部分之间:
\StartMacro{<name>}
:将 <name> 完全扩展为字符串并开始构造宏\<name>
,刷新任何先前在范围内具有相同名称的构造\BuildMacro{<name>}
或\BuildMacro[<argcount>]{<name>}
:作用于确切地* 与 相同
\newcommand {\<name>} [<argcount>] {<all appended code from instructions>}
,即,当命令完成时,\<name>
将在本地范围内定义一个宏,该宏接受 <argcount> 个参数(如果[<argcount>]
省略则不接受任何参数),并且“所有附加的指令代码”的解释与直接输入而不是增量构建的解释完全相同。(\BuildMacro
如果构建的代码不平衡,则 的行为将不确定。)
* 尽可能接近 LaTeXically
正在构建的宏的代码将存储在本地或全局。这并不重要。
因此,例如:
\StartMacro{mymacro}
\AddExpandedToMacro{[\ref{somelabel}]: }
\AddUnexpandedToMacro{\niceand}
\AddCharToMacro{c}
\AddExpandedToMacro{fancy\roman{mycounter}}
\AddCharToMacro{bg}
\AddCharToMacro{p}
\AddUnexpandedToMacro{1}
\AddCharToMacro{eg}
\BuildMacro [1] {mymacro}
完成后将产生与
\newcommand {\mymacro} [1] {[6.2]: \niceand\fancyiv{#1}}
假设\ref{somelabel}
扩展到6.2并且mycounter
是一个值为4的计数器。
再举一个例子,代码:
\newcommand {\chapterdata} {lion/Lions of the Serengeti, dolphin/Dolphins of the Pacific}
\newcommand {\AddBracketed} [1]
{\AddCharToMacro{bg}#1\AddCharToMacro{eg}}
\newcommand {\AddTitle} {\AddBracketed{\AddExpandedToMacro{\chaptitle}}}
\foreach \chapid/\chaptitle in \chapterdata { % for each chapter
\StartMacro{\chapid chaptitle}
\ifx\smallcaptitles\undefined % if not using \textsc
\AddExpandedToMacro{\chaptitle}
\else % if using \textsc
\AddUnexpandedToMacro{\texorpdfstring}
\AddBracketed{%
\AddUnexpandedToMacro{\textsc}%
\AddTitle}
\AddTitle
\fi
\BuildMacro{\chapid chaptitle}
}
完成后将产生与
\newcommand {\lionchaptitle} {Lions of the Serengeti}
\newcommand {\dolphinchapter} {Dolphins of the Pacific}
如果\smallcaptitles
未定义,否则
\newcommand {\lionchaptitle} {\texorpdfstring{\textsc{Lions of the Serengeti}}{Lions of the Serengeti}}
\newcommand {\dolphinchaptitle} {\texorpdfstring{\textsc{Dolphins of the Pacific}}{Dolphins of the Pacific}}
if\smallcaptitles
已定义。
(顺便说一句,我意识到这两个例子都可以通过其他方式实现。为了简洁起见,我把例子写得简单了。我正在考虑的实际应用程序在许多不同的子程序、循环、条件等中添加了不同的“片段”,而且要复杂得多。)
诸如定义受保护/全局/等宏的能力、定义带有可选参数的宏的能力、用于将常用代码片段附加到宏的更多样化的“指令集”等功能都很好,但并非绝对必要。
我的问题是:
在 LaTeX 中可以实现这样的程序化宏构造实用程序吗(遗憾的是没有 LuaTeX)?
如果是,那么是否有一个软件包可以提供此功能或类似的功能?
如果没有具有此功能的软件包,那么实现此功能需要多大的努力?
答案1
不确定这有什么用,但是...
\documentclass{article}
\usepackage{refcount}
\ExplSyntaxOn
\NewDocumentCommand{\startmacro}{}
{
\tl_clear:N \l__coto_macro_body_tl
}
\NewDocumentCommand{\finishmacro}{smm}
{% #1 = *, #2 = macro name, #3 = number of arguments
\IfBooleanTF{#1}
{
\coto_macro_finish:NNn \cs_set_protected:cn #2 { #3 }
}
{
\coto_macro_finish:NNn \cs_set:cn #2 { #3 }
}
}
\NewDocumentCommand{\addexpanded}{m}
{
\tl_put_right:Nx \l__coto_macro_body_tl { #1 }
}
\NewDocumentCommand{\addunexpanded}{m}
{
\tl_put_right:Nn \l__coto_macro_body_tl { \exp_not:n { #1 } }
}
\NewDocumentCommand{\addcsname}{m}
{
\tl_put_right:Nx \l__coto_macro_body_tl { \exp_not:n { \exp_not:c { #1 } } }
}
\NewDocumentCommand{\addleftbrace}{}
{
\tl_put_right:Nn \l__coto_macro_body_tl { \if_true: { \else: } \fi: }
}
\NewDocumentCommand{\addrightbrace}{}
{
\tl_put_right:Nn \l__coto_macro_body_tl { \if_false: { \else: } \fi: }
}
\NewDocumentCommand{\addparameter}{m}
{
\tl_put_right:Nn \l__coto_macro_body_tl { ## #1 }
}
\tl_new:N \l__coto_macro_body_tl
\cs_generate_variant:Nn \tl_set:Nn { Ne }
\cs_new_protected:Nn \coto_macro_finish:NNn
{
\tl_set:Ne \l__coto_macro_body_tl { \l__coto_macro_body_tl }
\exp_args:NnV #1 { __coto_macro_temp: \prg_replicate:nn { #3 } { n } } \l__coto_macro_body_tl
\cs_new_eq:Nc #2 { __coto_macro_temp: \prg_replicate:nn { #3 } { n } }
}
\ExplSyntaxOff
\begin{document}
\newcommand{\niceand}{NICEAND}
\newcommand{\fancyiv}[1]{FANCYIV-#1-}
\newcounter{mycounter}
\setcounter{mycounter}{4}
\setcounter{section}{6}
\setcounter{subsection}{1}
\refstepcounter{subsection}\label{somelabel}
\startmacro
\addexpanded{[\getrefnumber{somelabel}]: }
\addunexpanded{\niceand}
\addcsname{fancy\roman{mycounter}}
\addleftbrace
\addparameter{1}
\addrightbrace
\finishmacro{\mymacro}{1}
\texttt{\meaning\mymacro}
\mymacro{xyz}
\end{document}
用\finishmacro*
宏就可以了\protected
。
答案2
不确定这有什么用,但它可以仅通过 TeX 原语简单地实现,不需要 Expl3:
\def\addto #1#2{\expandafter\def\expandafter#1\expandafter{#1#2}}
\def\p{##}
\def\b{{\iffalse}\fi}
\def\e{\iffalse{\fi}}
\def\startmacro{\def\tmp{}}
\def\addexpanded#1{\addto\tmp{#1}}
\def\addunexpanded#1{\addto\tmp{\unexpanded{#1}}}
\def\addcsname#1{\addto\tmp{\expandafter\noexpand\csname#1\endcsname}}
\def\addleftbrace{\addto\tmp{\b}}
\def\addrightbrace{\addto\tmp{\e}}
\def\addparameter#1{\addto\tmp{\p#1}}
\def\finishmacro#1#{\toks0={#1}\finishmacroA}
\def\finishmacroA#1{\expandafter\edef\csname#1\expandafter\endcsname\the\toks0{\tmp}}
%test:
\def\reference{[6.1]}
\newcount\counter \counter=13
\startmacro
\addexpanded{\reference}
\addunexpanded{\niceand}
\addcsname{fancy\the\counter}
\addleftbrace
\addparameter 1
\addrightbrace
\finishmacro #1{mymacro}
{\tt \meaning\mymacro}
\bye
答案3
这种功能最灵活的方法是expl3
您可以操作令牌列表,正如 Ulrike 在评论中提到的以及 egreg 在他的回答中所展示的那样。
但是,作为概念验证,这里是使用该etoolbox
包实现的第一个示例。该包提供了\apptocmd
宏,可用于在现有命令的末尾附加代码。因此,这个想法是从一个空命令开始,然后在整个文档中添加各个部分。
有一些限制,主要是每个部分必须平衡且格式正确。因此,您不能先添加单个{
,然后再添加相应的}
,也不能#
先添加,1
然后再添加。当然,\bgroup
和\egroup
本身是可以单独使用的,因为它们是单独的宏。
要创建一个\fancyiv
由不同部分组成的命令名,您可以使用\csname ... \endcsname
,它接近字符串连接 + eval 原理,但仅用于构建宏名。
对于扩展,一个相对简单的方法是使用 提供的列表数据结构etoolbox
。这样可以将宏\listadd
用于未扩展的内容和\listeadd
扩展的内容。然后在最后,您可以循环列表并使用 将每个列表项添加到宏中\apptocmd
。但是,不幸的是,您无法轻松地将诸如 之类的参数存储在列表中。因此,应该在循环之外使用#1
单独的调用添加这些参数。另一个选择是使用单独的调用而不是 ,并且仅将列表用于需要扩展的内容。\apptocmd
\apptocmd
\listadd
请注意,这\ref
是特殊情况:在第一次运行 LaTeX 时,当数字尚不清楚时,您无法\ref
使用 进行扩展\edef
,并且也不能使用 进行扩展\listeadd
。在第二次运行中\ref
是可以扩展的,但如果第一次运行因错误而中止,您当然无法进行第二次运行。但是,您可以使用包\getrefnumber
中的refcount
,它在第一次运行时扩展为0
,并在后续运行中使用正确的数字。
梅威瑟:
\documentclass{article}
\usepackage[T1]{fontenc} % ensure correct output of \meaning
\usepackage{refcount} % expansion of \ref
\usepackage{etoolbox} % build macros
\newcounter{mycounter}
\newcommand{\fancyiv}[1]{fancy 4: #1} % dummy helper macro
\begin{document}
\setcounter{section}{6}
\setcounter{subsection}{1}
\subsection{Building a macro}
\label{somelabel}
% AddExpandedToMacro
\listeadd{\macroparts}{[\getrefnumber{somelabel}]}
% AddUnexpandedToMacro
\listadd{\macroparts}{\LaTeX\ }
\setcounter{mycounter}{4}
% Construct command name
\listadd{\macroparts}{\csname fancy\roman{mycounter}\endcsname}
% BuildMacro step 1: initialize empty macro
\newcommand{\mymacro}[1]{}
% BuildMacro step 2: initialize list processing
\renewcommand*{\do}[1]{\apptocmd{\mymacro}{#1}{\typeout{adding #1 ok}}{\typeout{adding #1 failed}}}
% BuildMacro step 3: execute list processing
\dolistloop{\macroparts}
% Workaround for arguments: directly append {#1} outside of the loop
\apptocmd{\mymacro}{{#1}}{\typeout{argument ok}}{\typeout{argument failed}}
% Results
Macro code:\\\small\meaning\mymacro\\[1Em]
\normalsize Result: \mymacro{hi}
\end{document}
当然,这个概念证明非常简单并且存在一些问题,但它在某种程度上解决了“是否有一个包提供此功能或类似功能”的问题。
最后一点:问题指出“我经常发现自己想要一种以编程方式构建宏的方法”。虽然看看你能走多远很有趣,但在 LaTeX 中采用这种方法编写宏却相当不寻常。这可能是在另一种编程语言或范例中经验丰富并试图将其应用于 LaTeX 的症状。然而,在这种情况下,这可能不是最好的方法。一般来说,在编写文档时最好不要定义太多自己的宏,并坚持使用 LaTeX 为您提供的工具 - 换句话说,尽量不要让事情变得不必要地复杂 :)
答案4
您希望一次积累构成宏定义文本的标记吗?
我有时也会做这样的事情。通常我通过宏来实现,这些宏会递归调用自身,并在每次迭代中在其中一个参数中逐渐积累所需的标记集。
这样你就不需要那么多中间分配了。
如果您打算将\AddUnexpandedToMacro
/\AddExpandedToMacro
等用作具有类似递归例程的辅助宏,我建议重新考虑是否也采用基于(尾部)递归扩展的方法(其中所需的标记在宏参数中累积)也是可行的。
是因为它可能。
TeX 从 .tex 输入文件中获取一组指令,用于将标记附加到标记流中。
标记可以是显式字符标记或控制序列标记。
宏的扩展及其参数的处理发生在标记化已完成时。
您说要将\AddCharToMacro{c}
特殊字符“command ( \
)”附加到正在构造的宏中。
这个特殊字符“命令(\
)”是干什么的?
如果事物是按照宏参数来处理的,那么它们将作为标记来处理,因此——由于事物已经被标记化——无需特别考虑反斜杠。
如果事物要在 catcode-12-régime 下进行标记化,以便\scantokens
在通常的 catcode-régime 生效时通过重新标记它们产生实际所需的标记集,那么也无需特别考虑反斜杠。但在 catcode-12-régime 下无法扩展事物,因为类别 12 的显式字符标记不可扩展。
我假设您希望根据宏参数和标记来完成操作。
在这种情况下,只有不平衡的括号才需要特殊考虑。
% Compile with a LaTeX-engine where the primitives `\expanded` and
% `\unexpanded` are available.
%
\makeatletter
\newcommand\StartMacro[1]{\newcommand*#1{}}%
\newcommand\BuildMacro[3]{%
\@ifundefined{\expandafter\@gobble\string#2}{\@latex@error{Buffer \string#2\space is undefined!}\@eha}{%
\expanded{\unexpanded{\let#2=\relax#1#2#3}{#2}}%
}%
}%
\newcommand\AppendUnexpandedToMacro[2]{\edef#1{\unexpanded\expandafter{#1\unexpanded{#2}}}}%
\newcommand\AppendCsTokenFromCsNameToMacro[2]{%
\edef#1{\unexpanded\expandafter{#1}\unexpanded\expandafter{\expandafter\unexpanded\expandafter{\csname#2\endcsname}}}%
}%
\newcommand\AppendExpandedToMacro[2]{%
\@ifundefined{\expandafter\@gobble\string#1}{\@latex@error{Buffer \string#1\space is undefined!}\@eha}{%
\let\@@protect\protect
\let\protect\@unexpandable@protect
\afterassignment\restore@protect
\edef#1{\unexpanded\expandafter{#1}\unexpanded\expandafter{\expandafter\unexpanded\expandafter{\expanded{#2}}}}%
}%
}%
\newcommand\AppendLeftBraceToMacro[1]{%
\@ifundefined{\expandafter\@gobble\string#1}{\@latex@error{Buffer \string#1\space is undefined!}\@eha}{%
\edef#1{\unexpanded\expandafter{#1}\unexpanded{{\expandafter\@gobble\string}}}%
}%
}%
\newcommand\AppendRightBraceToMacro[1]{%
\@ifundefined{\expandafter\@gobble\string#1}{\@latex@error{Buffer \string#1\space is undefined!}\@eha}{%
\edef#1{\unexpanded\expandafter{#1}\unexpanded{\expandafter\@gobble\string{}}}%
}%
}%
\newcommand\PrependUnexpandedToMacro[2]{\edef#1{\unexpanded{\unexpanded{#2}}\unexpanded\expandafter{#1}}}%
\newcommand\PrependCsTokenFromCsNameToMacro[2]{%
\edef#1{\unexpanded\expandafter{\expandafter\unexpanded\expandafter{\csname#2\endcsname}}\unexpanded\expandafter{#1}}%
}%
\newcommand\PrependExpandedToMacro[2]{%
\@ifundefined{\expandafter\@gobble\string#1}{\@latex@error{Buffer \string#1\space is undefined!}\@eha}{%
\let\@@protect\protect
\let\protect\@unexpandable@protect
\afterassignment\restore@protect
\edef#1{\unexpanded\expandafter{\expandafter\unexpanded\expandafter{\expanded{#2}}}\unexpanded\expandafter{#1}}%
}%
}%
\newcommand\PrependLeftBraceToMacro[1]{%
\@ifundefined{\expandafter\@gobble\string#1}{\@latex@error{Buffer \string#1\space is undefined!}\@eha}{%
\edef#1{\unexpanded{{\expandafter\@gobble\string}}\unexpanded\expandafter{#1}}%
}%
}%
\newcommand\PrependRightBraceToMacro[1]{%
\@ifundefined{\expandafter\@gobble\string#1}{\@latex@error{Buffer \string#1\space is undefined!}\@eha}{%
\edef#1{\unexpanded{\expandafter\@gobble\string{}}\unexpanded\expandafter{#1}}%
}%
}%
\makeatother
\StartMacro{\foobar}%
\AppendUnexpandedToMacro{\foobar}{This }%
\AppendUnexpandedToMacro{\foobar}{is the 1st argument: }%
\AppendUnexpandedToMacro{\foobar}{#1}%
\AppendUnexpandedToMacro{\foobar}{\\}%
\AppendUnexpandedToMacro{\foobar}{This }%
\AppendUnexpandedToMacro{\foobar}{is the 1st argument again: }%
\AppendUnexpandedToMacro{\foobar}{#}%
\AppendUnexpandedToMacro{\foobar}{1}%
\AppendUnexpandedToMacro{\foobar}{\\}%
\AppendLeftBraceToMacro{\foobar}%
\AppendCsTokenFromCsNameToMacro{\foobar}{LaTeX}%
\AppendUnexpandedToMacro{\foobar}{ is fun!}%
\AppendRightBraceToMacro{\foobar}%
\AppendUnexpandedToMacro{\foobar}{\\}%
\def\tempa{\empty\tempb}
\def\tempb{This }%
\AppendExpandedToMacro{\foobar}{\tempa}%
\def\tempb{is the 2nd argument: }%
\AppendExpandedToMacro{\foobar}{\tempa}%
\def\tempb{##2}%
\AppendExpandedToMacro{\foobar}{\tempa}%
\AppendLeftBraceToMacro{\foobar}%
\AppendLeftBraceToMacro{\foobar}%
\def\tempb{This is in braces.}%
\AppendExpandedToMacro{\foobar}{\tempa}%
\AppendRightBraceToMacro{\foobar}%
\AppendRightBraceToMacro{\foobar}%
\PrependUnexpandedToMacro{\foobar}{ }%
\PrependRightBraceToMacro{\foobar}%
\PrependUnexpandedToMacro{\foobar}{is fun!}%
\PrependExpandedToMacro{\foobar}{ \empty\empty\empty}%
\PrependCsTokenFromCsNameToMacro{\foobar}{LaTeX}%
\PrependLeftBraceToMacro{\foobar}%
% \BuildMacro{<definition-command>}{<stack/command that is to be defined>}{<parameter-text with syntax according to <definition-command>>}%
\BuildMacro{\newcommand*}{\foobar}{[2]}%
% You could also try something like
% \BuildMacro{\def}{\foobar}{#1#2}%
% or
% \BuildMacro{\NewDocumentCommand}{\foobar}{{mm}}%
% \expandafter\show\csname foobar code\endcsname
\show\foobar
\stop
终端和 .log 文件中的输出揭示了以下内容的定义\foobar
:
> \foobar=macro:
#1#2->{\LaTeX is fun!} This is the 1st argument: #1\\This is the 1st argument
again: #1\\{\LaTeX is fun!}\\This is the 2nd argument: #2{{This is in braces.}
}.
这是我期望从上面的代码中得到的结果。
对于每个维护宏定义文本堆栈的宏,您需要提供第一个参数,该参数表示应将哪个堆栈/哪个宏的定义文本应用到其中。
这样,您就可以同时/并行拥有多个宏定义文本堆栈。
\AppendExpandedToMacro
使用/时\PrependExpandedToMacro
,扩展不会延迟到\BuildMacro
执行,而是立即完成。这样,您可以在对\AppendExpandedToMacro
/ 的不同调用之间使用/重新定义临时宏\PrependExpandedToMacro
。
您说的是“使用 LaTeX 时”,因此 LaTeX 2ε 的\protect
机制被考虑在内。