5

I wrote the following macro that prints a range of numbers separated by commas:

\newcount\tmpnum

\def\range#1#2#3{\tmpnum=#2 \loop\the\tmpnum\advance\tmpnum by #1 \ifnum\tmpnum<#3,\repeat, }

For instance, \range{2}{3}{10} expands to 3,5,7,9,.

I'd like to store this expansion into the macro \therange. What I tried is:

\edef\therange{\range{2}{3}{10}}
% Should be the same as
% \def\therange{3,5,7,9,}

but I get an error message. Why? And how to achieve my goal?

User
  • 2,530
  • 1
  • 14
  • 25
  • 2
    With etex extensions you could do something like \def\range#1#2#3{\ifnum#2>#3\else#2\ifnum\numexpr#2+#1>#3\else, \fi\range{#1}{\the\numexpr#2+#1}{#3}\fi}, assuming . Are you working with Knuth's TeX? –  May 15 '21 at 08:04
  • @JairoA.delRio Yes, etex is available, since I'm using luatex. Yes, a recursive solution like yours gives no problem. But why doesn't the solution with \loop work as well? – User May 15 '21 at 11:58

4 Answers4

4
\def\range#1#2#3{\altrange{#2}{#3}{#1}}
\def\altrange#1#2#3{\ifnum #1<\numexpr 1+#2\relax#1,\expandafter
  \altrange\expandafter{\the\numexpr#1+#3\relax}{#2}{#3}\fi}

\edef\therange{\range{2}{3}{10}}

\edef\z{\range{3}{3}{15}}

The range is \therange\ while z is \z

\bye

enter image description here

If one wishes to quibble over the fact that I build up the stack with unresolved \fis, until the end (which could only become a factor for very large lists), then one can play the "Free-\fi-Fo'-Fun" game that I learned from David (Trying to eliminate stack overflow during recursion (Alphabetic Bubble Sorter)):

\def\range#1#2#3{\ifx11\altrange{#2}{#3}{#1}\fi}
\def\altrange#1#2#3\fi{\fi\ifnum #1<\numexpr 1+#2\relax#1,\expandafter
  \altrange\expandafter{\the\numexpr#1+#3\relax}{#2}#3\fi}

\edef\therange{\range{2}{3}{10}}

\edef\z{\range{3}{3}{15}}

The range is \therange\ while z is \z

\bye

3

The \edef command doesn't perform assignments and your loop is full of them.

With luatex you can use expl3.

\input expl3-generic

\ExplSyntaxOn

\cs_new:Npn \range #1 #2 #3 {% #1 is the step, #2 the starting point, #3 the upper bound #2 \int_step_function:nnnN { #2 + #1 } { #1 } { #3 } \user_addtorange:n } \cs_new:Nn \user_addtorange:n { , #1 }

\ExplSyntaxOff

\range{2}{3}{10}

\edef\therange{\range{2}{3}{10}}

{\tt\meaning\therange}

\bye

enter image description here

The function \int_step_function:nnnN has the syntax

\int_step_function:nnnN { <start> } { <step> } { <end> } <function>

where <function> should be a one argument function (macro, in plain TeX lingo) which will be passed all the integers that result by looping in the obvious way. Only integers that don't exceed <end> are passed. The function will expand to ,<current integer>. The start is added beforehand, so we have no problem with spurious commas.

A version without expl3, just for fun.

\def\range#1#2#3{% #1 = step, #2 = start, #3 = upper bound
  \betterrange{#2}{#3}{#1}%
}
\def\betterrange#1#2#3{% #1 = start, #2 = upper bound, #3 = step
  #1%
  \ifnum\numexpr#1+#3>\numexpr#2\relax
    \expandafter\gobble
  \else
    \expandafter\firstofone
  \fi
  {\expandafter,\expandafter\betterrange\expandafter{\the\numexpr#1+#3}{#2}{#3}}%
}
\def\gobble#1{}
\def\firstofone#1{#1}

\range{2}{3}{10}

\edef\therange{\range{2}{3}{10000}}

\show\therange

\bye

egreg
  • 1,121,712
  • Thank you for both solutions. The version without expl3 cannot exceed 1000 elements, though. I am curious how the expl3 version converts into TeX primitives. How is the \range expl3 macro defined in terms of TeX primitives? – User May 17 '21 at 18:57
  • @User I changed the code so that it uses correctly tail recursion. You can try \tracingmacros=1, in order to see how \range is defined with expl3, but beware that the trace will be very long. – egreg May 17 '21 at 19:43
3

Other answers have shown ways to do this, I'd like to cover why your approach fails.

Only some TeX primitives work purely by expansion - that is to say that they can achieve their outcome inside an \edef, \message or similar. In particular, nothing that

  • Performs an assignment
  • Does any typesetting

works by expansion. The definition of \loop in plain TeX uses an assignment, and so cannot be used in such a context. There are ways do set up loops without assignment, as shown in other answers.

I note you are using LuaTeX: it's important to note that assignment at the Lua level is permitted, and so the 'rules are different' if you write code in Lua.

Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
  • For LaTeX users, this type of insight is something that cannot be expected of user, but as you have chosen to use plain TeX, it's something you do have to be familiar with. – Joseph Wright May 17 '21 at 10:27
2

This is typical question for expandable loop. Various solutions were shown here. I show another solution using expandable \fornumstep from OpTeX (luaTeX + enhanced plain TeX):

\def\range#1#2#3{\fornumstep #1: #2..#3 \do{##1,}}
%test:
\edef\therange{\range{2}{3}{10}}
\meaning\therange
\bye
wipet
  • 74,238
  • This is actually where the question originated. I was trying to see if I could define \fornumstep by using a \foreach over the generated \range of numbers – User May 17 '21 at 19:01