我需要一个宏,它接收一个控制序列作为参数,并且如果这个宏实际上是一个长度(任何类型,例如dimen
,skip
等等)或“只是”一个普通的宏(包括尚未定义的宏)。
\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
}
然后我编写了一个用于重复比较的通用宏。我猜parse
egreg 的答案中使用的 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}