Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

Problem

I need to define a command that grabs all numerical digits following it and leaves the rest alone. Eventually I came up with this:

First Solution

\RequirePackage{xstring} % for \IfInteger

\newcommand*{\num}{%
    \def\@tmpnum{}%
    \@num%
}

\newcommand*{\@num}[1]{%
    \IfInteger{#1}{%
        \edef\@tmpnum{\@tmpnum#1}\@num%
    }{%
        [I saw the number \@tmpnum!]#1%
    }%
}

The \@num macro scans ahead by recursively calling itself. Every digit it encounters is appended to \@tmpnum. The first non-digit it sees ends the sequence and is put back.

It works as intended in simple cases:

\num1234...

outputs:

[I saw the number 1234!]...

How to Handle \par - Second Solution

It soon occurred to me that \IfInteger can't handle a \par, so... second solution:

\newcommand{\@num}[1]{%
    \def\@end{%
        [I saw the number \@tmpnum!]#1%
    }%
    \ifdefequal{#1}{\par}\@end{%
        \IfInteger{#1}{%
            \edef\@tmpnum{\@tmpnum#1}\@num%
        }\@end%
    }%
}

How to Handle a Closing Brace?

This one I can't figure out. What if I want to accept something like this:

\emph{\num42}

Whatever I try, I get some error about having too many closing braces. I guess I'm putting it back when it was already scanned. But that doesn't really help me.

  • Can I test for a closing brace? Is there some other way to handle this?
  • Are other such problems waiting for me?
share|improve this question
1  
What is the exact specification of the number that the macro should grab? It is a number with explicit digits and the next non-digit stops the number? Or is the number anything that TeX accepts as number? What about the case, that a number is not given? What is the purpose of the number grabbing? –  Heiko Oberdiek Nov 17 '12 at 19:43
    
The purpose is to grab a 1-based index. I really only need explicit simple positive integers. –  mhelvens Nov 17 '12 at 19:47

3 Answers 3

up vote 8 down vote accepted

To scan a character at a time you need to use \futurelet (or its LaTeX wrapper \@ifnextchar) not a macro argument #1 then it is safe for {}. But for numbers I would just do this:

enter image description here

\documentclass{article}

\begin{document}

\makeatletter
\def\num{\afterassignment\xnum\count@}


\def\xnum{the number was [\the\count@]}

\num44

\emph{\num99}

\end{document}

There is nothing special about \count@ here. It is just a handy scratch count register that is declared in LaTeX and plain. You could go \newcount\mycount and then use \mycount instead. The point is that when assigning a value to a count register TeX automatically parses for a number and puts the value in the register. \afterassignment then gives you a hook to gain control after the assignment has been done and do something with the value.

share|improve this answer
    
Good plan: I was thinking \futurelet with charcode detection, but as you say \afterassignment is by far the cleanest way here! –  Joseph Wright Nov 17 '12 at 19:16
    
Very elegant! Thanks also for pointing me to \futurelet, which I'll have to check out regardless. But I guess the trick here is \count@. Could you explain what it does exactly (in the answer, maybe)? This is not something I can Google for. –  mhelvens Nov 17 '12 at 19:26
    
@JosephWright This method gobbles spaces and expands tokens afterwards in some situations and it breaks, if there is no number. –  Heiko Oberdiek Nov 17 '12 at 19:27
    
@HeikoOberdiek You can deal with the no-number problem by adding a 0 in the definition of \num. From the question, it's not clear what the behaviour should be for spaces, implicit tokens, etc. –  Joseph Wright Nov 17 '12 at 19:29
    
@HeikoOberdiek yes I thought about putting a 0 in but wasn't sure if that was the right thing to do 0-1 wouldn't work for example. –  David Carlisle Nov 17 '12 at 19:33

I do not know the purpose and format of the "number". There are many variants what can be considered a number:

  1. Explicit number consisting of digits 0 up to 9.

  2. TeX number, that includes count registers, internal numbers given by primitives (\inputlineno, \value{page}, \numexpr), even dimensions (\textwidth) can be interpreted as numbers.

This solution assumes an explicit number, because the questions speaks of "grabbing numerical digits". Also the tokens afterwords should be kept and not removed. When scanning a number TeX sometimes gobbles a space or expands the tokens afterwards in some situations.

The test relies on \futurelet to inspect the next token.

\documentclass{article}

\makeatletter
\newcommand*{\num}{%
    \let\@tmpnum\@empty
    \futurelet\@tmptok\scan@getnum
}
\newcommand*{\scan@getnum}{%
  \let\scan@next\scan@addnum
  \ifx\@tmptok0%
  \else\ifx\@tmptok1%
  \else\ifx\@tmptok2%
  \else\ifx\@tmptok3%
  \else\ifx\@tmptok4%
  \else\ifx\@tmptok5%
  \else\ifx\@tmptok6%
  \else\ifx\@tmptok7%
  \else\ifx\@tmptok8%
  \else\ifx\@tmptok9%
  \else
    \def\scan@next{%
      [I saw the number \@tmpnum!]%
    }%
  \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi
  \scan@next
}
\newcommand*{\scan@addnum}[1]{%
  \edef\@tmpnum{\@tmpnum#1}%
  \futurelet\@tmptok\scan@getnum
}
\makeatother

\begin{document}

\num1234...

\num56 78 with spaces

\num9\par

\emph{\num42}

\end{document}

Result

share|improve this answer
    
Thanks! Very instructive! (To be honest I never even considered the issue of gobbling spaces. One problem at a time. :-) ) –  mhelvens Nov 17 '12 at 19:34
    
I see you have two different solutions in one code block now. Are they semantically equivalent? (Also, might want to separate them into two blocks?) –  mhelvens Nov 17 '12 at 19:52
    
@mhelvens Yes, they are equivalent. I have forgot to delete the second code block -- a drawback of the GUI with a thumbnail sized editor window :-( –  Heiko Oberdiek Nov 17 '12 at 19:58

I just discovered a feature of etextools, based on \futurelet, that seems to be made for this purpose:

\newcommand*{\num}{%
    \futuredef[0123456789]{\@nn}{[I saw the number \@nn!]}%
}

\futuredef takes a list of tokens that it accepts and it stops scanning when it sees anything else.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.