(这个问题与错误或任何不当行为无关。我只是想知道一个令人愉快的技巧是如何完成的。)
我刚刚遇到了另一个微妙之处,xparser 处理事物的方式与旧的方式不同:
如果通过\newcommand
处理可选参数的宏进行定义,则查找可选参数的机制\protected@testopt
→ \@testopt
→\kernel@ifnextchar
无论如何都会在“查找”可选参数是否存在时删除空格。当不存在可选参数时也会删除空格。
(这不一定是误解:用\newcommand
可选参数定义的宏通常直接位于形成该宏的控制字标记之后。因此,如果该控制字标记来自读取和标记 tex 输入,则 TeX 的读取装置在标记它之后处于状态 S(跳过空格)。因此在这种情况下,tex 输入的后续空格将被跳过,以便不会有空格标记来自它们,而 → \protected@testopt
→\@testopt
可以\kernel@ifnextchar
“看到”。当不存在可选参数时,该机制的空格移除也仅在标记某个标记之后立即搜索可选参数的情况下相关,在该标记之后读取装置处于状态 m(行中间)。)
似乎解析查找可选参数的例程(与根据 定义的宏一起使用)\NewDocumentCommand
仅在存在可选参数的情况下删除空格。
似乎解析的例程用于寻找可选参数,该例程与根据 定义的宏一起使用\NewDocumentCommand
,如果不存在可选参数,则会保留空格。
我的问题是:
你能概括一下吗解析如果不存在可选参数,那么保留空格的技巧是什么?
\ifx
(在“提前查看” via \futurelet
(或 via )以检测空格之后,检查下一个标记 via 的含义\afterassignment\checknexttokensmeaning\let\nexttoken=
可能是技巧的一部分,但该空间可能不是显式空间标记而是隐式空间,例如,控制序列标记 let 等于空间标记或活动空间 let 等于空间标记。仅检查含义无法解决这些情况。)
如果你需要看到它,这里有一个小例子来展示细微的差别 - 请不要被\exchange
、\expandafter
和混淆\csname...\endcsname
- 我只是做了一些技巧以便将-NoValue-
-marker 放入定义中可选参数的默认值中\newcommand
,以便也\IfNoValueF
可以在\newcommand
-definition 中使用:
\documentclass{article}
%\usepackage{xparse}
%
\NewDocumentCommand\testdocumentcommand{mo}{\{#1\}\IfNoValueF{#2}{[#2]}}%
%
\newcommand\testnewcommand[1]{\{#1\}\testnewcommandoptarg}%
\begingroup\def\exchange#1#2{#2#1}
\expandafter\expandafter\expandafter\exchange
\expandafter\expandafter\expandafter{\csname c_novalue_tl\endcsname}%
{\endgroup\newcommand\testnewcommandoptarg[1][}]{\IfNoValueF{#1}{[#1]}}%
\begin{document}
\noindent Here the results differ:
\noindent With \verb|\testdocumentcommand|'s routine for looking for
optional arguments the space between \verb|{A}| and \verb|C| is not removed in case
of no optional argument being present.
\noindent With \verb|\testnewcommand|'s routine for looking for
optional arguments the space between \verb|{A}| and \verb|C| is removed in case
of no optional argument being present.
\medskip
\verb*|\testdocumentcommand{A} C|: \testdocumentcommand{A} C
\verb*|\testnewcommand{A} C|: \testnewcommand{A} C
\medskip\hrule\medskip
\noindent Here the results are the same as no space is present between \verb|{A}| and \verb|C|:
\verb*|\testdocumentcommand{A}C|: \testdocumentcommand{A}C
\verb*|\testnewcommand{A}C|: \testnewcommand{A}C
\medskip\hrule\medskip
\noindent Here the results are the same as the optional argument \verb|[B]| is present:
\verb*|\testdocumentcommand{A} [B]C|: \testdocumentcommand{A} [B]C
\verb*|\testnewcommand{A} [B]C|: \testnewcommand{A} [B]C
\medskip\hrule\medskip
\noindent Here the results are the same as the optional argument \verb|[B]| is present:
\verb*|\testdocumentcommand{A} [B] C|: \testdocumentcommand{A} [B] C
\verb*|\testnewcommand{A} [B] C|: \testnewcommand{A} [B] C
\end{document}
答案1
我刚刚发现xparse.dtx内部不可扩展的命令\@@_peek_nonspace:NTF
用于在搜索可选参数时保留空格。
\@@_peek_nonspace:NTF
反过来定义在ltcmd.dtx:
% \begin{macro}{\@@_peek_nonspace:NTF, \@@_peek_nonspace_remove:NTF, \@@_peek_nonspace_aux:nNNTF}
% Collect spaces in a loop, and put the collected spaces back in the
% false branch of a call to \cs{peek_meaning:NTF} or
% \cs{peek_meaning_remove:NTF}.
% \begin{macrocode}
\cs_new_protected:Npn \@@_peek_nonspace:NTF
{ \@@_peek_nonspace_aux:nNNTF { } \@@_peek_meaning:NTF }
\cs_new_protected:Npn \@@_peek_nonspace_remove:NTF
{ \@@_peek_nonspace_aux:nNNTF { } \@@_peek_meaning_remove:NTF }
\cs_new_protected:Npn \@@_peek_nonspace_aux:nNNTF #1#2#3#4#5
{
\peek_meaning_remove:NTF \c_space_token
{ \@@_peek_nonspace_aux:nNNTF { #1 ~ } #2 #3 {#4} {#5} }
{ #2 #3 { #4 } { #5 #1 } }
}
% \end{macrocode}
界面3.pdf反过来说\peek_meaning_remove:NTF
:
\peek_meaning_remove:NTF ⟨test token⟩ {⟨true code⟩} {⟨false code⟩}
测试输入流中的下一个 ⟨token⟩ 是否与 ⟨test token⟩ 具有相同的含义(由测试定义
\token_if_eq_meaning:NNTF
)。测试尊重空格,如果测试为真,则从输入流中删除⟨token⟩。然后,该函数将⟨真代码⟩或⟨假代码⟩放置在输入流中(根据测试结果)。
严格地说,\@@_peek_nonspace:NTF
它不会从标记流中收集显式空间标记,而是通过从\peek_meaning_remove:NTF
标记流中删除其含义与空间标记含义相同的标记,并积累与删除的显式/隐式空间标记一样多的显式空间标记。因此,对于不可扩展命令,不会产生隐式空间标记的情况,但会用显式空间标记替换隐式空间标记。
对于可扩展命令,如果不存在可选参数则需要保留空格的情况不会发生,因为对于可扩展命令,禁止最后一个参数为可选参数。
但是由于非可扩展命令的可选参数搜索例程用显式空格标记替换了隐式空格标记,当可选参数位于强制参数之间时,在搜索可选参数和抓取后续非可选参数方面,不可扩展命令和可扩展命令的行为存在细微的差别:
\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand\Unexptest{mom}{\{#1\}\IfNoValueF{#2}{[#2]}\{#3\}}
\NewExpandableDocumentCommand\exptest{mom}{\{#1\}\IfNoValueF{#2}{[#2]}\{#3\}}
\begin{document}
\makeatletter
The unexpandable variant does not take \verb|\@sptoken| for the second mandatory argument: The routine for searching the optional argument replaces them by explicit space tokens which (unlike implicit space tokens) in turn get discarded when \TeX\csname @sptoken\endcsname gathers the second mandatory argument.
The expandable variant takes the first \verb|\@sptoken| for the second mandatory argument because here with the routine for sarching the optional argument replacement of implicit spaces with explicit spaces does not occur.
\verb|\Unexptest{A}\@sptoken\@sptoken{C}|: \Unexptest{A}\@sptoken\@sptoken{C}
\verb|\exptest{A}\@sptoken\@sptoken{C}|: \exptest{A}\@sptoken\@sptoken{C}
\medskip\hrule\medskip
\verb|\Unexptest{A} {C}|: \Unexptest{A} {C}
\verb|\exptest{A} {C}|: \exptest{A} {C}
\end{document}