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.

I'm in need of a macro which receives a control sequence as argument and needs to branch if this macro is actually a length (of any kind, e.g. dimen, skip etc.) or "just" a normal macro (including one which is not defined yet).

\def\assignlengthormacro#1#2{%
   \@iflength{#1}{%
      \setlength{#1}{#2}%  or simply #1=#2\relax
   }{%
      \def#1{#2}%
   }%
}

\newlength\mylength % LaTeX length => TeX skip register
\newdimen\mydimen
\def\mymacro{Some other definition}
\assignlengthormacro{\mylength}{1pt}
\assignlengthormacro{\mydimen}{1pt}
\assignlengthormacro{\mymacro}{1pt}
\assignlengthormacro{\previouslyundefinedmacro}{1pt}% should still work

Note: I'm able to code it myself (using \meaning), but I think it is a good question and like to see other possible approaches. Maybe other high-rep users will wait a little and give others a chance to try it and earn some reputation points. Bonus challenge: make the test fully expandable ;-)

share|improve this question

5 Answers 5

Here is a purely expandable non-expl3 solution for everything. I welcome suggestions of its failure modes. Because of the behaviour of the \meaning of \count, \skip, \dimen and \toks-defined registers, an expandable test for primitives isn't as straightforward as it might first seem.

\documentclass{article}
\makeatletter
\def\@@empty{\@gobble\@@empty}
\def\if@cond#1\fi{\csname @#1\@@empty first\else second\fi oftwo\endcsname}
\def\if@blank#1{\if@cond\ifcat$\detokenize\expandafter{\@gobble#1.}$\fi}
\def\if@num#1#{\if@cond\ifnum#1\fi}
\def\if@cseq#1#2{\if@cond\ifx#1#2\fi}
\begingroup
\catcode`\&=7
\gdef\defregistertester#1#2{%
  \begingroup
  \def\x##1{\unexpanded\expandafter
    {\csname\expandafter\@gobble\string#1@test@##1\endcsname}}%
  \edef\x{\endgroup
    \def\noexpand#2####1{%
      \unexpanded{\if@cseq#1##1\register@test@a}{%
        \unexpanded{\ifprimitive{##1}\register@test@b}
        {\noexpand\expandafter\x{a}\noexpand\meaning####1:&}%
      }%
    }%
    \def\x{a}####1:####2&{\x{b}####1\string#1&}%
    \def\x{b}####1\string#1####2&{\noexpand\if@blank{####1}}%
  }\x
}
\endgroup
\def\register@test@a#1#2{#2}
\def\register@test@b#1#2{is primitive}
\def\ifprimitive#1{\expandafter\if@primitive\meaning#1\relax}
\def\if@primitive#1#2\relax{%
  \if@cond\if#1\@backslashchar\fi{%
    \ifdigitfound{#2}\@secondoftwo\@firstoftwo
  }{%
    \@secondoftwo
  }%
}
\def\ifdigitfound#1{\if@cond\if0\if@blank{#1}{1}{\if@digitfound#1\@nnil}\fi}
\def\if@digitfound#1{%
  \if@cseq#1\@nnil{1}{%
    \if@num`#1>47{%
      \if@num`#1<58{0\remove@to@nnil}{\if@digitfound}%
    }{%
      \if@digitfound
    }%
  }%
}

% Tests
\newskip\skipa
\newcount\cnta
\newdimen\dima
\newtoks\toksa

\defregistertester\skip\ifskip
\defregistertester\count\ifcount
\defregistertester\dimen\ifdimen
\defregistertester\toks\iftoks

\edef\x{\ifdimen\textwidth{T}{F}} % -> True
\edef\x{\ifdimen\hsize{T}{F}} % -> is primitive
\edef\x{\ifdimen\dima{T}{F}} % -> True
\edef\x{\ifskip\muskip{T}{F}} % -> is primitive
\edef\x{\ifskip\foo{T}{F}}
\edef\x{\ifskip\skipa{T}{F}}
\edef\x{\ifskip\skip{T}{F}}
\edef\x{\ifskip\skipit{T}{F}}
\def\skipx{}
\edef\x{\ifskip\skipx{T}{F}}
\makeatother

\begin{document}
\textbf{Primitive tests}\par

True: \ifdigitfound{a1c}{T}{F}\par
False: \ifdigitfound{abc}{T}{F}\par
True: \ifprimitive\muskip{T}{F}\par
True: \ifprimitive\hsize{T}{F}\par
False: \ifprimitive\textwidth{T}{F}\par
False: \ifprimitive\martin{T}{F}\par
True: \ifprimitive\toks{T}{F}\par
False: \ifprimitive\toksa{T}{F}\par

\par\medskip
\textbf{Skip tests}\par\medskip

False: \ifskip\foo{T}{F}\par
True: \ifskip\skipa{T}{F}\par
False: \ifskip\skip{T}{F}\par
False: \ifskip\skipx{T}{F}

\par\medskip
\textbf{Count tests}\par\medskip

False: \ifcount\foo{T}{F}\par
True: \ifcount\cnta{T}{F}\par
False: \ifcount\skipa{T}{F}\par
\def\countx{}
False: \ifcount\countx{T}{F}\par

\par\medskip
\textbf{Dimension tests}\par\medskip

False (is primitive): \ifdimen\hsize{T}{F}\par
True: \ifdimen\textwidth{T}{F}\par
True: \ifdimen\dima{T}{F}

\par\medskip
\textbf{Toks tests}\par\medskip

True: \iftoks\toksa{T}{F}\par
False: \iftoks\toks{T}{F}\par
False: \iftoks\toksb{T}{F}\par
\end{document}
share|improve this answer

Thanks a lot for all the answers. I also like to show how I approached it. For a non-expandable version I would expanded the \meaning and read a predefined number of character to compare it with \ifx with a \edef\SKIP{\string\skip}. This can be repeated for several different values.

In order to keep it expandable every character can be compared using its char-code. This is done using \ifnum`, because \if also compares the catcode which is set to other not letter. This is basically a state-machine. At the end I check if a digits follows to exclude primitives with the same starting letters (if there are any) or \dimen and \skip itself (thanks to Frank Mittelbach who pointed this possibility out in his answer).


Here is the approach with one macro per letter. It assumes that \escapechar is non-negative, which is usually the case.

\def\@iflength#1{%
    \ifcase0\expandafter\@iflength@a\meaning#1\@nnil\space
        \expandafter\@firstoftwo
    \else
        \expandafter\@secondoftwo
    \fi
}

\def\@iflength@a#1{%
    \ifnum\escapechar=`#1\space
        \expandafter\@iflength@b
    \else
        1%
    \fi
}
\def\@iflength@b#1{%
    \ifcase0%
        \ifx#1\@nnil 0\else
        \ifnum`d=`#1 1\else
        \ifnum`s=`#1 2\fi\fi\fi
    \space
        1%
    \or
        \expandafter\@iflength@di
    \else
        \expandafter\@iflength@sk
    \fi
}
\def\@iflength@sk#1{%
    1%
}
\def\@iflength@di#1{%
    \ifcase0%
        \ifx#1\@nnil 0\else
        \ifnum`i=`#1 1\fi\fi
    \space
        1%
    \or
        \expandafter\@iflength@dim
    \fi
}
\def\@iflength@dim#1{%
    \ifcase0%
        \ifx#1\@nnil 0\else
        \ifnum`m=`#1 1\fi\fi
    \space
        1%
    \or
        \expandafter\@iflength@dime
    \fi
}
\def\@iflength@dime#1{%
    \ifcase0%
        \ifx#1\@nnil 0\else
        \ifnum`e=`#1 1\fi\fi
    \space
        1%
    \or
        \expandafter\@iflength@dimen
    \fi
}
\def\@iflength@dimen#1{%
    \ifcase0%
        \ifx#1\@nnil 0\else
        \ifnum`n=`#1 1\fi\fi
    \space
        1%
    \or
        \expandafter\@iflength@dimen@
    \fi
}
\def\@iflength@dimen@#1{%
    \ifcase0%
        \ifx#1\@nnil 0\else
        \ifnum47<`#1 \ifnum58>`#1 1\fi\fi\fi
    \space
        1%
    \or
        0%
        \expandafter\remove@to@nnil
    \fi
}
\def\@iflength@sk#1{%
    \ifcase0%
        \ifx#1\@nnil 0\else
        \ifnum`k=`#1 1\fi\fi
    \space
        1%
    \or
        \expandafter\@iflength@ski
    \fi
}
\def\@iflength@ski#1{%
    \ifcase0%
        \ifx#1\@nnil 0\else
        \ifnum`i=`#1 1\fi\fi
    \space
        1%
    \or
        \expandafter\@iflength@skip
    \fi
}
\def\@iflength@skip#1{%
    \ifcase0%
        \ifx#1\@nnil 0\else
        \ifnum`p=`#1 1\fi\fi
    \space
        1%
    \or
        \expandafter\@iflength@skip@
    \fi
}
\def\@iflength@skip@#1{%
    \ifcase0%
        \ifx#1\@nnil 0\else
        \ifnum47<`#1 \ifnum58>`#1 1\fi\fi\fi
    \space
        1%
    \or
        0%
        \expandafter\remove@to@nnil
    \fi
}

I then coded a general macro for the repeated comparison. I guess the parse macro of LaTeX3 used in egreg's answer works similar, at least in principle.

Some test code is also included.

\documentclass{article}

\makeatletter

\def\@iflength#1{%
    \ifcase0\expandafter\@iflength@a\meaning#1\@nnil\space
        \expandafter\@firstoftwo
    \else
        \expandafter\@secondoftwo
    \fi
}

\def\@iflength@a#1{%
    \ifnum\escapechar=`#1\space
        \expandafter\@iflength@b
    \else
        1%
        \expandafter\remove@to@nnil
    \fi
}
\def\@skip@or@fi#1\or#2\fi{#1\fi}
\def\@iflength@b#1{%
    \ifcase0%
        \ifx#1\@nnil 0\else
        \ifnum`d=`#1 1\else
        \ifnum`s=`#1 2\else
        \ifnum`m=`#1 3\fi\fi\fi\fi
    \space
        1%
    \or
        \@skip@or@fi
        \@iflength@cmp imen\relax
    \or
        \@skip@or@fi
        \@iflength@cmp kip\relax
    \or
        \@iflength@cmp uskip\relax
    \fi
}
\def\@checkdigit#1{%
    \ifcase0%
        \ifnum`0>`#1 1\else
        \ifnum`9<`#1 1\fi\fi
    \space
        1%
    \else
        0%
    \fi
}
\def\@iflength@cmp#1#2\fi#3{%
    \fi
    \ifcase0%
        \ifx#3\@nnil  0\else
        \ifx#1\relax \@checkdigit#3\else
        \ifnum`#1=`#3 2\fi\fi\fi
    \space
        1%
    \or
        0%
        \expandafter\remove@to@nnil
    \or
        \@iflength@cmp#2%
    \fi
}

% \skip
% \dimen
%
\def\@expand#1{%
  \par\string#1: \@iflength{#1}{\the}{}#1%
}

\begin{document}

\def\test#1{\@iflength{#1}{\typeout{\string#1: is a length}}{\typeout{\string#1: is NOT a length (\meaning#1)}}}
%\def\test#1{\edef\A{\@iflength{#1}{YES}{No}}\A\show\A}

\test{\test}
\test{\@gobble}
\test{\@nil}
\test{\@@nil}
\test{\textwidth}
\newlength\mylength
\test{\mylength}
\def\mymacro#1{test #1}
\test{\mymacro}
\test{\skip}
\test{\dimen}
\muskipdef\mymuskip=1
\test{\muskip}
\test{\mymuskip}
\def\mymacro{1pt}

\@expand\textwidth

\@expand\mymacro

\makeatother
\end{document}
share|improve this answer
    
I've corrected a missing i and the \muskipdef\mymuskip=1pt. The first was a typo, the second a real error: the syntax is \muskipdef\cs=<number>. In you case pt was simply printed. However, you can't assign a dimension in points to a \muskip register. –  egreg May 23 '12 at 15:09
    
@egreg: Thanks, I didn't know \muskip much and thought it works similar to \skip. In this case I should rather remove it. –  Martin Scharrer May 23 '12 at 15:20
    
In e-TeX one can compare \muskip registers with \dimen or \skip registers, but it's a very crude comparison via \gluetomu or \mutoglue that convert 1pt to 1mu and vice versa. –  egreg May 23 '12 at 15:26

Let me start with an unexpandable version:

\documentclass{article}
\usepackage{xparse,l3regex}
\ExplSyntaxOn
\NewDocumentCommand{\IfLengthTF}{mmm}
 {
  \lentest_if_length:NTF #1 { #2 } { #3 }
 }
\NewDocumentCommand{\IfLengthF}{mm}
 {
  \lentest_if_length:NF #1 { #2 }
 }
\NewDocumentCommand{\IfLengthT}{mm}
 {
  \lentest_if_length:NT #1 { #2 }
 }
\prg_new_protected_conditional:Npnn \lentest_if_length:N #1 {p,T,F,TF}
 {
  \regex_match:nxTF {\A\\??(skip|dimen).+} { \token_to_meaning:N #1 }
   { \prg_return_true: }
   { \prg_return_false: }
 }
\cs_generate_variant:Nn \regex_match:nnTF {nx}
\ExplSyntaxOff

\newlength\pippo
\newdimen\pluto

\IfLengthTF{\pippo}{\typeout{It's a length}}{\typeout{It's NOT a length}}
\IfLengthTF{\pluto}{\typeout{It's a length}}{\typeout{It's NOT a length}}
\IfLengthTF{\skip}{\typeout{It's a length}}{\typeout{It's NOT a length}}
\IfLengthTF{\xxeey}{\typeout{It's a length}}{\typeout{It's NOT a length}}

The output is

It's a length
It's a length
It's NOT a length
It's NOT a length

One can also use \IfLengthT or \IfLengthF, when the true or false branches are not needed.

The regex matches \skip or \dimen, followed by at least one character, at the start of the argument's meaning. The \\?? means that the backslash may not be present (it happens if \escapechar is -1; other possibilities for the value of \escapechar are not taken care of, but they could if needed).


Now an expandable test (thanks to Joseph Wright for suggesting it):

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\IfLengthTF}{mmm}
 {
  \lentest_if_length:NTF #1 { #2 } { #3 }
 }
\NewDocumentCommand{\IfLengthF}{mm}
 {
  \lentest_if_length:NF #1 { #2 }
 }
\NewDocumentCommand{\IfLengthT}{mm}
 {
  \lentest_if_length:NT #1 { #2 }
 }

\prg_new_conditional:Npnn \lentest_if_length:N #1 {p,T,F,TF}
 {
  \bool_if:nTF
   {
    \token_if_dim_register_p:N #1 || \token_if_skip_register_p:N #1
   }
   { \prg_return_true: }
   { \prg_return_false: }
 }
\ExplSyntaxOff

Of course the \IFLength... commands are not expandable; but the \lentest_if_length:NTF test and its siblings are.


Why choose one or the other? It depends on what's the purpose of the macros. The second version can be made expandable, which might be desirable in certain contexts. The first version can be generalized in various ways, testing for whatever is necessary with a suitable regular expression.


Caveat: these tests don't distinguish among control sequences which are not symbolic names for skip or dimen registers and other control sequences. So \IfLengthTF{\hsize}{...}{...} will choose the "false" branch. It's impossible to recognize an internal dimen or skip register only via \meaning: one way might be to check the meaning against a list of the primitive names of the parameters (which might be added to the first method).

share|improve this answer
    
Did you look at using the \token_if... tests? –  Joseph Wright May 22 '12 at 16:02
    
@JosephWright No; but they are too easy! :) –  egreg May 22 '12 at 16:04
1  
Nice example of showing the power of expl3! –  Frank Mittelbach May 22 '12 at 16:15
    
@FrankMittelbach Do you agree with the form I used, particularly with respect to \NewDocumentCommand? –  egreg May 22 '12 at 16:17
    
@egrep yes I do. Clear separation of the functionality provided, i.e., \lentest_if_length:NTF and the document interface (here using 2e syntax via xparse). –  Frank Mittelbach May 22 '12 at 19:48

Here is an expandable test for "skips" it could be extended to dimens in a similar manner:

\edef\meaningskip{\meaning\skip}
\def\grabfive #1#2#3#4#5#6#7\grabfive{#2#3#4#5\ifx#6!!\fi}

\def\testforskip#1{%
\expandafter\ifx\csname \expandafter\grabfive\meaning#1!!!!!\grabfive\endcsname\skip
   \typeout{\string #1 victory}%
\else
   \typeout{\string#1 fail =\meaning#1}%
\fi}

\newskip\boo

\testforskip\foo
\testforskip\boo
\testforskip\skip
\testforskip\skipit
\def\skipthat{}
\testforskip\skipthat

This would give you:

\foo fail =undefined
\boo victory
\skip fail =\skip
\skipit fail =undefined
\skipthat fail =macro:->

My claim would be that there isn't anything expandable + safe that doesn't involve \meaning.

share|improve this answer
    
Thanks! Very interesting use of \csname! Again something I wouldn't have thought off. –  Martin Scharrer May 22 '12 at 14:08
    
Why #6#7 and the \ifx#6!!\fi? –  egreg May 22 '12 at 14:17
    
@egreg take it out and watch what happens –  Frank Mittelbach May 22 '12 at 14:20
    
I did: the output is the same. As long as there are at least five ! after \meaning#1 there should be no problem. –  egreg May 22 '12 at 14:25
    
@egrep if you take it out \skip will generate victory, no? –  Frank Mittelbach May 22 '12 at 14:38

A simple measurment of an \hbox should work. Unfortunately, this method may not work when \mymcro requires argument(s).

\documentclass{article}
\makeatletter
\def\iflength#1{%
    \begingroup
    \setbox\z@\hbox{\ifdefined#1\expandafter#1\fi0pt }%
    \ifdim\wd\z@=\z@\endgroup\expandafter\@firstoftwo
    \else\endgroup\expandafter\@secondoftwo
    \fi}
\makeatother
\newdimen\mydimen
\newskip\myskip
\def\mymacro{dummy code}% must not have argument
\begin{document}
\iflength\mydimen{true}{false}

\iflength\myskip{true}{false}

\iflength\mymacro{true}{false}

\iflength\undefmacro{true}{false}
\end{document}
share|improve this answer
    
I like this idea. I wouldn't thought about it myself. However, the fact that the macro is typeset etc. is a drawback. –  Martin Scharrer May 22 '12 at 11:03
    
it would break for quite a number of cases so it isn't something that you can use with arbitrary macro input –  Frank Mittelbach May 22 '12 at 13:49

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.