[texhax] A non-interfering \ifmodulo macro in TeX

Ignacy Gawedzki texhax at qult.net
Thu Jul 26 06:38:30 CEST 2007


Hi texhaxers!

I have been lastly playing around with the macro expansion mechanisms in plain
TeX and have come with an interesting solution.

I wanted to write a macro to compute a modulo.  Of course, the best would have
been to be able to use it like this

  \count0=\modulo{\n}{\m}

and then use \count0 or whatever counter that has received the result of the
computation.  Unfortunately, plain TeX does not allow such things, simply
because all macro expansion happens before any assignment (or something like
that).  After some searching on the web (and in texhax's archives), I learned
that it is probably almost that easy to do using e-TeX new functionalities.

But still, I wanted to try as hard as I could to do it in plain TeX.

A possible approach, if one forgets about assigning the result to a counter,
is to define an \ifmodulo macro that would have to be used in the following
way

  \ifmodulo{\n}{\m}{\p}{code for true case}{code for false case}      ,

with the semantics being that the true case gets expanded if \n mod \m equals
\p, otherwise the false case is expanded.  A quick and dirty implementation is
then

  \def\ifmodulo#1#2#3#4#5{%
    \begingroup\count0=#1\divide\count0by#2\multiply\count0by#2
    \multiply\count0by-1\count1=#1\advance\count1by\count0
    \expandafter\endgroup\expandafter\ifnum\the\count1=#3 #4\else #5\fi
  }

the \begingroup and \endgroup protecting the environment from interference of
the use of \count0 and \count1.

Although the syntax of such a macro is nice, it is lacking the power of
classical \if* of plain TeX.  The idea would be to have an \ifmodulo macro
that would be usable in the following way

  \ifmodulo \n by \m < \p ... \else ... \fi    , or
  \ifmodulo \n by \m = \p ... \else ... \fi    , or even
  \ifmodulo \count12 by \count34 > 304 ... \fi

The idea is to have the macro capture the first and second operand and compute
the result that is then fed to \ifnum, along with all the following tokens, so
that the whole construct could be used just as a primitive conditional.  The
main problem I encountered is that unlike the first operand that is delimited
by the "by" characters, the second operand is not delimited by anything known
in advance.  Thus, the macro has to be able to parse a variable length second
argument, accepting tokens following the first one, as long as they are
contiguous digits (not separated by space).  By chance, it happens that in the
case counters like \count123 are used, the rule is the same: get the first
token and all following contiguous digits.

So here comes my code.

  \catcode`@=11
  \def\ifmodulo#1by#2{%
    \begingroup
    \def\parsesign##1{%
      \def\\{\let\stoken= } \\ %
      \ifx-\token\def\next{\parsesignget{##1}}%
      \else\ifx+\token\def\next{\parsesignget{##1}}%
      \else\ifx\stoken\token
        \def\next{\def\p{\parsesignget{##1}{}}\afterassignment\p\let\token= }%
      \else\def\next{\parsefirstget{##1}}\fi\fi\fi\next}%
    \def\parsefirstget##1##2{\def\p{\parsenum{##1##2}{}}\futurelet\token\p}%
    \def\parsesignget##1##2{\def\p{\parsesign{##1##2}}\futurelet\token\p}%
    \def\parsenum##1##2{%
      \ifx0\token\def\next{\parsenumget{##1}{##2}}%
      \else\ifx1\token\def\next{\parsenumget{##1}{##2}}%
      \else\ifx2\token\def\next{\parsenumget{##1}{##2}}%
      \else\ifx3\token\def\next{\parsenumget{##1}{##2}}%
      \else\ifx4\token\def\next{\parsenumget{##1}{##2}}%
      \else\ifx5\token\def\next{\parsenumget{##1}{##2}}%
      \else\ifx6\token\def\next{\parsenumget{##1}{##2}}%
      \else\ifx7\token\def\next{\parsenumget{##1}{##2}}%
      \else\ifx8\token\def\next{\parsenumget{##1}{##2}}%
      \else\ifx9\token\def\next{\parsenumget{##1}{##2}}%
      \else\def\next{\endgroup\begingroup
        \advance\count10by\@ne \ch at ck 0\insc at unt\count
        \allocationnumber=\count10\countdef\firstarg=\allocationnumber
        \advance\count10by\@ne \ch at ck 0\insc at unt\count
        \allocationnumber=\count10\countdef\secondarg=\allocationnumber
        \firstarg=\number#1
        \secondarg=\number##1##2
        \def\cond{\csname ifnum\endcsname}%
        \count0=\firstarg\divide\firstarg by\secondarg
        \multiply\firstarg by-\secondarg\advance\count0by\firstarg
        \expandafter\expandafter\expandafter\endgroup\expandafter\cond\the\count0 }%
      \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\next}%
    \def\parsenumget##1##2##3{\def\p{\parsenum{##1}{##2##3}}\futurelet\token\p}%
    \let\token #2\parsesign{}#2}%
  \catcode`@=10


To clarify, the \parse*get macros are the ones extracting the tokens for the
corresponding \parse* macros.  \parsesign basically extracts all leading + or
- signs and skips spaces until anything not a sign nor a space is read.  Then
\parsefirstget extracts the first such token and calls parsenum, which
basically extracts all the following digits, while keeping in its first
argument, the signs and the first non-space-non-sign token.  Lastly, the \next
macro defined after the last \else of \parsenum does the actual computation,
by allocating two new counters (using an expanded inner \newcount).
Strangely, I had to use the \ifnum indirectly, to avoid it gobbling a \fi.

It works like a charm as far as I tested it.  The only flaw is that if you
have for example

  \newcount\foo
  \foo=123
  \ifmodulo\foo456 by 2 = 0 ...

then \foo456 is accepted and eventually expanded as 123456, which is not what
happens with an \ifnum.  I just don't know how to determine that some token is
in fact a \countdef (or a \dimendef, \skipdef, \toksdef, etc).

What do you think?  Do you have any simpler way of doing it?  

Cheers. =)

Ignacy

-- 
To err is human, to purr feline.


More information about the texhax mailing list