LaTeX2e 样式的两种方法

LaTeX2e 样式的两种方法

考虑以下示例。

默认情况下\g@addto@macro不会扩展其参数,如果宏值稍后发生变化,则在将宏作为参数传递时可能会产生意外结果。

为了扩展宏参数,我使用了下面显示的解决方法。这种方法有什么缺点吗?有没有更好的方法?

在此示例代码中,\append不扩展其参数。\newappend扩展。 它们分别将参数#1(后跟逗号)附加到宏\slist\newslist

\documentclass[12pt]{article}

\def\slist{}
\def\newslist{}

\makeatletter
\def\append#1
{
 \g@addto@macro\slist{#1,}%
}
\def\newappend#1
{
  \def\tmpmacroval{#1}
  \edef\next{\noexpand\g@addto@macro\noexpand\newslist{\tmpmacroval,}}%
  \next
}
\makeatother

\def\foo{fooval}

\begin{document}
\append{\foo}

\newappend{\foo}

\def\foo{someotherval}

\verb|\slist| is \slist

\verb|\newslist| is \newslist
\end{document}

\append附录:为了简单起见,我省略了这个问题的上下文,但/的参数\newappend基本上是一个标签。这通常只是一串字符,但我开始使用一个变体,它是一个扩展为字符串的单个宏,并附加一个字符串。例如\theletternum:2019.09.30,其中letternum是一个计数器,由 定义\stepcounter{letternum}。在这种情况下,参数需要先扩展,然后才能附加到\slist/ \newslist。我目前认为输入不会变得比这更复杂,但当然很难预见未来。

答案1

首先:正如我多次写过的,TeX 是不是自由形式。两者之间有很大区别

\def\append#1
{
 \g@addto@macro\slist{#1,}%
}

以及你可能想要的代码,即

\def\append#1{%
 \g@addto@macro\slist{#1,}%
}

在您的代码中,参数\append被确定为它后面直到第一个空格标记的任何内容。因此,如果您写

\append{a}Some words

结果是{a}Some将其附加到列表中。

现在来看看你的问题。你期望的输入类型并不十分清楚。如果输入是任何一个文本或者一个扩展为文本的宏,你可以简单地做

\expandafter\append\expandafter{\foo}

如果您想要传递可能包含要扩展的宏的任意文本,那么您需要非常小心。如果此文本包含类似这样的内容,\ref并且引用的对象仍然未知,那么即使您使用,您也注定会失败\protected@edef。代码

\documentclass{article}

\makeatletter
\def\slist{}
\def\append#1{%
 \protected@xdef\slist{\unexpanded\expandafter{\slist}#1,}%
}
\makeatother

\begin{document}

\append{\ref{what}}

\show\slist

\end{document}

将在终端上打印

> \slist=macro:
->\protect \G@refundefinedtrue {\protect \mbox  {\protect \normalfont  \protect
 \bfseries  ??}}\protect \GenericWarning  {               }{LaTeX Warning: Refe
rence `what' on page 1 undefined},.

这或许是你不太愿意看到的。

因此我假设参数\append是文本或单个宏。

\documentclass{article}

\makeatletter
\def\slist{}
\def\append{\@ifstar{\append@expand}{\append@text}}
\def\append@text#1{\g@addto@macro\slist{#1,}}
\def\append@expand#1{\expandafter\append@text\expandafter{#1}}%
\makeatother

\newcommand{\foo}{something}

\begin{document}

\append{a}\append*{\foo}

\show\slist

\end{document}

这将打印预期

> \slist=macro:
->a,something,.

我会谨慎地建议更简单的

\documentclass{article}

\makeatletter
\def\slist{}
\def\append#1{%
  \expandafter\g@addto@macro\expandafter\slist\expandafter{#1,}%
}
\makeatother

\newcommand{\foo}{something}

\begin{document}

\append{a}\append*{\foo}

\show\slist

\end{document}

效果相同。如果您的输入始终是一串字符或扩展为一串字符的单个宏(非技术意义上的“字符串”,即“序列”),则可以使用这种更简单的方法。

答案2

正如 @UlrikeFischer 和 @DavidCarlisle 指出的那样,使用\edef任意材料都可能导致错误。例如,包含的材料就是这种情况\textbf,还有很多其他情况。

LaTeX2e 样式的两种方法

我介绍了两种使用 LaTeX2e 样式的技术,\protected@edef以便\DeclareRobustCommand不会影响用 定义的宏。这样,添加的材料可能包含\textbf

这些技术也不会覆盖\next宏,这总是件好事(好吧,它们会覆盖宏,但完成后会自动恢复它)。还请注意,您的\tmpmacroval宏实际上没什么用。

请注意,尽管我添加了分组,但您的列表仍会得到适当修改,因为\g@addto@macro使用了\xdef执行全局分配的。

第一种技术

\documentclass{article}

\newcommand{\newslist}{}

\makeatletter
\newcommand{\newappend}[1]{%
  \begingroup
  \protected@edef\next{\endgroup\noexpand\g@addto@macro\noexpand\newslist{#1,}}%
  \next
}
\makeatother

\newcommand{\foo}{\textbf{foo}val}

\begin{document}

\newappend{\foo}
\renewcommand{\foo}{someotherval}
\verb|\newslist| is \newslist.

\end{document}

在此处输入图片描述

解释

假设 TeX 刚刚扩展了\newappend{〈argument〉}。这会将以下内容留在输入流的最前面:

\begingroup
\protected@edef\next{\endgroup\noexpand\g@addto@macro\noexpand\newslist{〈argument〉,}}%
\next

执行 后\begingroup,TeX 打开一个新组。然后他看到以下行:

\protected@edef\next{\endgroup\noexpand\g@addto@macro\noexpand\newslist{〈argument〉,}}

\protected@edef展开后,所有生成的标记都已处理完毕,宏\next将在当前组中定义。该定义仅限于此组,并且\next此组结束后,TeX 将自动恢复先前的定义(可能:无)——这是 TeX 组的主要目的。现在,输入流中的下一个标记是\next控制序列标记。由于它是一个宏,TeX 会将其展开,这会导致此\next标记被替换为:

\endgroup\g@addto@macro\newslist{〈expanded argument〉,}

(在 的实现中,这些\noexpand标记已作为 执行的一部分被删除)。因此,输入流中的下一个标记现在是。TeX 执行它(它是不可扩展的,因此它在 TeX 的胃中处理,与宏相反),这将关闭当前组。如前所述,这会取消我们所做的(重新)定义。这是干净的。输入流前面剩下的现在是\edef\protected@edef\endgroup\next

\g@addto@macro\newslist{〈expanded argument〉,}

这正是您想要的。

第二种技术

\documentclass{article}

\newcommand{\newslist}{}

\makeatletter
\newcommand{\newappend}[1]{%
  \begingroup
  \protected@edef\next{\noexpand\g@addto@macro\noexpand\newslist{#1,}}%
  \expandafter
  \endgroup
  \next
}
\makeatother

\newcommand{\foo}{\textbf{foo}val}

\begin{document}

\newappend{\foo}
\renewcommand{\foo}{someotherval}
\verb|\newslist| is \newslist.

\end{document}

与前面的示例输出相同。

示例expl3

expl3有许多工具可以处理列表类材料。我将展示一些示例clist,即逗号分隔的列表。这允许从用户输入轻松直接设置,因为可以在文本中输入逗号。对于更一般的列表,您可以使用seq变量。对于(键,值)对的列表,有prop模块(“属性列表”)。所有这些模块都是用语言构建的expl3,因此在\usepackage{expl3}或之后可用\usepackage{xparse}expl3.无需加载任何其他内容。

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\clist_new:N \g_faheem_clist

\cs_new_protected:Npn \faheem_append:Nn #1#2
  {
    \clist_gput_right:Nn #1 {#2} % 'g' for “global”
  }

% Variant that performs one expansion step on the second argument
\cs_generate_variant:Nn \faheem_append:Nn { No }
% Variant that performs \edef-like expansion on the second argument
\cs_generate_variant:Nn \faheem_append:Nn { Nx } % \edef-like expansion 
% There are also 'e' to perform \expanded-like expansion, 'f' for
% \romannumeral-like expansion, etc.

\NewDocumentCommand \newList { m }
  {
    \clist_new:N #1
  }

% In fact, for serious work, better do:
%
% \cs_new_protected:Npn \faheem_new_list:N #1
%   {
%     \clist_new:N #1
%   }
%
% and make \newList call \faheem_new_list:N or \faheem_new_list:c as I do for
% the \append* functions below (the 'c' variant would allow you to choose
% arbitrary names for the clist variable: you would pass whatever name you
% want to the 'c' variant, normally without a leading slash, otherwise that
% would create a strange beast: \\the_passed_name).

\NewDocumentCommand \clearList { O{\g_faheem_clist} }
  {
    \clist_gclear:N #1          % 'g' for “global”
  }

\NewDocumentCommand \appendNoExp { O{\g_faheem_clist} m }
  {
    \faheem_append:Nn #1 {#2}
  }

\NewDocumentCommand \appendOneExp { O{\g_faheem_clist} m }
  {
    \faheem_append:No #1 {#2}
  }

\NewDocumentCommand \appendFullExp { O{\g_faheem_clist} m }
  {
    \faheem_append:Nx #1 {#2}
  }

% This requires a recent LaTeX release (2020 or later?)
\NewDocumentCommand \appendTextExpand { O{\g_faheem_clist} m }
  {
    % \text_expand:n works inside an 'x'-type argument because it is fully
    % expandable, as indicated by the star in the margin in interface3.pdf.
    \faheem_append:Nx #1 { \text_expand:n {#2} }
  }

\NewDocumentCommand \useList { O{\g_faheem_clist} m }
  {
    \clist_use:Nn #1 {#2}
  }
\ExplSyntaxOff

\newcommand*{\showres}[1]{\makebox[0pt][r]{#1.~}\ignorespaces}

\begin{document}

\newList{\somelist}
\appendNoExp{first in default list}
\appendNoExp[\somelist]{first in somelist}

\def\foobar{element with \textbf{non-expandable}\_material}
\def\baz{quux}
\appendOneExp{\foobar}
\appendOneExp[\somelist]{\baz}

\showres{1}
Default list: \useList{, }\par
\verb|\somelist|: \useList[\somelist]{, }

\def\fooleveli{\foolevelii}
\def\foolevelii{\fooleveliii}
\def\fooleveliii{other element}
\appendFullExp{\fooleveli}

\showres{2}
Default list: \useList{, }

\renewcommand{\fooleveliii}{\textbf{non-expandable} \emph{stuff}}
% Expand again \fooleveli, \foolevelii and (the new) \fooleveliii, this time
% with a method that won't break with LaTeX non-expandable commands defined
% with \DeclareRobustCommand.
\appendTextExpand{\fooleveli}

\showres{3}
Default list: \useList{, }.

\def\foobar{overwrite}
\def\fooleveli{overwrite}

\showres{4}
Default list: \useList{, }.\par
\verb|\somelist|: \useList[\somelist]{, }.

\showres{5}
With X as separator: \useList{X}.

\clearList                      % clear the default list

\showres{6}
Y\useList{X}Y

\end{document}

解释3

注意:为了扩展文本材料,避免由于不可扩展、不受引擎保护的命令(如)而导致错误,\textbf最近的expl3命令是(见\text_expand:n\protected@edefJoseph Wright 的博客界面3.pdf)。

答案3

  1. 您的代码会产生大量不需要的空格标记:
    因为\endlinechar在每行的右端添加了一个回车符(类别代码为 5(行尾))。在生成诸如and和(如= )之类的字符标记后,(La)TeX 的读取装置处于状态 m(行中),因此后面的类别代码 5 回车符和后面的空格字符都将被标记为空格标记。空格标记反过来会在(受限)水平模式下产生可见的水平粘连。{1(begin group)}2(end group)112(other)#1#6(parameter)112(other)

  2. \edef而不是\protected@edef可能会扩大不应进一步扩大的事物。

  3. 如果的参数\newappend包含哈希值( ),则展开#时两个连续的哈希值将合并为一个哈希值。展开时也会发生同样的情况。反过来会使哈希值翻倍。换句话说:将连续哈希值的数量除以 2。\tmpmacroval\next\g@addto@macro\newappend

纠正 1 和 2 可能会将您的代码变成如下形式:

\documentclass[12pt]{article}

\def\slist{}
\def\newslist{}

\makeatletter
\def\append#1{\g@addto@macro\slist{#1,}}
\def\newappend#1{%%%%
  \def\tmpmacroval{#1}%%%%
  \protected@edef\next{\noexpand\g@addto@macro\noexpand\newslist{\tmpmacroval,}}%
  \next
}%%%
\makeatother

\def\foo{fooval}

\begin{document}
\append{\foo}

\newappend{\foo}

\def\foo{someotherval}

\verb|\slist| is \texttt{\meaning\slist}

\verb|\newslist| is \texttt{\meaning\newslist}

% Here are sequences with four consecutive hashes:    
\def\foo{\def\noexpand\bar####1{Arg of \string\bar\space is ####1}}

\append{\foo}

\newappend{\foo}

\verb|\slist| is \texttt{\meaning\slist}

\verb|\newslist| is \texttt{\meaning\newslist}

\end{document}

在此处输入图片描述

当你查看结果输出的最后一行时,你会注意到源中的连续哈希值####减半,因此\newslist仅包含两个哈希值的序列。(扩展时\newslist,哈希值的数量再次减半,因此扩展\newslist会产生标记:
fooval,\def\bar#1{Arg of \bar is #1},;第二个\bar被字符串化。)

\expanded使用-primitive 和-primitive都可用的 TeX 引擎\unexpanded,您还可以纠正问题 3(扩展期间两个连续哈希值折叠为单个哈希值)。我可能会做这样的事情:

\documentclass{article}

\newcommand\exchange[2]{#2#1}

\makeatletter
\newcommand\MyExpandedG@addto@macro[2]{%
  % The following three lines form the \protected@edef-mechanism:
  \let\@@protect\protect
  \let\protect\@unexpandable@protect
  \afterassignment\restore@protect
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  \xdef#1{%
    % \unexpanded inside \edef yields doubling of amounts of hashes.
    \unexpanded\expandafter{%
      \romannumeral0%
      % \romannumeral0-\exchange-trickery to get the things that are to be appended
      % expanded fully via \expanded, and to get the macro where things are to be
      % appended expanded once; during expansion - no matter if expansion
      % is only once or fully - amounts of hashes get halved:
      \expandafter\exchange\expandafter{\expanded{#2}}%
                                       {\exchange{ }\expandafter#1}%
    }%
  }%
}%
\newcommand\newappend[1]{\MyExpandedG@addto@macro\newslist{#1,}}%
\newcommand\append[1]{\g@addto@macro\slist{#1,}}%
\makeatother

\newcommand\slist{}
\newcommand\newslist{}

\newcommand\foo{fooval}

\begin{document}
\append{\foo}

\newappend{\foo}

\def\foo{someotherval}

\verb|\slist| is \texttt{\meaning\slist}

\verb|\newslist| is \texttt{\meaning\newslist}

% Here are sequences with two consecutive hashes:
\def\foo{\def\noexpand\bar##1{Arg of \string\bar\space is ##1}}

\append{\foo}

\newappend{\foo}

\verb|\slist| is \texttt{\meaning\slist}

\verb|\newslist| is \texttt{\meaning\newslist}

\end{document}

在此处输入图片描述

当你查看结果输出的最后一行时,你会注意到 summa summarum 的数量 源中的连续哈希值##不会减半,因此\newslist包含具有相同数量哈希值的序列。

相关内容