不理解NewExpandableDocumentCommand
的用法和NewDocumentCommand
的区别。
何为完全展开,下面是一个没测试出他们区别的MWE,希望得到一个非用NewExpandableDocumentCommand
不可的使用情景.
这个链接也看的有点一头雾水url-difference
\documentclass[12pt]{article}
\usepackage{xparse}
\newcommand\foo{April Fool!}
\NewDocumentCommand{\barr}{}{\foo Ahahahaha!}
\NewDocumentCommand{\foobar}{}{\foo Ahahahaha! \barr}
\begin{document}
\foo
\barr
\foobar
\section{\foo}
\section{\barr}
\section{\foobar}
\section{\foo,\barr}
\end{document}
你使用 \tableofcontents
然后查看 .toc 文件就能看到区别。可以再定义一个 expandable document cmd,放在 \section
里,再看看目录文件。
“展开”与上下文有关,需要区分“展开”和“执行”。“展开”是宏替换,把宏替换为其它东西。
在正常的上下文中,读入、展开、执行、输出这四个依次交替执行,这是一般情形。在所谓的“完全展开”的上下文中,比如 \expanded
的“参数”(也就是 latex3的 e 型展开),还有用 \write
把“参数”写入文件等等,这些参数都会被“展开”,但不会“执行”,“赋值”操作不会生效的。还有 f 型的展开,和 \csname
\endcsname
之间的内容的展开。
与之相关的还有 protected 宏、robust 宏。前者不会在 e 型展开中被展开(但在正常的上下文中仍然会被展开)。后者是没有 protected 宏的时代为了阻止宏被展开的一个 trick。
interface3.pdf 中命令标有实心星号的可以在 e 型和 f 型中安全的展开,空心星号的可以在 e 型但不能在 f 型中安全的展开。
这些是下面要讲的内容的基础。
\NewDocumentCommand
定义的命令是 protected 宏,e 型展开不会展开它,所以写入文件时、做为 \expanded
的参数等情形,它都保持不变;而不是 protected 宏,比如 expandable document cmd,则会被替换为它的“替换文本”,这个替换是完全替换,也就是在此上下文中所有能被替换的都会替换,这种替换也就是“完全展开”。
要构造非得用 expandable document cmd 的情形,就比如在会展开而不会执行的上下文中,比如 \tl_set:Ne
,\str_set:Ne
,然后用 \tl_show:N
查看它的值。你的例子只要把 \section
替换为 \tl_set:Ne
就好了。
另外,我们知道,latex3 除了有 int 类型,还有一种特殊的类型:flag,可以自己试试在 e 型展开中 \int_incr:N
和 \flag_raise:N
有何区别。
fragile command
)与健壮(robust command
)命令该问题可谓是tse
上最常被访问的问题之一,可见链接macros - What is the difference between Fragile and Robust commands? When and why do we need protect? - TeX - LaTeX Stack Exchange本部分的内容将总结自该回答。一个宏被处理的顺序是读入、展开(expand)、执行(execute)、输出。下面是定义:
脆弱命令(Fragile Command)指的是在被展开时行为不正常,而在被执行时可以正常实现的命令。“A fragile command is one that does not behave properly when expanded, but not executed.”与之相对的,健壮命令(Robust Command)是指在展开和执行时均可以正常实现的命令。
这样的脆弱命令要正确执行,需要保证【在下一个token被展开之前】,某些命令被正确地执行,而健壮命令不需要这点。这样的命令只能在 normal(interleaved)mode
中正确执行,而在只进行展开而不执行expansion-only contexts
的某些情况下将导致错误。
“But in certain circumstances, most notably when writing to a file, TeX only expands things without executing them (the result will most probably be (re-expanded and) executed later when TeX reads the file back)”在某些情况下(例如执行tableofcontents
时),TeX
将只展开而不执行宏,此后将其写入辅助文件.toc
后再次读入,以便生成正确的目录。因此下面可以使用\section
并通过观察对比.toc
文件来区分。
关于展开与执行的一些细节比较:
Expansion v.s. Execution
input stream
),意味着将改变TeX
引擎下一步将读取到什么内容TeX
引擎将对读取到的内容进行剩下的操作
\input
是可以展开的,TeX
将会插入其他文件的内容
\def
和\kern
命令由于均为TeX
的原语(primitive),属于不可进一步展开的宏**\show \def > \def=\def. **\show \kern > \kern=\kern.
脆弱命令为什么会报错?这里直接借助回答里提供的例子来说明
\newcommand\foo[1]{\def\arg{#1}\ifx\arg\empty T\else F\fi}
%定义一个单参数命令`\foo`,当传入参数为空时返回`T`,否则返回`F`
在正常的文本模式(normal context)中,\foo{} => T
而\foo{stuff} => F
;在执行过程中TeX
读入\def
并尝试将其展开,发现其不可展开之后,将执行\def
,这时TeX
吞入并移除了作为命令\def
参数的\arg{#1}
;进一步TeX
吞入了\ifx
命令,尝试展开后执行,执行的时候吞入并移除了其参数\arg\empty
,最终按照条件判断的逻辑输出结果T
或F
;
而在只进行展开的模式(expand-only context)中,相信我们已经知道会发生什么了,在吞入\def
并展开失败之后,并不会执行\def
吞入下一个token\arg
,此时\arg
可能并未被定义,然后TeX
将会raise an error msg
;如果宏\arg
已经被定义(例如\def\arg{\abc}
),那么在这种只展开的模式内,使用\foo{}
将会得到\def \abc{}
,这将重新定义宏\abc
,而这种操作远不是我们想实现的效果,脆弱命令在expand-only context并没有那么稳健。
\protect
命令的用法从一个Eureka
大神给出的例子出发,下面是一个体现fragile command
的例子
\documentclass[12pt]{article}
\newcommand{\testA}{testA}
\NewDocumentCommand{\testB}{}{testB}
\NewExpandableDocumentCommand{\testC}{}{testC}
\begin{document}
\tableofcontents
\section{\testA}
\section{\protect\testA}
\section{\testB}
\section{\testC}
\end{document}
输出的.toc
文件的结果形如:
\contentsline {section}{\numberline {1}testA}{1}{}%
\contentsline {section}{\numberline {2}\testA }{1}{}%
\contentsline {section}{\numberline {3}\testB }{1}{}%
\contentsline {section}{\numberline {4}testC}{1}{}%
上述使用\newcommand
命令定义的\testA
不是protected
宏,因此在\section
内部会被直接展开结果为testA
;而是用\protect
命令“保护”了\testA
后其不会被展开;使用NewDocumentCommand
定义的\testB
属于\protected
宏,因此不会被展开;而使用NewExpandableDocumentCommand
定义的\testC
与\newcommnd
的行为类似,均会在\section
内部这一expand-only context中被展开,最后一起输出到.toc
文件被二次读入后执行。在这种流程下,得到的\tableofcontents
命令读入展开(但不执行)后写入.toc
文件时是否展开上有所不同。
在LaTeX2e
的源码source2e.pdf
中\protect
宏的工作原理如下:
\protect
将会被展开至\relax
,意味着什么事也不做\protect
将意味着\noexpand
,该命令将阻止下一个token的展开,因此也就是保护了下一个命令命令,让其不被展开。这正好fix了脆弱命令在只进行展开模式下的脆弱性。本部分部分参考自链接macros - When to use edef, noexpand, and expandafter? - TeX - LaTeX Stack Exchange,关于命令定义部分参考自The TeXBook chap20
\expandafter<token>
:首先读取紧随其后的一个token,同时展开该token后的另一个token,同时将原先紧随其后的token添加到展开结果之前\noexpand<token>
:展开的结果为被展开的token本身,但该token如果是一个【按照TeX展开规则一般要被展开的命令】,则其含义此时等同于\relax
\csname<string>\endcsname
:将<string>
转换为宏\<string>
,如果宏\<string>
未被定义,则默认定义为\relax
\string<token>
:将token转换为字符记号本身\def
:\def<cs><parameter text>{<replacement text>}
意为将控制序列定义为<replacement text>
的内容;\gdef
等同于命令\global\def
,这使得命令在编组外可用\edef
:\edef<cs><parameter text>{<replacement text>}
意为在定义\cs
时先完全地展开<replacement text>
再将其作为控制系列\cs
的定义;\xdef
等同于\global\edef
\expanded
是pdftex、xetex、luatex、uptex 等引入的,knuth tex、etex中都没有,其定义如下:
而查阅 The TeXBook中可以知道\message
命令的定义如下(不需要双写##
参数符号):
\message
的行为与\edef
类似, 完全展开所有参数后输出到终端, 唯一的区别是内部参数不需要双写(double)\expanded
的行为与\message
类似,这也与下文中e
型展开不需要双写一脉相承下面学习一下上面链接中有关展开的小例子
\def\examplea{more stuff}
\def\exampleb{Some stuff First \expandafter\noexpand\csname examplea\endcsname}
\edef\examplec{Some stuff Second \expandafter\noexpand\csname examplea\endcsname}
\examplea %=> more stuff
\exampleb %=> Some stuff First
\examplec %=> Some stuff Second more stuff
%(使用\edef在定义时即展开了\expandafter,此时\noexpand组织了\expandafter的展开,最终保留了\relax\examplea)
在LaTeX3
中,关于展开有以下不同的参数类型(由于我对expl3
的也不熟悉,在这里浅浅搬运并翻译一下interface3.pdf
中关于展开参数的几种类型的介绍):
c
型展开,意为csname
,是一种完全展开,是N
的变体(要求传入参数为一个token),类似于\csname<cmd string>\endcsname
,该命令将会对其内的字符串内容构建作为命令名称后展开,常用于拼接命令字符串,或通过传入命令的字符串形式而直接使用该命令o
型展开,与n
类似,会读入一个token list
的内容,但对其中的内容只进行一次展开x
(exhaustive expansion
,一展到底
),同样地读入一个token list
的内容,并将每一个参数递归地展开直至遇到不可展开的参数类型为止;与Plain-TeX
的原语(primitive
)中的命令\edef
有相同的行为。当函数的参数带有x
型参数时,该函数是不可展开的(not expandable)e
型展开,e
型展开在大多数情况下的行为与x
型展开完全相同,但与此对应的是TeX
原语中的\expanded
命令。参数变量(通常是#
)在被使用时不需要双写(doubled)。当函数使用e
型参数时,该函数可能是可以展开的(expandable
)f
(full expansion
),f
和x
型展开类似,均会对参数的宏进行递归展开,但会在展开至(遇到)第一个不可展开的 token 时停止。当传入的token list包含空格(space token)时,空格将会被吞下(gobble),并立刻停止,后面的内容保持不变,此后的其他空格也不会被移除。V
与v
型参数意为变量的值(value of variable),该参数用于获取变量的内容而不用担心数据内在的TeX 结构。V
型参数对应N
类型,要求传入一个token
(例如\foo:V \MyVariable
); 而v
型参数对应n
类型,要求传入一个token list
(例如\foo:v {MyVariable}
)【关于expl3
的问题点: V
型展开和其他类型的展开的区别是什么,何时需要使用V
型展开,何时需要使用x/e/f
型展开】\write
,\csname \endcsname
,以及\expandafter
等命令均只会进行展开而不会执行
LaTeX3
中关于f
型展开/e
型展开均不会进行执行操作,赋值操作也均不会生效
NewExpandableDocumentCommand
的例子关于构造的问题,首先感谢一下交流群友Eureka
给出的下面的例子:
\documentclass[border=6pt]{standalone}
\usepackage{tikz}
\NewDocumentCommand\testA{m}{#1}
\NewExpandableDocumentCommand\testB{m}{#1}
\newcommand{\testC}[1]{#1}
\begin{document}
\begin{tikzpicture}
%\draw[\testA{orange}] (0,0)--(1,1); % fails
\draw[\testB{magenta}] (0,0)--(1,1); % works
\draw[\testC{cyan}] (0,0)--(1,0); % works
\end{tikzpicture}
\end{document}
% Thanks Eureka!
由于在tikz
的node
命令选项(option)中的参数默认不会被展开,因此需要使用NewExpandableDocumentCommand
才可正常使用(也可以直接使用\newcommand
).
.toc
测试展开方式的差异测试代码和效果如下
\documentclass[12pt]{article}
\newcommand\foo{April Fool!}
\NewDocumentCommand{\barr}{}{\foo\ Ahahaha!}
\NewDocumentCommand{\foobar}{}{\foo\ Ahaha! \barr}
\NewExpandableDocumentCommand{\expbarr}{}{\foo\ Ahaha!}
\NewExpandableDocumentCommand{\expfoobar}{}{\foo\ Ahaha! \barr}
\begin{document}
\tableofcontents
\section{\foo}
\section{\barr}
\section{\foobar}
\section{\expbarr}
\section{\expfoobar}
\end{document}
按照上面的回答,在正常的上下文中,读入、展开、执行、输出这四个将依次交替执行。上述代码在生成初次编译生成.toc
文件写入时执行的是e
型展开【问题点:哪里的文档对此有介绍咧(?)】,而由于NewDocumentCommand
定义是的protected
宏,并不会被e
型展开展开,因此从结果来看,命令\barr
和\foobar
均不会被展开;而对于NewExpandableDocumentCommand
定义的命令,由于其定义的宏不是protected
宏,在写入.toc
文件时执行e
型展开则会被完全替换为宏的定义文本<replacement text>
,这种替换也被称为“完全展开”。、
e
型展开与\int
和\flag
对比下面尝试使用latex3
来构造例子,可见下面的文件
待补充,希望路过的各位uu不吝赐教批评指正(😭😭😭)
还挖了好多坑的,我还是一个个开新的提问尝试一下能否解决,会把新的问题移到这里的
\section
中使用\verb
以及宏包fancyvrb
提供的\Verb
报错的原因…..protected
宏和robust
宏这一trick的实现,\protect
与\protected
的区别,以及cprotect
宏包用法expl3
中f
型展开与e
型展开吞空格的差异….interface3
中提到的fully expandable functions
与restricted expandable functions
的区别与例
\bar
保持不变。\DeclareRobustCommand
定义的命令为 robust cmd。robust cmd 可以像 protected 宏一样,在某些特定的 e 型展开的上下文中保持不变(至少看起来没有什么变化),这种上下文会自动在 robust cmd 前面加上\protect
。\section
就是这种上下文。但现在我们可以直接用 protected 宏,而不必使用这种trick。“健壮的命令”一般指的就是 robust cmd。interfere3 文档的 Restricted expandable functions 和 Fully expandable functions 只是区分命令能否被 e 和 f 安全地展开,而与 robust command 无关。“受限可展的函数”只能用 e 型展开才能得到正确的结果,不能使用 f 展开;而“完全可展的函数”既可以通过 e 型展开也可以通过 f 展开得到结果,但得到的结果仍然略有区别,因为 f 展开会吞掉一个空格,这个后面讲。
\section
中不能用\verb
除了\verb
不是 robust cmd/protected 宏之外,还另有原因,但与本问题无关。\tl_set:Nn
没有 e 变体,生成一个 e 变体试试。\int_incr:N
与\flag_raise:N
?好的,我再试着理解一下然后把上面的内容再划分板块总结一下,非常感谢!
我按照类似知识笔记的形式 更新了按我理解的部分内容,现在还有一下几个小问题能否请您拨冗解答。
\section
这种命令展开的方式是latex3
中的e
型展开这一点呢,那么其他命令的展开行为是expl3中的哪一种展开?@u70550 context 就是上下文,即记号所处的环境。
\expanded
是 pdftex、xetex、luatex、uptex 等引入的,knuth tex、etex 都没有。texdoc pdftex
打开 pdftex 的文档查看使用方法。关于 f 型展开,只有第一个记号(token),或展开后的第一个记号,是空格,才会移除这个空格。
V
参数变体,它获取这个参数保存的值。例如,变量的类型为 tl 就是 tl 保存的东西,如果是寄存器,就是这个寄存器保存的值。就是不需要使用\.._use:N
来获取变量的值。只对特定的类型才有效。写入文件执行何种展开、是否会进行展开取决于命令的具体实现。对于
\section
,一般会执行\protected@edef
和\protected@write
,为了使得 robust cmd 达到应有的效果,必须使用这几个\protected@
开头的命令,而 protected 宏则没有这个要求。\expfoobar
命令在 .toc 中只展开了\foo
而没有展开\barr
是因为\barr
是 protected 宏,在 e 型展开中不会被展开。首先应该查看 l3flag 的文档,只有两页。
先猜一猜两个 tl 的值,然后再执行试试。
@u10307 ops我看了那两页关于flag文档,但是flag_xx没有提供类似
use
的方法,也没有提供e
型参数,让我感到有点迷惑。没有想到能用\tl_set:Ne
的方式来用e
型展开\flag_raise:N l_tmpa_flag
,应该是因为接触latex3时间还是太短。我会仔细研读您耐心的解答,再次向您表达我最诚挚的谢意。我又对上文进行了一些修改,关于您的回答上述回答我已经基本弄明白了,剩下的几个小坑我先找时间再根据您上面的有关说明试试,如果不行再开新的问题提问,谢谢您!
@u70550 其实还有小小问题来着.... interface.pdf中的命令有三种
想请教一下这种无任何后缀的函数在被展开时的行为如何呢?
@u70550 没有后缀的函数一般是 protected 宏。
@u70550 int 和 flag 的例子,不仅仅是看 int 和 flag 最后的结果,而是看 tl 保存的值,这个更为重要。
@u10307 非常感谢您的耐心,其实文档中输出的结果就是tl的结果?也许您指的是这个?