# Testing if a token list can be assigned to a \dimendef'd quantity

Given an arbitrary token list (say 01abpt), how does one check that it can be assigned to a dimension (say \@tempdima)? LaTeX \@defaultunits wasn't made for this type of test. If the token list is of the form 10pt, then it is easy. But the token list can be arbitrary. For example, abpt will cause "! Missing number, treated as zero", and we can't do

\@tempdima\dimexpr 0pt+abpt\relax


To add to the problem, we may (in the case in mind) accept 10ptABD as dimensionable.

Note: undefined control sequences are ruled out of the token list.

There is a discussion of testing for numbers at here, but that isn't relevant.

• Not sure if I can understand what you are after. You are looking for a reliable method to trap errors for user input? Will the lengths include skips i.e., minus plus etc? – Yiannis Lazarides Jun 7 '12 at 5:55
• Parse the token list with expl3 and l3regex facilities. If the first token is a \skipdef or \dimendef token, you're done; if it's a \countdef or \chardef or \mathchardef token, you have to test for a unit just after it. Otherwise you can test for a number with a regular expression and then for the presence of a unit. This is, of course, very complicated if the token list is arbitrary and you want to expand tokens as you go; f-expansion can be useful. – egreg Jun 7 '12 at 9:30
• @YiannisLazarides: Given an arbitrary token list (a direct user input or internally derived token list), I needed to know if it can be assigned to a \dimendef'd variable. If it can't, the processor/assignor should raise an error. I now think it is safer to restrict the input: as egreg's analysis has shown, the solution is complicated, if possible. – Ahmed Musa Jun 7 '12 at 17:35

Just to give the flavor, here's the test up to the check whether the first token (after f-expansion) is either \dimen or \skip and is followed by an integer less than 32768:

\documentclass{article}
\usepackage{xparse,l3regex}

\ExplSyntaxOn
\tl_new:N \l_ahmed_arg_tl
\tl_new:N \l_ahmed_first_tl
\tl_new:N \l_ahmed_rest_tl
\tl_new:N \l_ahmed_temp_tl
\seq_new:N \l_ahmed_extr_seq
\prg_new_protected_conditional:Npnn \ahmed_if_dimen:n #1 {T,F,TF}
{
\tl_set:Nf \l_ahmed_arg_tl { #1 }
\ahmed_check_primitive:
}
\cs_generate_variant:Nn \token_if_eq_meaning_p:NN {NV}
\cs_new_protected:Npn \ahmed_check_primitive:
{
\tl_set:Nx \l_ahmed_first_tl { \tl_head:V \l_ahmed_arg_tl }
\tl_set:Nx \l_ahmed_rest_tl { \tl_tail:V \l_ahmed_arg_tl }
\bool_if:nTF
{
\token_if_eq_meaning_p:NV \tex_dimen:D \l_ahmed_first_tl
||
\token_if_eq_meaning_p:NV \tex_skip:D \l_ahmed_first_tl
}
{ \ahmed_check_integer: }
{ \ahmed_check_def_token: }
}

\cs_generate_variant:Nn \regex_extract_once:nnN {nV}
\cs_new_protected:Npn \ahmed_check_integer:
{
\tl_set:Nf \l_ahmed_temp_tl { \l_ahmed_rest_tl }
\regex_extract_once:nVN { \A \d * } \l_ahmed_temp_tl \l_ahmed_extr_seq
\seq_if_empty:NTF \l_ahmed_extr_seq
{ \prg_return_false: }
{
\int_compare:nTF { \seq_item:Nn \l_ahmed_extr_seq {0} < 32768 }
{ \prg_return_true: }
{ \prg_return_false: }
}
}

\cs_new:Npn \ahmed_test:n #1
{
\ahmed_if_dimen:nTF { #1 }{ \typeout{YES} } { \typeout{NO} }
}

\ahmed_test:n {\dimen34abc}
\ahmed_test:n {\skip1234567}
\def\xyz{\dimen22}
\ahmed_test:n {\xyz}
\ahmed_test:n {aaa}


The output is

YES
NO
YES
! Undefined control sequence.
<argument> \ahmed_check_def_token:

l.53 \ahmed_test:n {aaa}


showing that in the fourth case the control is indeed transferred to the next stage. This is actually still incomplete, because the integer following \dimen or \skip might be an "implicit number" (a count register or \chardef token, for instance), so a check for that would be necessary. Or, worse, it might have been embedded in a macro:

\def\fake{2}
\dimen1\fake 1=1pt


would be a legitimate assignment to \dimen121.

If there is more control on the token list, for example we are sure it can be expanded to a list of unexpandable tokens, the check could be easier.

• Well, you an't capture all cases, e.g. once \def\delimited#1\flag{3pt } then \ahmed_test:n {\delimited\flag} works, but \ahmed_test:n {\delimited\noflag} breaks. Presumably, one could simply fully expand tokens, and not try to catch expansion errors like this, but only catch errors on the expanded input. Then you can in principle check for dimendef-able token lists. However, regexes won't be enough to parse expressions (that requires a proper grammar), so \dimen\numexpr 1+(2+(...(3)...))\relax with an unequal number of ( and ) will be misdetected... – Bruno Le Floch Jun 7 '12 at 22:11
• @BrunoLeFloch Thanks. I was just trying to underline the difficulties of solving the problem in full generality. For "tame" token lists one can do, for "wild" ones there's not much to do. – egreg Jun 7 '12 at 22:16
• the problems are similar to what I hit upon when trying to produce an expandable x-expansion: \toks\numexpr <arbitrary numexpr> is a killer since I can't just expand blindly. – Bruno Le Floch Jun 7 '12 at 22:22

I find the following test so far adequate for my need. The only type of input I consider is typified by the following

{1pt,1.5pt,...,10pt,13pt,15pt,...,21pt}
{1ex,2ex,...,10ex,13ex,15ex,...,21ex}


which, for example, has been supplied directly by the user for parsing on a callback. So catcode-12 tokens are not expected as part of the input, but it is easy to accommodate such tokens. I also can accommodate \dimendef'd registers through the tests at Test if a given control sequence is a length register. In the intended applications, I don't need to test for \count'd and \skip'd registers. The problem is actually with internal dimensions, but they aren't expected to be part of the above type of user input.

\documentclass{article}

\makeatletter
% Also test for number (without unit)
\newif\ifisnumber
\def\ifbooltrue#1{%
\csname @\csname if#1\endcsname first\else second\fi oftwo\endcsname
}
\def\ifcmdcmp@#1#2\@nil{\expandafter#1}
\def\ifcmdcmp#1#2{%
\csname @\expandafter\expandafter\expandafter
\ifx\ifcmdcmp@#1\batchmode\@nil\@car#2\noboundary\@nil
first\else second\fi oftwo\endcsname
}
\def\findunit#1#2#3\@nil{%
% Check for a valid unit.
\in@{,#1,}{,\paperwidth,\paperheight,\textwidth,\textheight,%
\hsize,\vsize,\columnwidth,\linewidth,}%
\ifbooltrue{in@}{%
\@firstoftwo
}{%
\lowercase{\in@{,#1#2,}}{,em,ex,in,pt,pc,cm,mm,dd,cc,nd,nc,bp,sp,}%
\ifbooltrue{in@}%
}%
}
\def\catchremainder#1\@nil{\endgroup\def\remainder{#1}}
\def\ifdimensionable#1{%
\isnumberfalse\begingroup
\afterassignment\catchremainder
\@tempcnta0#1\relax\@nil
\ifcmdcmp\remainder\empty{%
\isnumbertrue\@secondoftwo
}{%
\expandafter\ifcmdcmp\expandafter{\remainder}\relax{%
\isnumbertrue\@secondoftwo
}{%
\expandafter\findunit\remainder xx\@nil
}%
}%
}
\makeatother

%\ifdimensionable{01}{\def\x{T}}{\def\x{F}}
%\ifdimensionable{01pt}{\def\x{T}}{\def\x{F}}
\ifdimensionable{01abpt}{\def\x{T}}{\def\x{F}}
%\ifdimensionable\paperwidth{\def\x{T}}{\def\x{F}}
% Is 'pt' 0pt or 1pt? Make it invalid input in revised code:
%\ifdimensionable{pt}{\def\x{T}}{\def\x{F}}

\begin{document}
x
\end{document}

• PT,BP,MM etc all upper case are valid units:) – Yiannis Lazarides Jun 7 '12 at 21:32
• And the units can be given with category code 12 (no other catcode, though), or as a dimension register (or skip, or internal dimension...). – Bruno Le Floch Jun 7 '12 at 22:13
• Thanks. I have added \lowercase on input. I recognize that it isn't a generic test! For example, what happens to 1.2pt? In the latter case, I have used an indirect approach. – Ahmed Musa Jun 8 '12 at 0:28

Arbitrary token lists?

How to catch up nice never ending eTeX situations like in

\newdimen\tempdima
\def\Collatz#1{%
\expandafter\Collatz
\expandafter{\number\numexpr#1\ifodd#1 *3+1\else/2\fi\relax}%
}%
%
\tempdima=\Collatz{2057}pt %
%
\bye


?

How to handle things that produce unbalanced braces, e.g.,

\tempdima=\iffalse{\fi}4pt


?

How to handle the possible presence of \outer-tokens?
(You can do

\newdimen\tempdima

, but \somenumber cannot be processed as part of the argument of a macro which triggers examination of the things that might be passed to \tempdima.)