在另一个线程中出现了一个问题,是否可以定义一个控制序列,或者更好的控制单词,之后的空格字符会改变命令的含义。也就是说,两次调用\foo{}
和\foo {}
应该会给出不同的结果。
有人告诉我可以使用包g
的参数选项来实现xparse
,但我找不到可行的解决方案。我能找到的最好的方法是将空格作为第二个参数处理:
\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand{\foo}{gg}{%
\IfNoValueTF{#1}{--}{(#1)}%
\IfNoValueTF{#2}{--}{(#2)}%
}
\begin{document}
\foo{}{}xy
\foo{} {}xy
\end{document}
结果是
() ()xy
()-xy
那么问题是,有没有什么方法可以定义这样的命令?恕我直言,这根本不可能,因为 TeX 的输入处理器的工作方式。在构建控制字后,TeX 会切换到“跳过空格”状态,这会吞噬所有空格字符(可能还有行尾字符)。这意味着,无论使用什么黑魔法来定义\foo
,有关空格的信息在\foo
扩展 时就已经消失了。
请注意,使用控制字符(如\f {}
)或在宏扩展之前更改 catcode(如\catcode`\ =12 \foo {}
)的解决方案不算作该问题的合法解决方案。
答案1
按照 David Carlisle 的建议更改空间的 catcode,您可以尝试以下操作:
{\catcode`\ =12\relax%
\global\let\otherspace= %
\gdef\xfoosp {\foosp}}
\def\foo{\begingroup\catcode`\ =12\futurelet\nexttok\xfoo}
\def\xfoo{%
\ifx\nexttok\otherspace
\endgroup\expandafter\xfoosp
\else
\endgroup\expandafter\foonosp
\fi}
\def\foosp#1{foo with space: arg=#1}
\def\foonosp#1{foo without space: arg=#1}
您可以按照自己的喜好定义\foosp
和\foonosp
,甚至可以使用不同数量的参数。
或者,前三行可以替换为
\begingroup\lccode`+=`\ %
\lowercase{\endgroup
\let\otherspace=+
\def\xfoosp+{\foosp}}
这使得所有定义都保持本地化。
答案2
由于您在问题中陈述的原因,这是无法做到的。
您可能已经看到关于 xparse 的讨论,即 xparse 在查找参数时是否应该跳过空格。这对您的\foo {}
情况没有影响,但对\\{}
or \\ {}
(命令符号后不删除空格,其名称为 catcode 的单个字母,而不是 11) 或 or\foo{} {}
后不删除空格会产生影响}
,因此会影响除第一个参数以外的其他参数。
LaTeX\@ifnextchar
用于预先查找*
表格和可选[]
参数以及其他内容,它是一种包装器,\futurelet
主要用于跳过空格。这意味着和\\[2pt]
是\\ [2pt]
相同的,除非amsmath
加载了它,否则它会改变这一点,使空白变得有意义,以便常见的情况如
\begin{align}
zzzaa\\
[1,2] \in zzzz
\end{align}
之后的空格\\
允许将[1,2]
视为要排版的字符而不是 的参数\\
(这将产生错误,因为它不是该参数所要求的合法长度语法)。
简而言之,在定义参数解析器时,您可以选择丢弃或保留拾取的任何参数之前的空格标记,但您不能保留不存在的内容,并且后面没有空格标记\foo
如果您确实以以下方式定义\foo
后续空格标记很重要,那么您必须通过以下方式调用它
\def\zzz#1{#1}
\zzz{\foo} {}
或者
\expandafter\foo\space{}
或者其他在该点产生空格标记的语法。
答案3
通过应用#{
-notation,您可以定义最后一个参数由左括号分隔的宏。与其他在收集参数时被删除的参数分隔符不同,TeX 会保留一个分隔的左括号。
(实际上,在 TeX 版本 3.141592653 之前的 TeX 版本中,情况发生了变化2021 TeX-TuneUp—该机制不限于打开括号字符标记。使用 TeX 版本 3.141592653 之前的 TeX 版本,您可以在定义时使用类别代码为 1 的任何标记。也可以#\WeIrd
在之后使用\let\WeIrd={
。)
分隔参数可以为空。
因此,为了从一组字符标记中获取控制序列标记,这些字符标记构成了所讨论的控制序列标记的名称,既可以用于定义,也可以用于调用该控制序列标记,您可以(通过应用 -notation #{
)发明一个控制序列,\name
该控制序列处理由括号分隔的参数和后面的非分隔参数(嵌套在括号中)。让 TeX 获取参数后,您可以让 TeX 旋转它们并应用于\csname..\endcsname
括号内提供的参数。所讨论的控制序列标记的名称也可以包含空格标记。
\makeatletter
%
\newcommand\name{}%
\long\def\name#1#{\UD@innername{#1}}%
%
\newcommand\UD@innername[2]{%
\expandafter\UD@exchange\expandafter{\csname#2\endcsname}{#1}%
}%
%
\newcommand\UD@exchange[2]{#2#1}%
%
\makeatother
\name foo{bar}
→ 扩展步骤 1:
\UD@innername{foo}{bar}
→ 扩展步骤 2:
\expandafter\UD@exchange\expandafter{\csname bar\endcsname}{foo}
→ 扩展步骤 3:
\UD@exchange{\bar}{foo}
→ 扩展步骤 4
foo\bar
:。
在扩展上下文中,您需要四个\expandafter
链才能获得结果。
由于\romannumeral
遇到非正数时不会产生任何标记,因此您可以添加一些\romannumeral
-expansion以减少\expandafter
-chains的数量。
要么这样做\romannumeral\name0 foo{bar}
。这样,只需要一个命中令牌\expandafter
的链即可。\romannumeral
或者将\romannumeral
-expansion “硬编码”在定义中 - 这样就\expandafter
需要两个 -chains。第一个用于获取 的顶层扩展\name
。第二个用于诱导\romannumeral
-expansion。
\makeatletter
%
\newcommand\name{}%
\long\def\name#1#{\romannumeral0\UD@innername{#1}}%
%
\newcommand\UD@innername[2]{%
\expandafter\UD@exchange\expandafter{\csname#2\endcsname}{ #1}%
}%
%
\newcommand\UD@exchange[2]{#2#1}%
%
\makeatother
使用这样的宏,您就不会受到特定定义命令的约束:
\name{foo}
→ \foo
。
\name\newcommand{foo}
→ \newcommand\foo
。
\name\DeclareRobustCommand{foo}
→ \DeclareRobustCommand\foo
。
\name\global\long\outer\def{foo}
→ \global\long\outer\def\foo
。
\name\expandafter{foo}\bar
→ \expandafter\foo\bar
。
\name\let{foo}=\bar
→ \let\foo=\bar
。
\name\string{foo}
→ \string\foo
。
\name\meaning{foo}
→ \meaning\foo
。
您也可以使用这样的宏来定义/调用名称包含空格的宏:
\name{foo }
→ \foo␣
。
\name\newcommand{foo }
→ \newcommand\foo␣
。
\name\DeclareRobustCommand{foo }
→ \DeclareRobustCommand\foo␣
。
\name\global\long\outer\def{foo }
→ \global\long\outer\def\foo␣
。
\name\expandafter{foo }\bar
→ \expandafter\foo␣\bar
。
\name\let{foo }=\bar
→ \let\foo␣=\bar
。
\name\string{foo }
→ \string\foo␣
。
\name\meaning{foo }
→ \meaning\foo␣
。
您还可以嵌套调用\name
:
示例 1:
\name\name\expandafter{f o o }{b a r }
处理第一个\name
收益:
\name\expandafter\f␣o␣o␣{b a r }
。
处理第二个\name
收益:
\expandafter\f␣o␣o␣\b␣a␣r␣
。
(类似地:\name\name\let{f o o }={b a r }
→ \let\f␣o␣o␣=\b␣a␣r␣
。)
示例 2:
\name\name\name\expandafter\expandafter\expandafter{f o o }\expandafter{b a r }{c r a z y }
处理第一个\name
收益:
\name\name\expandafter\expandafter\expandafter\f␣o␣o␣\expandafter{b a r }{c r a z y }
。
处理第二个\name
收益:
\name\expandafter\expandafter\expandafter\f␣o␣o␣\expandafter\b␣a␣r␣{c r a z y }
。
处理第三个\name
得到:
\expandafter\expandafter\expandafter\f␣o␣o␣\expandafter\b␣a␣r␣\c␣r␣a␣z␣y␣
。
示例 3:
在扩展上下文中,您可以使用\romannumeral
-expansion 来使事情继续进行。
\romannumeral\name\name\name0 \expandafter\expandafter\expandafter{f o o }\expandafter{b a r }{c r a z y }
\romannumeral
不断扩展,直到找到某个数字。最后它会找到数字0
,而对于非正数\romannumeral
,不会传递任何 token:
%\romannumneral-expansion in progress
\name\name\name0 \expandafter\expandafter\expandafter{f o o }\expandafter{b a r }{c r a z y }
处理第一个\name
收益:
%\romannumneral-expansion in progress
\name\name0 \expandafter\expandafter\expandafter\f␣o␣o␣\expandafter{b a r }{c r a z y }
。
处理第二个\name
收益:
%\romannumneral-expansion in progress
\name0 \expandafter\expandafter\expandafter\f␣o␣o␣\expandafter\b␣a␣r␣{c r a z y }
。
处理第三个\name
得到:
%\romannumneral-expansion in progress
0 \expandafter\expandafter\expandafter\f␣o␣o␣\expandafter\b␣a␣r␣\c␣r␣a␣z␣y␣
。
现在\romannumeral
找到数字0
。因此\romannumeral
-expansion 被中止并且\romannumeral
不会传递任何令牌:
\expandafter\expandafter\expandafter\f␣o␣o␣\expandafter\b␣a␣r␣\c␣r␣a␣z␣y␣
。
请注意,在应用时\name
内部应用的副作用是,如果在应用之前未定义相关控制序列,则将 -primitive 的含义分配给相关控制序列。即使在应用时 -parameter 具有正值,该分配也将限制在当前范围内。\csname
\csname
\relax
\csname
\globaldefs
\csname
%%\errorcontextlines=1000
\documentclass[a4paper]{article}
\usepackage{textcomp}%
\makeatletter
\newcommand\name{}%
\long\def\name#1#{\romannumeral0\UD@innername{#1}}%
\newcommand\UD@innername[2]{%
\expandafter\UD@exchange\expandafter{\csname#2\endcsname}{ #1}%
}%
\newcommand\UD@exchange[2]{#2#1}%
\makeatother
%\newcommand\foo[2]{%
\name\newcommand{foo}[2]{%
\noindent
Control sequence whose name does not contain any space.\\
Argument 1: \textit{\textlangle#1\textrangle}\\
Argument 2: \textit{\textlangle#2\textrangle}\\
}%
\name\newcommand{foo }[2]{%
\noindent
Control sequence whose name has a trailing space.\\
Argument 1: \textit{\textlangle#1\textrangle}\\
Argument 2: \textit{\textlangle#2\textrangle}\\
}%
\name\newcommand{ f o o }[2]{%
\noindent
Control sequence whose name is interspersed with spaces.\\
Argument 1: \textit{\textlangle#1\textrangle}\\
Argument 2: \textit{\textlangle#2\textrangle}\\
}%
\begin{document}
\name{foo}{Arg 1}{Arg 2}
\name{foo }{Arg 1}{Arg 2}
\name{ f o o }{Arg 1}{Arg 2}
Nesting \texttt{\string\name}:
\name\expandafter\newcommand\expandafter*\expandafter{C o N f u SiO n}\expandafter{%
\romannumeral\name\name\name0 %
\expandafter\expandafter\expandafter{F O O}\expandafter{B A R}{C R A Z Y}%
}%
\texttt{\name\string{C o N f u SiO n} is \name\meaning{C o N f u SiO n}}%
\\
Playing around with grouping:
%Be aware that \texttt itself opens up a new scope for typesetting its argument.
%\globaldefs=1\relax
\texttt{%
\begingroup\name\string{w e i r d } is \name\endgroup\meaning{w e i r d }%
}%
\texttt{%
\name\string{w e i r d } is \name\meaning{w e i r d }%
}%
\end{document}
答案4
那空间包裹?
\documentclass{standalone}
\usepackage{xspace}
\newcommand{\hello}{Hello\xspace}
\begin{document}
\hello world!
\end{document}