解释

解释

此代码应该是一个 for 循环,将\inlcude45 个文件(从 1.tex 到 45.tex)放入主文件中。但是,除了 之外,我什么都不太明白,所以\newcommand有人能给出更详细的解释吗?我从未见过以这种方式编写的 LaTeX,有人可以指出一些好的资源来了解更多信息吗?

\newcommand\exchange[2]{#2#1}%
\newcommand\includepatternloop[5]{%
  \include{#5#3#1#4}%
  \ifnum#1<\expandafter\exchange\expandafter{\number#2}{} %
     \exchange{\expandafter\includepatternloop\expandafter{\number\numexpr#1+1\relax}{#2}{#3}{#4}{#5}}%
  \fi
}%
\includepatternloop{1}{45}{}{.tex}{./chapters/}%

答案1

作为 @egreg@UlrichDiez 布局时,使用存在潜在错误:结尾\includepatternloop内 不应使用(较新版本的 LaTeX 会检测到这一点,但为了安全起见,请忽略它)。因此,在您的案例中,最好使用 。\include.tex\includepatternloop{1}{45}{}{}{./chapters/}

解释

好的,让我们仔细看看你的代码。

首先要看的(也是比较简单的)宏是\exchange。它所做的就是接受两个参数并切换它们的顺序。如果参数带有括号,它将删除一组括号。因此两者

\exchange{a}{b}
\exchange ab

ba经过一步扩展后将得到

另一个宏是你的\includepatternloop。它将接受 5 个参数,为了更好地跟踪它们,我将在这里命名它们:

  1. <current>
  2. <stop>
  3. <base>
  4. <post>
  5. <dir>

这两个参数<base><dir>有点多余,而且出于语义原因,它们只是两个不同的参数。您可以删除其中一个,然后简单地将它们的内容合并为一个参数(但我们稍后会查看简化版本)。

那么你的宏会做什么呢?首先,它会根据不同的参数组合出一个文件名,并将它们包含在内

  \include{#5#3#1#4}%

在我们的命名参数版本中与此 \include{<dir><base><current><post>}调用的第一次迭代中将 使用的相同 (为空)。\includepatternloop{1}{45}{}{.tex}{./chapters/}\include{./chapters/1.tex}<base>

下一行:

  \ifnum#1<\expandafter\exchange\expandafter{\number#2}{} %

这行代码检查我们是否完成了。\ifnum是比较两个数字的测试。 #1<current>迭代,这检查它是否小于 #2(以一种过于复杂的方式)。 TeX 的数字解析有一个缺陷,即它会扩展事物,直到找到不能成为数字一部分的东西。 要停止这种扩展,通常在它后面放一个空格(该空格将被吞噬)或(为了真正安全起见)一个\relax标记。

为了理解这一行,我们需要知道,这\expandafter基本上扩展了 token然后下一个标记将删除自身,然后下一个标记执行其操作(并且\expandafter在一个步骤中完成)。因此 \expandafter\exchange\expandafter将首先扩展第二个\expandafter,该扩展将扩展\number括号后面的,并\number读入 TeX 认为是数字的任何内容并将数字保留为阿拉伯数字。所以这 \number#2是一个规范化。TeX 的数字解析被右括号停止(不可能是数字,对吗?)。所以当 s\expandafter完成后,我们的输入看起来像\exchange{<stop (normalised)>}{}后面跟着一个空格。现在 \exchange交换两个参数,第二个是空的,所以它后面<stop (normalised)>跟着一个空格。该空格终止 TeX 对第二个数字的数字解析\ifnum

所做的一切\exchange就是允许规范化以\number右括号结束,并且行末的空格用于终止的\ifnum数字解析而不是\number

现在与\ifnum进行比较。如果不满足条件(小于),则吞噬向下两行之前的所有内容,宏完成。否则,使用以下行调用下一次迭代<current><stop (normalised)>\fi

    \exchange{\expandafter\includepatternloop\expandafter{\number\numexpr#1+1\relax}{#2}{#3}{#4}{#5}}%

\exchange将此行的其余部分与 交换\fi,因此现在\fi 结束了该\ifnum块,之后

\expandafter\includepatternloop\expandafter{\number\numexpr#1+1\relax}{#2}{#3}{#4}{#5}

被评估。

我们又有几个\expandafter。它们将首先扩展运行\number之前的 。这将扩展直到 TeX 有一个完整的数字。为此,它会扩展。现在也是一个数字解析宏,这个允许进行基本计算,并且将被某些不能成为数字一部分的东西(但半忽略空格,不需要在这里进一步挖掘)并且不是有效运算符(它支持、、和括号)终止。它将 被终止(它会吞噬!)。所以之后会扩展一次,第一个括号组的新内容将是,它是的结果 。\includepatternloop\number\numexpr\numexpr+-*/\relax\number\numexpr<current>+1\relax<next><current>+1

s\expandafter已完成,我们的新输入行如下所示:

\includepatternloop{<next>}{<stop>}{<base>}{<post>}{<dir>}

导致下一次迭代。


替代实施方案

该宏可以稍微简化一些。两个参数可以合并为一个<pre>参数。并且行中的数字解析\ifnum有点过分。相反,我会使用两步方法,一个前端宏进行规范化,一个宏实际进行循环。\the类似于 \number,但不适用于文字数字输入(因此\number5可以,结果为 5,会\the5引发错误),但速度稍微快一些(并且对于 \numexpr它没有区别)。输入规范化是用 \numexpr而不是 完成的\number(这允许 当场计算<start>和,因此可以)。s将停止并用作参数分隔符,因为内部循环是用普通语法定义的。<stop>\includepatternloop{1+6}{100-50}{./chapters/}{.tex};\numexpr\def

\makeatletter
\newcommand\includepatternloop[2]% <start> and <stop> are grabbed, rest curried
  {%
    \expandafter\@includepatternloop\the\numexpr#1\expandafter;\the\numexpr#2;
  }
\protected\def\@includepatternloop#1;#2;#3#4% <- will grab 2 arguments right delimited by `;` and 2 normal ones
  {%
    \include{#3#1#4}%
    \ifnum#1<#2 % <- space after #2 terminates number parsing
      \exchange{\expandafter\@includepatternloop\the\numexpr#1+1;#2;{#3}{#4}}%
    \fi
  }
\makeatother

新的前端语法是\includepatternloop{<start>}{<stop>}{<pre>}{<post>} ,要包含从 1 到 45./chapters/的每个文件A<number>B,你需要使用

\includepatternloop{1}{45}{./chapters/A}{B}

请记住不要包含文件扩展.tex\include

答案2

代码的目的是为了N从 1 到 45,

\include{./chapters/N.tex}

错误的首先,因为扩展.tex应该绝不传递\include。我将略过这一方面。

我觉得这没什么指导意义,但还是给你。这个想法是

\include{#5#3#1#4}

然后测试是否#1仍然小于#2。如果是,那么

\includepatternloop{#1+1}{#2}{#3}{#4}{#5]

被称为,其中#1+1表示先前的值增加了 1。

人们可能会尝试做一个简化版本,即

\newcommand\includepatternloop[5]{%
  \include{#5#3#1#4}%
  \ifnum#1<#2
    \expandafter\includepatternloop\expandafter{\the\numexpr#1+1\relax}{#2}{#3}{#4}{#5}
  \fi
}

但这样会很糟糕,因为它不会吃掉尾随的,这就是为什么采用\fi策略的原因。\exchange

让我们从条件开始:它可以简化为

\ifnum#1<#2

当的第二个参数\includepatternloop是明确的数字时。 用于\exchange展开#2(它可能是一个宏),但仍有一个尾随空格,从而避免了需要\relax(有趣的技巧)。

如果测试返回 false,则不会执行任何其他操作,因此让我们看看测试返回 true 时的有趣部分,比如在第一步:我们有

\includepatternloop{1}{45}{}{.tex}{./chapters/}

即成为

\include{./chapters/1.tex}
\ifnum 1<\expandafter\exchange\expandafter{\number#2}{} %
  \exchange{\expandafter\includepatternloop\expandafter{\number\numexpr1+1\relax}{45}{}{.tex}{./chapters/}}%
\fi

后面的 token<被扩展来寻找数字,最终导致

\ifnum 1<45 %
  \exchange{\expandafter\includepatternloop\expandafter{\number\numexpr1+1\relax}{45}{}{.tex}{./chapters/}}%
\fi

好的,测试是正确的,所以我们得到

  \exchange{\expandafter\includepatternloop\expandafter{\number\numexpr1+1\relax}{45}{}{.tex}{./chapters/}}%
\fi

请注意,\fi尚未删除。现在\exchange执行,结果为

  \exchange{\expandafter\includepatternloop\expandafter{\number\numexpr1+1\relax}{45}{}{.tex}{./chapters/}}%
\fi

这就是\fi被删除的地方,因为我们剩下

\fi
\expandafter\includepatternloop\expandafter{\number\numexpr1+1\relax}{45}{}{.tex}{./chapters/}}

的扩展\fi为空,工作可以继续:\expandafter它的工作最终完成了,\number所以我们剩下

\includepatternloop{2}{45}{}{.tex}{./chapters/}

并重新启动作业。

怎么能用更少的技巧来做呢?当然你可以用expl3不需要\expandafter和没有技巧的。

\ExplSyntaxOn
\NewDocumentCommand{\includepatternloop}{mmmmm}
 {
  \int_step_inline:nnn { #1 } { #2 }
   {
    \include { #5 #3 ##1 #4 }
   }
 }
\ExplSyntaxOff

如果我尝试上述\includepatternloop{1}{5}{}{.tex}{./chapters/}操作

\typeout{\string\include{#5 #3 ##1 #4}}

在循环体中只是为了看看会发生什么,控制台会显示

\include{./chapters/1.tex}
\include{./chapters/2.tex}
\include{./chapters/3.tex}
\include{./chapters/4.tex}
\include{./chapters/5.tex}

这里的重点是##1代码中的 指的是 创建的循环中的当前整数\int_step_inline:nnn

相关内容