5

This code is supposed to be a for loop that \inlcude 45 files called 1.tex through 45.tex into the main file. However I don't quite understand anything except for the \newcommand so can someone give a somewhat more detailed explanation. I have never seen LaTeX written this way, can someone point to good resources to learn more?

\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/}%
zmkm
  • 379

2 Answers2

4

As @egreg and @UlrichDiez layed out, the usage of \includepatternloop has a potential error: Inside of \include the .tex ending shouldn't be used (newer versions of LaTeX detect this, but to be safe just omit it). So better would've been to use \includepatternloop{1}{45}{}{}{./chapters/} in your case.

Explanation

Ok, lets take a close look at your code.

The first (and simpler macro) to look at is \exchange. Everything it does is taking two arguments and switching there order. It will remove one set of braces if the argument is braced. So both

\exchange{a}{b}
\exchange ab

will result in ba after one step of expansion.

The other macro is your \includepatternloop. It'll take 5 arguments, and in order to keep better track of them I'll name them here:

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

The two arguments <base> and <dir> are a bit redundant and only two distinct arguments for semantic reasons. You could drop one of the two and simply put there contents together into one argument (but we'll look at a simplified version later).

So what does your macro do? First it puts together a file name from the different arguments and includes them

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

is in our named argument version the same as \include{<dir><base><current><post>} so on the first iteration of the call \includepatternloop{1}{45}{}{.tex}{./chapters/} this will use \include{./chapters/1.tex} (<base> is empty).

Next line:

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

This line checks whether we're done. \ifnum is a test comparing two numbers. #1 is the <current> iteration, and this checks whether it is smaller than #2 (in an overly complicated way). There is a pitfall in TeX's number parsing that is it expands things until it finds something that can't be part of a number. To stop this expansion one usually puts a space after it (that space will be gobbled) or (to be on the really safe side) a \relax token.

To understand that line we need to know that \expandafter basically expands the token after the next token then removes itself and the next token does its thing (and \expandafter does so in one step). So \expandafter\exchange\expandafter will first expand the second \expandafter, that one will expand the \number behind the brace, and \number reads in anything TeX could consider a number and leaves the number in arabic digits. So \number#2 is a normalisation. TeX's number parsing is stopped by the closing brace (that can't be a number, right?). So when the \expandafters are done our input looks like \exchange{<stop (normalised)>}{} followed by a space. Now \exchange swaps the two arguments, the second is empty, so this becomes <stop (normalised)> followed by a space. That space terminates TeX's number parsing for the second \ifnum number.

All the \exchange did was allow that the normalisation with \number would be ended by the closing brace and the space at the end of the line is used to terminate \ifnum's number parsing instead of \number's.

Now \ifnum compares <current> with <stop (normalised)>. If the condition (smaller than) is not met, everything up to the \fi two rows down is gobbled and the macro is done. Else the next iteration is called with the line

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

The \exchange will swap the rest of this line with the \fi, so now the \fi ends the \ifnum block and afterwards

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

is evaluated.

We have a few \expandafter again. Those will first expand the \number before \includepatternloop is run. That \number will expand until TeX has a complete number. To do so it expands \numexpr. Now \numexpr is a number parsing macro as well, this one allows to do basic calculations, and will be terminated by either something that can't be part of a number (but semi-ignoring spaces, no need to further dig here) and is no valid operator (it supports +, -, *, / and parenthesis). It will be terminated by \relax (which it will gobble!). So after \number\numexpr<current>+1\relax is expanded once the new contents of the first braced group will be <next> which is the result of <current>+1.

The \expandafters are done and our new input line looks like this:

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

resulting in the next iteration.


Alternative implementation

That macro could be a bit simplified. Two of the arguments can be combined into one <pre> argument. And the number parsing in the \ifnum row is a bit over the top. Instead I'd use a two step approach, one front end macro doing the normalisations, and one macro actually doing the loop. \the is similar to \number, but doesn't work on literal numeric input (so \number5 is fine and would result in 5, \the5 throws an error), but is a tad faster (and for \numexpr it doesn't make a difference). Input normalisations are done with \numexpr instead of \number (this allows to calculate <start> and <stop> on the spot, so \includepatternloop{1+6}{100-50}{./chapters/}{.tex} would be fine). The ;s will stop the \numexpr and serve as argument delimiters because the inner loop is defined with plain syntax \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

The new frontend syntax is \includepatternloop{<start>}{<stop>}{<pre>}{<post>} and to include every file in ./chapters/ that is named A<number>B from 1 to 45 you'd use

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

Remember to not include the file extension .tex for \include.

Ulrich Diez
  • 28,770
Skillmon
  • 60,462
  • The space used for terminating number parsing with #2 might be spurious in the first iteration in case the number in that iteration comes from a \chardef-token or the like TeX--quantity whereafter TeX does not scan for one optional space. That's why in my variant I did the \number-thingie for normalization. – Ulrich Diez Apr 02 '22 at 09:09
  • @UlrichDiez still the \exchange thing is a bit over the top. In that case, this isn't expandable anyways, use \relax :) – Skillmon Apr 02 '22 at 09:45
3

The purpose of the code is to do, for N going from 1 to 45,

\include{./chapters/N.tex}

which is wrong to begin with, because the extension .tex should never be passed with \include. I'll gloss over this aspect.

I don't find it particularly instructive, but here you go. The idea is to do

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

and then do a test on whether #1 is still less than #2. If it is, then

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

is called, where #1+1 denotes the previous value increased by 1.

One might be tempted to do a simplified version, namely

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

but this would be bad, because it would not eat the trailing \fi and this is why the strategy with \exchange is employed.

Let's start from the conditional: it can be simplified to

\ifnum#1<#2

when the second argument to \includepatternloop is an explicit number. The \exchange is used to have #2 expanded (it might be a macro) but to still have a trailing space that avoids the need of \relax (interesting trick).

If the test returns false, nothing more is done, so let's look at the interesting part when the test returns true, say at the first step: we have

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

that becomes

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

The token following < is expanded to look for a number and this eventually results in

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

OK, the test is true, so we get

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

Note that \fi is not yet removed. Now \exchange is performed, which results in

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

and this is where the \fi is removed, because we're left with

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

The expansion of \fi is empty and the work can continue: \expandafter does its job eventually hitting \number so we're left with

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

and the job is restarted.

How can it be done with less tricks? Of course you can use expl3 that needs no \expandafter and no tricks.

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

If I try the above with \includepatternloop{1}{5}{}{.tex}{./chapters/} and

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

in the loop body just to see what would happen, the console would show

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

The main point here is that ##1 in the code refers to the current integer in the loop created by \int_step_inline:nnn.

egreg
  • 1,121,712
  • Oh, I forgot the .tex thingy being wrong... :) – Skillmon Apr 02 '22 at 09:45
  • By the way: I, Ulrich Diez, am the one to be blamed for errors in the code presented in the question, e.g., the extension ".tex" with \include - the code stems from an answer of mine to the question "How to use for loop to input files". There in an edit I switched from \input (primitive) to \include and overlooked the need of removing the filename-extension. – Ulrich Diez Apr 02 '22 at 15:03
  • @UlrichDiez Nice code nonetheless. – egreg Apr 02 '22 at 15:03
  • @egreg Thank you. Maybe, yes. But I could not write nice code if you did not teach me so many things over the years. :-) – Ulrich Diez Apr 02 '22 at 15:05