此代码应该是一个 for 循环,将\inlcude
45 个文件(从 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 个参数,为了更好地跟踪它们,我将在这里命名它们:
<current>
<stop>
<base>
<post>
<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
。