Understanding Implicit Delimiters/Terminators

I made you an expandable version of \rnumexpr which does not require a delimiter and will stop on the first unexpandable, \numexpr-invalid token. It tries to emulate the behaviour of \numexpr up to some extent, and ignore brace pairs.

The thing about \numexpr, which everyone already commented, is that it's a primitive, so its rules are different than the rules that govern the realms of men dealing with simple macros. Unfortunately some things simply can't be done without primitive support.

You want expandability, so right off the bat you cannot have lookahead (with \futurelet). \futurelet would allow you to look at the next token and decide what to do with it. Expandability restricts you to grab tokens as arguments and pass them around in funny ways, and grabbing stuff as argument (with a open-ended command like \rnumexpr) means that:

  1. {\rnumexpr 1+1} is impossible because TeX will yell at you when it grabs }
  2. \rnumexpr 1+1 ⟨something else⟩ will eventually grab ⟨something else⟩, whatever it happens to be, determine if it has to be expanded or not, and deal with it accordingly.

With a delimited argument you could use something like expl3's \__tl_act:NNNnn to expandably loop through a token list and act on an item differently, depending if it is a space, a grouped token list, or another single token, which would make the task at hand much easier.


First let me point some things about your code. In your test for emptiness \expandafter\ifx\relax#2\relax, the \expandafter skips \ifx and expands \relax, so it isn't of much use and can be removed. Also this test might print undesired characters should the the input contain a \relax. Of course you are in the middle of a \numexpr, so this is just nitpicking.

Also your conditional doesn't end at each iteration of \@rnumexpr, but only at the very end of the \numexpr. This will, for large expressions (and with large I mean enough copies of +1 to get a result larger than 1500–very large) use up all of TeX's input stack. And finally, your definition does not work for \rnumexpr{+1{+1}}+1\rrelax and other (far too weird to be considered normal input) combinations of braces.


I defined a slow, certainly-suboptimal, probably-too-convoluted, most-likely-buggy, ⟨insert-other-qualifiers-here⟩, emulation of \numexpr. Mostly the behaviour is the same (to the extent of the tests I done), except that it ignores braces.

It starts scanning the input, token by token, then deciding what to do with each. It tries to expand tokens as it goes, and stops on the first unexpandable, \numexpr-invalid token. If that token is \relax, it is consumed, like \numexpr does, so the behaviour is very similar in this aspect.

The major difference is that, as it grabs tokens as undelimited arguments, spaces are ignored, so while the result of \the\numexpr 1+1 1 is 21 (2 appended with an 1), the result of \the\rnumexpr 1+1 1 is 12 (1+11), so it needs a “harder” ending token than \numexpr. This can be avoided by either using a \relax: \the\rnumexpr 1+1\relax 1 to end the \rnumexpr or by using \obeyspaces so that the spaces are sent to the underlying \numexpr which will then do the right thing.

here it is:

\documentclass{article}

\makeatletter
\def\rnumexpr{\romannumeral-`0\rn@collect{}}
\long\def\rn@collect#1#2{%
  \rn@ifsinglechar{#2}%
    {%
      \rn@ifvalid@numexpr@token{#2}%
        {\rn@collect{#1#2}}%
        {\rn@finish{#1}{#2}}%
    }%
    {%
      \rn@ifsingletoken{#2}%
        {%
          \rn@ifrelax{#2}%
            {\rn@finish{#1}{}}%
            {\rn@expand@after{#1}#2}%
        }%
        {\rn@collect{#1}#2}%
    }%
}
\def\rn@qrtail{\rn@qrtail}
\def\rn@expand@after#1{%
  \rn@@expand@after{\expandafter\rnumexpr}#1\rn@qrtail\rn@qrstop}
\def\rn@@expand@after#1#2{%
  \ifx#2\rn@qrtail
    \rn@finish@expandafter{#1}%
  \else
    \expandafter\rn@@expand@after
  \fi
    {#1\expandafter#2}%
}
\def\rn@finish@expandafter#1#2\fi#3\rn@qrstop{%
  \fi#1\romannumeral-`0\rn@check@unexpandable}
\long\def\rn@check@unexpandable#1{%
  \expandafter\rn@@check@unexpandable\expandafter#1%
    \romannumeral-`0#1}
\long\def\rn@@check@unexpandable#1#2{%
  \ifx#1#2%
    \expandafter\rn@unexpandable
  \else
    \expandafter\rn@expandable
  \fi
  {#1}{#2}}
\long\def\rn@expandable#1#2{#2}
\long\def\rn@unexpandable#1#2{\relax#2}
\long\def\rn@finish#1#2{%
  \numexpr#1\relax#2}
\long\def\rn@ifrelax#1{%
  \ifx#1\relax
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\def\rn@ifvalid@numexpr@token#1{%
  \expandafter\rn@@ifvalid@numexpr@token\expandafter{\number`#1}}
\def\rn@@ifvalid@numexpr@token#1{%
  \if
    \ifnum58>#1    1\else x\fi
    \ifnum   #1>39 1\else y\fi
    \ifnum
      \ifnum#1=44 1\else 0\fi
      \ifnum#1=46 1\else 0\fi
      =0
      \rn@true
    \else
      \rn@false
    \fi
  \else
    \ifnum#1=32
      \rn@true
    \else
      \rn@false
    \fi
  \fi
}
\def\rn@true{\expandafter\@firstoftwo\romannumeral-`0}
\def\rn@false{\expandafter\@secondoftwo\romannumeral-`0}
\edef\rn@catofamp{\the\catcode`\&}
\catcode`\&=11
\long\def\rn@gobble#1&{%
  \romannumeral-`0\rn@@gobble#1\rn@qrtail &}
\long\def\rn@@gobble#1#2&{%
  \ifx\rn@qrtail#1%
    \expandafter\rn@@gobble@end
  \else
    \expandafter\rn@de@tail
  \fi#2}
\def\rn@@gobble@end{ }
\long\def\rn@de@tail#1\rn@qrtail{ #1}
\long\def\rn@ifsinglechar#1{%
  \rn@ifempty{#1}%
    {\@secondoftwo}%
    {%
      \if\relax\expandafter\rn@gobble\detokenize{#1}&\relax
        \expandafter\@firstoftwo
      \else
        \expandafter\@secondoftwo
      \fi
    }%
}
\long\def\rn@ifsingletoken#1{%
  \rn@ifempty{#1}%
    {\@secondoftwo}%
    {%
      \rn@if@head@is@group{#1}%
        {\@secondoftwo}%
        {%
          \if\relax\detokenize\expandafter\expandafter
              \expandafter{\rn@gobble#1&}\relax
            \expandafter\@firstoftwo
          \else
            \expandafter\@secondoftwo
          \fi
        }%
    }%
}
\long\def\rn@if@head@is@group#1{%
  \ifcat\expandafter\@gobble\expandafter{\expandafter{\string#1?}}**%
    \expandafter\@secondoftwo
  \else
    \expandafter\@firstoftwo
  \fi
}

\catcode`\&=\rn@catofamp
\long\def\rn@ifempty#1{%
  \if\relax\detokenize{#1}\relax
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\makeatother

\begin{document}

\def\twop{+1+1}

\the\numexpr 1+1 1

\the\rnumexpr 1+1 1

\the\numexpr\twop+1+1+1
\the\numexpr\twop+1+1+1
\the\numexpr\twop+1+1+1
\the\numexpr\twop+1+1+1+1+1
\the\numexpr\twop+1+1+1+1+1

\the\numexpr 1+1
\the\numexpr 1+1\twop

\def\twop{{+1+1}}

\the\rnumexpr\twop+1{+1+1}\relax
\the\rnumexpr\twop{+1+1+1}\relax
\the\rnumexpr\twop{+1{+1+1}}\relax
\the\rnumexpr\twop{+1{+1+1}}+1+1\relax
\the\rnumexpr\twop{+1{+1+1{}}}+1+1\relax

\the\rnumexpr 1+1
\the\rnumexpr 1+1\twop

Expandable! \edef\z{\the\rnumexpr+1+1{+1+1}\relax}\texttt{\meaning\z}

\the\rnumexpr1{{+1}+1{+1}}+1\relax

\the\rnumexpr{1{+1}}+1\relax

{\the\numexpr1+1+1}

Groups everywhere:
\the\rnumexpr{+1{+1{+1{+1{+1{+1{+1{+1{+1{+1}}}}}}}}}}+1,
\the\rnumexpr{+1{{{{{{{{+1}+1}+1}+1}+1}+1}+1}+1}+1}+1,
\the\rnumexpr{+1{{{{{{{{+1}}}}}}}}}+1,
\the\rnumexpr{{{{{{{{{{{{{{{{{{{{{{{{{{+1}}}}}}}}}}}}}}}}}}}}}}}}}}

No leftover:
\detokenize\expandafter{\the\rnumexpr{+1{{{{{{{{+1}}}}}}}}}+1\relax}

% {\the\rnumexpr1+1+1} STILL WON'T WORK :(

\end{document}

enter image description here

The macro could be much faster if the expression were evaluated with \the\numexpr0 beforehand, instead of grabbing every single token and evaluating them only at the bitter end. However this would spoil “stability” (if you can call it that) of the macro because at each evaluation (as many as there are groups), a \relax would be consumed, so to properly terminate the macro you would need to resort to things like \the\rnumexpr1{+1{+1{+1}}}\relax\relax\relax\relax, so I opted out of this possibility.


The input for \numexpr ends when something (unexpandable) that can't appear in a \numexpr is found. Note that \numexpr triggers expansion until the input terminates as defined before.

If the token that signalled the end of the integer expression is \relax, it is removed altogether; thus it won't appear if you say

\edef\test{\the\numexpr1+1\relax}

which would expand to 2.

Braces are not allowed in integer expression, unless they're used for delimiting arguments to macros that are expanded as the integer expression is scanned. So

\def\addition#1#2{#1+#2}
\numexpr\addition{1}{2}\relax

will evaluate to 3. But \numexpr 1+{1+1}\relax is illegal, because the { stops the scanning and the operand for the first + is missing.

You can use ( and ) to delimit subexpressions to be evaluated with the usual precedence rules: \numexpr2*(1+3)\relax evaluates to 8.