定义控制序列,之后留有空格

定义控制序列,之后留有空格

在另一个线程中出现了一个问题,是否可以定义一个控制序列,或者更好的控制单词,之后的空格字符会改变命令的含义。也就是说,两次调用\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}

例子

相关内容