这是类似的,但不是重复的如果未传递可选参数,则会出现特殊行为和提供参数时的不同宏行为。
我想定义一个宏\XXX
,使得\XXX{1}
和\XXX1
的行为相同(就像我以前\newcommand{\XXX}[1]{...}
定义宏时一样),但\XXX
本身的行为会有所不同。换句话说,类似于
\newcommand{\XXX}[1]{Hello #1}
\newcommand{\XXX}{Bye}
因此\XXX1
和\XXX2
分别生成“Hello 1”和“Hello 2”,但\XXX
生成“Bye”。
问题\@ifnextchar
是它不测试空格,而且我也不能只测试括号。我xparse
也没有找到用包来实现这一点的方法。
澄清:我不在乎\XXX 1
行为举止如何。
添加:在阅读评论并进行进一步思考后,我认为我所用的语法并不好。感谢大家的帮助。
答案1
后面没有空格标记,\XXX
因此您可以使用\@ifnextchar\lbrace
(或者您可以使用 xparseg
参数类型)但两者都完全违反 latex 语法。该参数是可选的,因此应该使用[..]
not {..}
。
遵循 LaTeX 语法约定的语法是
\newcommand\XXX[1][\relax]{\ifx\relax#1 bye \else hello #1\fi}
\XXX
\XXX[1]
答案2
\documentclass{article}
\makeatletter
% If #1 is empty or does hold space-tokens only, yields "bye",
% otherwise yields "hello #1".
% Requires an engine with eTeX-extensions.
\newcommand\XXX[1]{%
\if\relax\detokenize\expandafter{\@firstofone#1{}}\relax bye \else hello #1\fi
}
\makeatother
\begin{document}
\XXX{}
\XXX{ }
\XXX{1}
\end{document}
答案3
例如,您可以创建一个命令,让 LaTeX 暂时更改空格、制表符、回车符和 % 的 catcode(% 用于注释),并通过的变体\kernel@ifnextchar
(其本身基于\futurelet
)检查标记流中的下一个标记。
几天前,我在回答这个问题时LaTeX 命令后的空格中,我尝试解释让 LaTeX “提前查看”下一个标记的方法的缺点:
该方法的主要缺点是,它依赖于通过从 tex 源代码读取和标记 tex 输入而产生的标记流中的下一个标记,而这些 catcode 的临时更改是有效的。
但是标记也可以进入标记流,不是通过从 tex 源代码读取和标记 tex 输入,而是通过扩展,例如,宏标记,其中替换文本和参数在这些类别代码未改变的时间点都被标记化。
临时改变类别代码制度并依赖于改变的类别代码制度在它们应处理的标记被标记化时生效的命令不能在它们应处理的事物已经在未改变的类别代码制度下被标记化的情况下使用,例如,当它们通过扩展其他宏而从其他宏获得其参数时。
因此,在下面的例子中,\XXX
定义为\outer
尽可能确保它不会在定义文本或其他宏的参数中使用。
您需要另一种变体,\kernel@ifnextchar
因为您不能安全地使用,\kernel@ifnextchar
因为 \kernel@ifnextchar
它绝对不是 100% 可靠的:
这LaTeX 2e 的注释源作为一个pdf 文件其名字是源2e.pdf可以在这里找到http://mirrors.ctan.org/macros/latex/base/source2e.pdf。
\kernel@ifnextchar
在 LaTeX 2e 源中定义如下:文件 d: ltdefns.dtx 日期: 2018/09/26 版本 v1.5e :
321 \long\def\@ifnextchar#1#2#3{% 322 \let\reserved@d=#1% 323 \def\reserved@a{#2}% 324 \def\reserved@b{#3}% 325 \futurelet\@let@token\@ifnch} 326 \let\kernel@ifnextchar\@ifnextchar 327 \def\@ifnch{% 328 \ifx\@let@token\@sptoken 329 \let\reserved@c\@xifnch 330 \else 331 \ifx\@let@token\reserved@d 332 \let\reserved@c\reserved@a 333 \else 334 \let\reserved@c\reserved@b 335 \fi 336 \fi 337 \reserved@c} 338 \def\:{\let\@sptoken= } \: % this makes \@sptoken a space token 339 \def\:{\@xifnch} \expandafter\def\: {\futurelet\@let@token\@ifnch}
例如,您说您无法测试空格。没错。在实现
\kernel@ifnextchar
/时,需要付出额外的努力来实现一个删除空格的循环\@ifnextchar
。此循环会导致如下错误消息:
\kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}\@sptoken
这与以下事实有关:知道标记是否具有相同的含义并不意味着知道它们是否是相同的标记。
当 参数后面的标记
\kernel@ifnextchar
具有标记 的含义时\reserved@d
,即当您有类似\kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}\reserved@d
,
\kernel@ifnextchar
无论其第一个参数是什么,您都会得到它的第二个参数。(这就是为什么诸如此类的东西
\reserved@d
被保留。;-))例如,看看你从
\documentclass{article} \makeatletter \begin{document} % This is nice: \kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}\reserved@d \kernel@ifnextchar{Y}{The next thing is Y}{The next thing is not Y}\reserved@d \kernel@ifnextchar{Z}{The next thing is Z}{The next thing is not Z}\reserved@d \kernel@ifnextchar{\LaTeX}{The next thing is \LaTeX}{The next thing is not \LaTeX}\reserved@d % This raises nice errors. % \kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}\@sptoken X \end{document}
这样做的原因是
\kernel@ifnextchar
,不区分具有相同含义的不同标记。作为此行为的一个特例,\kernel@ifnextchar
不区分隐式字符和显式字符。
例如,请参见您从\documentclass{article} \makeatletter \let\implicitA=A \begin{document} \kernel@ifnextchar{A}{We have an }{We don't have an }\implicitA \kernel@ifnextchar{\implicitA}{We have an }{We don't have an }A \end{document}
上面说了:
例如,您可以创建一个命令,让 LaTeX 暂时更改空格、制表符、回车符和 % 的 catcode(% 用于注释),并通过的变体
\kernel@ifnextchar
(其本身基于\futurelet
)检查标记流中的下一个标记。
\futurelet
如果将这些字符的 catcode 切换为 12(其他)并且只对这些字符进行检查,则可以让 LaTeX 通过保留该标记来查看下一个标记的含义。
如果下一个标记的含义等于这些显式 catcode-12 字符标记之一的含义,您可以放心地让 LaTeX 将该标记作为未分隔的宏参数抓取。因此,在检查显式 catcode-12 字符的特殊情况下,您可以让 LaTeX“抓取”标记本身,以定义一个扩展到该标记的临时宏。如果已经定义了另一个扩展到您的\kernel@ifnextchar
-variant 的第一个参数的临时参数,LaTeX 可以\ifx
与这些临时宏进行比较,以确保所讨论的两个标记不仅具有相同的含义,而且实际上是相同的标记。(当抓取下一个标记作为参数时,LaTeX 应该在正确的时刻将其放回原位……)
在下面的例子中,我尝试实现一个例程\UD@ifnextcharForOtherTokens
,使 LaTeX 执行这些操作。至少我希望它能做到。;-)
\documentclass{article}
\makeatletter
% Patch verbatim to also display horizontal tabs:
% The patch is needed for this example only.
\usepackage{amssymb}
\g@addto@macro\dospecials{\keystroketab}
\newbox\UD@tempbox
\begingroup
\catcode`\^^I=13\relax
\@firstofone{%
\endgroup
\newcommand\keystroketab{%
\setbox\UD@tempbox\hbox{\verbatim@font\char32}%
\catcode`\^^I=13\relax
\def^^I{\mbox{\hbox to 3\wd\UD@tempbox{\null\hfill$\leftrightarrows$\hfill\null}}}%
}%
}%
%verbatim-patch done.
% \UD@ifnextcharForOtherTokens peeks at the following token and
% compares it with its first argument w h i c h m u s t b e
% a s i n g l e t o k e n a n d w h i c h i n c a s e
% o f b e i n g a c h a r a c t er t o k e n --- b e
% i t e x p l i c i t o r i m p l i c i t --- m u s t
% b e a c h a r a c t e r t o k e n w h o s e e x p l i c i t
% v a r i a n t c a n b e s a f e l y g r a b b e d a s
% u n d e l i m i t e d a r g u m e n t!!! T h i s i s t h e
% c a s e, e. g., w i t h c h a r a c t er t o k e n s o f
% c a t e g o r y c o d e 12(other)!
% If both are the same it executes its second argument, otherwise
% its third.
\newcommand\UD@reserved@a{}%
\newcommand\UD@reserved@b{}%
\newcommand\UD@reserved@c{}%
\newcommand\UD@reserved@d{}%
\newcommand\UD@let@token{}%
\newcommand\UD@ifnextcharForOtherTokens[3]{%
\begingroup
\def\UD@reserved@d{#1}%
\def\UD@reserved@a{#2}%
\def\UD@reserved@b{#3}%
\futurelet\UD@let@token\UD@ifnch
}%
\newcommand\UD@ifnch{%
\expandafter\ifx\expandafter\UD@let@token\UD@reserved@d
\expandafter\UD@ifnchsnapnexttoken
\else
\expandafter\expandafter\expandafter
\endgroup\expandafter\UD@reserved@b
\fi
}%
\newcommand\UD@ifnchsnapnexttoken[1]{%
\def\UD@reserved@c{#1}%
\ifx\UD@reserved@c\UD@reserved@d
\expandafter\expandafter\expandafter
\endgroup\expandafter\UD@reserved@a
\else
\expandafter\expandafter\expandafter
\endgroup\expandafter\UD@reserved@b
\fi
#1%
}%
\begingroup
% -------------------------------------------------
% Don't indent the following code!
% Each line of the following code must end with ^^A
% vwhich serves as comment-char!
\catcode`\^^A=14\relax%
\catcode`\ =12\relax^^A
\catcode`\^^I=12\relax^^A
\catcode`\^^M=12\relax^^A
\catcode`\%=12\relax^^A
\@firstofone{^^A
\endgroup^^A
\newcommand\UD@removeotherspace{}^^A
\long\def\UD@removeotherspace#1 {#1}^^A
\newcommand\UD@removeotherreturn{}^^A
\long\def\UD@removeotherreturn#1^^M{#1}^^A
\newcommand\UD@removeothertab{}^^A
\long\def\UD@removeothertab#1^^I{#1}^^A
\newcommand\UD@removecomment{}^^A
\long\def\UD@removecomment#1#2%#3^^M{\endgroup\UD@removecommentloop{#1}{#2}}^^A
\begingroup^^A
\newcommand\UD@CheckWhetherSourceSpace[1]{^^A
\endgroup^^A
\newcommand\UD@removecommentloop[2]{^^A
\UD@ifnextcharForOtherTokens{ }{\UD@removeotherspace{\UD@removecommentloop{##1}{##2}}}{^^A
\UD@ifnextcharForOtherTokens{^^I}{\UD@removeothertab{\UD@removecommentloop{##1}{##2}}}{^^A
\UD@ifnextcharForOtherTokens{%}{\begingroup\catcode`\^=12#1\UD@removecomment{##1}{##2}}{^^A
\UD@ifnextcharForOtherTokens{^^M}{\UD@removeotherreturn{\endgroup##1{\par}}}{\endgroup##2}^^A
}}}}^^A
\newcommand\UD@CheckWhetherSourceSpace[2]{^^A
\begingroup^^A
\catcode`\ =12\relax^^A
\catcode`\^^I=12\relax^^A
\catcode`\^^M=12\relax^^A
\catcode`\%=12\relax^^A
\UD@ifnextcharForOtherTokens{%}{\begingroup\catcode`\^=12#1\UD@removecomment{##1}{##2}}{^^A
\UD@ifnextcharForOtherTokens{ }{\endgroup\UD@removeotherspace{##1{#1\ignorespaces}}}{^^A
\UD@ifnextcharForOtherTokens{^^M}{\endgroup\UD@removeotherreturn{##1{#1\ignorespaces}}}{^^A
\UD@ifnextcharForOtherTokens{^^I}{\endgroup\UD@removeothertab{##1{#1\ignorespaces}}}{\endgroup##2}^^A
}}}}}^^A
}%<- closing brace of \@firstofone's argument
\UD@CheckWhetherSourceSpace{ }%
%
% Now we are back to normal circumstances. ;-)
% -------------------------------------------------
%
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
% Commands that call \UD@CheckWhetherSourceSpace must be defined in
% terms of \outer.
% They may be used only in circumstances where their arguments get
% read and tokenized _while_ they are carried out.
% They must not be used in circumstances where the tokens probably
% forming their arguments already got tokenized and then were passed
% on to them via "spitting out" some macro-definition or
% macro-argument or the like.
% Be aware that defining in terms of \outer does not prevent
% erroneous usage to 100%,
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
%
% The first argument of \UD@CheckWhetherSourceSpace is a macro that
% performs the action in case no argument is present. It seems
% confusing but it must nonetheless process one argument.
% That argument is deliveredby \UD@CheckWhetherSourceSpace as a means
% of providing info about whether a space token or a \par-token
% needs to be appended after the action.
% The second argument of \UD@CheckWhetherSourceSpace is a macro that
% performs the action in case an argument is present.
\newcommand\XXX{}
\outer\def\XXX{%
\UD@CheckWhetherSourceSpace{\XXX@Space}{\XXX@Arg}%
}%
\newcommand\XXX@Space[1]{\fbox{Bye}#1}%
\newcommand\XXX@Arg[1]{\fbox{Hello #1}}
\makeatother
\parindent=-.66cm
\begin{document}
{\bfseries Linebreak between \verb|\XXX| and following stuff:}
\emph{The code}
\begingroup\topsep=0ex\partopsep=0ex
\begin{verbatim*}
text \XXX
1 A
\end{verbatim*}%
\endgroup
\emph{yields:}\\
text \XXX
1 A
\emph{The code}
\begingroup\topsep=0ex\partopsep=0ex
\begin{verbatim*}
text \XXX
{1} A
\end{verbatim*}
\endgroup
\emph{yields:}\\
text \XXX
{1} A
\vfill
{\bfseries Comment and linebreak between \verb|\XXX| and following stuff:}
\emph{The code}
\begingroup\topsep=0ex\partopsep=0ex
\begin{verbatim*}
text \XXX%
% Comment ^^M^^M Comment
% Comment
1 A
\end{verbatim*}
\endgroup
\emph{yields:}\\
text \XXX%
% Comment ^^M^^M Comment
% Comment
1 A
\vfill
{\bfseries Comment and linebreak and linebreak between \verb|\XXX| and following stuff:}
\emph{The code}
\begingroup\topsep=0ex\partopsep=0ex
\begin{verbatim*}
text \XXX%
% Comment ^^M^^M Comment
% Comment
1 A
\end{verbatim*}
\endgroup
\emph{yields:}\\
\begingroup\parindent=0ex
text \XXX%
% Comment ^^M^^M Comment
% Comment
1 A
\endgroup
\vfill
{\bfseries Space between \verb|\XXX| and following stuff:}
\verb*|text \XXX 1 A|: text \XXX 1 A
\verb*|text \XXX {1} A|: text \XXX {1} A
\vfill
{\bfseries Horizontal tab between \verb|\XXX| and following stuff:}
\verb*|text \XXX 1 A|: text \XXX 1 A
\verb*|text \XXX {1} A|: text \XXX {1} A
\vfill
{\bfseries Nothing between \verb|\XXX| and following stuff:}
\verb*|text \XXX1 A|: text \XXX1 A
\verb*|text \XXX{1} A|: text \XXX{1} A
\verb*|text \XXX123 A|: text \XXX123 A
\verb*|text \XXX{123} A|: text \XXX{123} A
\vfill\vfill\vfill\vfill\vfill\vfill
\end{document}
由于某种我不知道的原因,在将代码粘贴到 StackExchange 的“答案”窗口时,示例中的水平制表符会转换为四个空格。
因此,我在编译示例之前输入的水平制表符可能在复制粘贴过程中转换为空格,这意味着从 StackExchange 复制粘贴示例并进行编译的结果可能与上图略有不同。
我明确地表示,我不推荐这种方法。它有太多缺点和限制。这个例子旨在展示 (La)TeX 编程是多么令人困惑。 ;-)
已经提到了一个限制:您不能\XXX
在宏定义或宏参数中使用。最好不要让其他宏传递 的\XXX
参数或应该在 后面\XXX
但不能作为 的\XXX
参数使用的标记。
其他一些限制和缺点包括:
\XXX
执行大量临时分配。因此\XXX
无法完全扩展,并且无法在纯扩展环境中安全使用。类似的东西
\XXX
不能安全地用于移动参数中,即在写入临时文件的东西中,以便将来运行 LaTeX 时它们可以弹出到目录中或\label
- -\ref
交叉引用中。其中一个原因是 LaTeX 在将控制字标记未展开写入外部文本文件时始终附加一个空格字符。例如,
\newwrite\mywrite \immediate\openout\mywrite experiment.tex % \immediate\write\mywrite{\noexpand\XXX{Something}}% \immediate\closeout\mywrite
产生一个文件实验.tex其内容为:
\XXX␣{Something}
\XXX
注意和之间的空格字符{Something}
。