# Signalling error in “expandable context”

I'm reviewing a few old macros of mine to make them fully expandable. One of the macros takes one argument, which should either be a two-digit or a four-digit integer (>1955). The macro then converts two-digit numbers to four-digit years in the range 1956-2055, and then goes on to do some other stuff (basically based on the difference between current year and the given year). I want to make this expandable since I want to be able to use it in for example \href{mailto:foo\mymacro{98}bar@example.com}{Mail me!}.

Now, reviewing the code, I can see that I used lots of assignments, but using \numexpr I can get rid of all of this, although at the cost of making the same computation several times as I branch through the different cases.

But I also used to have a bit of sanity checking: If #1 is greater than 99 but smaller than 1956, I issue a \PackageError explaining the problem. If the sanity check holds, this is simply skipped, so its non-expandability is not a problem. Also, when used in ordinary text, the error works as expected. However, inside \href, the error becomes

Undefined control sequence.
\GenericError  ...
#4  \errhelp \@err@     ...


Is there a way to provide the user with an indication of the problem?

PS: I'm not sure if "expandable context" is the right way to describe the situation. Feel free to suggest a better title.

Edit: As an example, assume I want \mymacro to expand to the absolute value of the difference between the given year and \year. If the input is less than 100, we should interpret it as a year in the range 1956-2055. Input in the range 100-1955 should give an error. What I have now is something like

\def\mymacro#1{%
\ifnum\numexpr#1>1955 %
\ifnum\numexpr\year-#1\relax<0 %
\number\numexpr#1-\year\relax
\else
\number\numexpr\year-#1\relax
\fi
\else
\ifnum#1>99 %
ERROR!%
\else
\ifnum#1>56 % 19xx
\number\numexpr\year - 19#1\relax
\else % 20xx
\ifnum\numexpr\year - 20#1\relax<0
\number\numexpr20#1 - \year\relax
\else
\number\numexpr\year - 20#1\relax
\fi
\fi
\fi
\fi
}

%\def\test#1{#1: a\mymacro{#1}b}
\test{96} \test{1996}; \test{08} \test{2008}; \test{11} \test{2011};
\test{20} \test{2020}

\test{123}


(the a and b are just there to check that spaces don't creep in). So I'd like to replace ERROR! by something which tells the user about the problem, preferably using a message that includes the offending #1.

-

I don't think it's possible, since the expansion of \mymacro should always be something that can be interpreted by \href as a link and \PackageError isn't.

You can test if you are inside the argument of \href by checking whether the catcode of % is 13:

\ifnum\catcode\%=\active\else\PackageError{...}{...}{...}\fi


but I don't know how robust is this code. Can you show the relevant part of your code?

-
Choosing an answer to accept wasn't trivial: I used both this and Joseph's suggestion. Thanks, all. –  Villemoes May 8 '11 at 7:53

The best approach I think is to use a deliberately undefined but well-named control sequence. Something like

\ifnum\SomeVar>100\relax
\expandafter\SignalError
\fi

...

\begingroup
\catcode\!=11\relax
\catcode\ =11\relax%
\gdef\SignalError{%
\ Input too big!%
}%
\endgroup%


Done using a catcode 11 space, you'll get an error which at least has some description but which does not crash inside an \edef. You might need to adjust the number of letters you use to get it to look 'nice', but this is usually not too hard.

-

Similar to Joseph's answer, but allows any error message (I will use it soon in a package which I try to make very robust about wrong input).

\makeatletter
\catcode\:=11\relax
\newcommand{\fp@error@aux}[1]
{\romannumeral \numexpr 0\@firstofone{\fp@error: #1}}
\newcommand{\fp@error}[1]
{\expandafter\fp@error@aux\csname fp info: #1\endcsname}

\fp@error{wrong input (123)}


Giving

! Undefined control sequence.
<argument> \fp@error:
\fp info: wrong input (123)


Here, \csname fp info: #1\endcsname builds a control sequence from the characters between \csname and \endcsname. TeX does not leave it undefined: instead, it is turned into \relax, which will not cause an error. Instead, we put an undefined control sequence nearby. \@firstofone improves the formatting of the error message, since the undefined control sequence \fp@error: is now alone with our \relax-ed control sequence in an <argument>. Finally, we need a clean way of getting rid of \relax, and \romannumeral\numexpr0 does a good job at this.

EDIT: a few months after that answer, we added \msg_expandable_error:n to LaTeX3. This command takes one (braced) argument and displays it (without expanding it). If the user inserts some material (answering I... to TeX's prompt), that material is inserted after cleaning up the eror message.

-

You could define \mymacro to expand to be empty when the argument is out-of-range and wrap \href in a macro that would test for emptiness.

\documentclass{article}
\usepackage{hyperref}

\def\mymacro#1{%
\ifnum\numexpr#1>1955 %
\ifnum\numexpr\year-#1\relax<0 %
\number\numexpr#1-\year\relax
\else
\number\numexpr\year-#1\relax
\fi
\else
\ifnum#1>99 %
% out-of-range error!
\else
\ifnum#1>56 % 19xx
\number\numexpr\year - 19#1\relax
\else % 20xx
\ifnum\numexpr\year - 20#1\relax<0
\number\numexpr20#1 - \year\relax
\else
\number\numexpr\year - 20#1\relax
\fi
\fi
\fi
\fi
}

\begin{document}
%\def\test#1{#1: a\mymacro{#1}b}
\test{96} \test{1996}; \test{08} \test{2008}; \test{11} \test{2011};
\test{20} \test{2020}

\def\hrefwrap#1{\def\tmpi{\mymacro{#1}}\if\relax\tmpi\relax
\typeout{error! (#1 out of range)}ERROR\footnote{#1 out of range}\else