是否存在用于以编程方式(增量)构建宏的包?

是否存在用于以编程方式(增量)构建宏的包?

当使用 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已定义。

(顺便说一句,我意识到这两个例子都可以通过其他方式实现。为了简洁起见,我把例子写得简单了。我正在考虑的实际应用程序在许多不同的子程序、循环、条件等中添加了不同的“片段”,而且要复杂得多。)

诸如定义受保护/全局/等宏的能力、定义带有可选参数的宏的能力、用于将常用代码片段附加到宏的更多样化的“指令集”等功能都很好,但并非绝对必要。

我的问题是:

  1. 在 LaTeX 中可以实现这样的程序化宏构造实用程序吗(遗憾的是没有 LuaTeX)?

  2. 如果是,那么是否有一个软件包可以提供此功能或类似的功能?

  3. 如果没有具有此功能的软件包,那么实现此功能需要多大的努力?

答案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机制被考虑在内。

相关内容