测试给定的控制序列是否是长度寄存器

测试给定的控制序列是否是长度寄存器

我需要一个宏,它接收一个控制序列作为参数,并且如果这个宏实际上是一个长度(任何类型,例如dimenskip等等)或“只是”一个普通的宏(包括尚未定义的宏)。

\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

笔记:我可以自己编写代码(使用\meaning),但我认为这是个好问题,我想看看其他可能的方法。也许其他高声誉用户会稍等片刻,让其他人有机会尝试并获得一些声誉点数。奖金挑战:使测试完全可扩展;-)

答案1

这是一个针对“跳过”的可扩展测试,它可以以类似的方式扩展到 dimens:

\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

这将为你带来:

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

我的主张是,没有任何不涉及的可扩展+安全的东西\meaning

答案2

这是一个纯粹可扩展的非 expl3 解决方案。我欢迎对其故障模式的建议。由于 、\meaning和定义寄存器的行为,对原语的可扩展测试并不像乍一看那么简单。\count\skip\dimen\toks

\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}

答案3

简单的测量\hbox应该有效。不幸的是,当\mymcro需要参数时,这种方法可能不起作用。

\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}

答案4

非常感谢大家的回答。我也想展示一下我是如何解决这个问题的。对于不可扩展的版本,我会扩展\meaning并读取预定义的字符数,以便将其与进行比较\ifx\edef\SKIP{\string\skip}这可以针对几个不同的值重复进行。

为了保持可扩展性,可以使用其 char-code 比较每个字符。这是使用 完成的\ifnum`,因为 \if还会比较设置为的 catcode其他不是。这基本上是一个状态机。最后,我检查后面是否有数字,以排除具有相同首字母的基元(如果有的话)或\dimen\skip它本身(感谢 Frank Mittelbach 在他的回答中指出了这种可能性)。


以下是每个字母一个宏的方法。它假设为非\escapechar负数,通常情况如此。

\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
}

然后我编写了一个用于重复比较的通用宏。我猜parseegreg 的答案中使用的 LaTeX3 宏至少在原理上是类似的。

还包括一些测试代码。

\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}

相关内容