我想定义一个expl3
命令:
- 将
n
包含参数编号的代码作为 -type 参数,例如#1
; - 有采用此方法的变体相同的代码作为
x
-type 或e
-type 参数;以及 - 不需要可扩展。
这是一个相当简单的工作示例来说明这样的命令:
\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_new:Nn \example:n {
\tl_map_inline:nn { 1 2 3 } {#1}
}
\cs_generate_variant:Nn \example:n { x, e }
\begin{document}
\example:n { #1 . }
\example:e { #1 . }
% In practice, I would not actually use the `expl3'-style command in the
% document text, nor would I use the `x'-type or `e'-type variant so
% unnecessarily.
\end{document}
上述每次调用\example:(n|e)
typesets 1.2.3.
,都符合预期。但是,如果\example:e
将其更改为\example:x
,则会出现以下错误消息:
This is pdfTeX, Version 3.14159265-2.6-1.40.20 (TeX Live 2019/NixOS.org) (preloaded format=pdflatex 2020.10.14) 14 OCT 2020 23:06
[...]
! Illegal parameter number in definition of \l__exp_internal_tl.
<to be read again>
1
l.10 \example:x { #1 . }
You meant to type ## instead of #, right?
Or maybe a } was forgotten somewhere earlier, and things
are all screwed up? I'm going to assume that you meant ##.
如果\example:x { #1 . }
改为\example:x { ##1 . }
,其行为与 完全相同。此行为与、等\example:e { #1 . }
不同,其中 的最后一个参数的参数数量同样可接受,无论参数被视为-type 还是-type。\cs_new:Nn
\cs_new:Npn
n
x
现在,虽然我不熟悉 LaTeX3 的内部结构(也不特别熟悉低级 TeX),但我可以猜测此错误发生在使用生成的变体命令中,\cs_generate_variant:Nn
但不是由于\cs_new:Nx
手动\cs_new:Nx
定义的,因此\cs_new:Nx \foo:n { #1 . }
转换为涉及以下内容的内容 -
\edef\foo:n#1{#1.}
—这很好,而生成的变体命令采用x
-type 参数,显然\example:x { #1 . }
(从错误消息来看)转换为涉及以下内容:
\edef\l__exp_internal_tl{#1.}
—其中参数编号#1
显然需要转义。
抛开猜测,据我所知,查看interface3.pdf
2020-10-05 版本的 I-IV 部分,没有记录(和系列) 和生成的变体命令在接受-type 与-type 参数\cs_new:Nx
中的参数编号方面的行为方面的任何差异。x
n
这种行为上的差异是否没有记录,或者我遗漏了什么?
我的实际应用是-
- 我有一个
expl3
命令,它包装了xparse
和\NewDocumentCommand
,\NewExpandableDocumentCommand
以定义一个-style 命令,当需要时,xparse
该命令会自动替换为可扩展的等效命令hyperref
\pdfstringdef
- 我发现这个命令定义命令能够将命令的主体代码定义为 -type
x
或e
-type 以及n
-type 参数,以 的方式,会很有用\cs_new:N(n|x)
。
(编辑以尽量清晰:我曾希望定义一个类似的命令\cs_new:Npn
,因为它(1)将定义一个新的命令“D”,并且(2)能够将最后一个参数作为x
-type而不是n
-type并扩展它而不需要引用出现在该参数中的“D”(#1
等等)进行转义(##1
等等),就像\cs_new:Npx
不需要对它们进行转义一样。)
如果我使用 -typee
而不是x
-type 参数,我所做的事情会很好,实际上,我想我会使用e
-type 参数,并接受在 Debian 稳定版上造成“通常慢 200 倍以上”的性能。1鉴于 此代码,迄今为止,纯粹是为了我自己的使用,而且我使用的不是 Debian 稳定版,而是如上所示,在一个“不太稳定”的 Linux 发行版上,在我写这篇文章的时候,TeX Live 2020 确实应该在构建农场中运行,这更多的是一个理论问题,但是,尽管如此,什么是好的如果有的话,解决这种行为差异的方法不依赖任何未记录的行为,这样我就可以拥有expl3
如本文开头所述的命令,该命令有一个采用x
-type 参数的变体,允许以与基本命令对应的 -type 参数相同的方式包含参数编号n
?
1编辑以解释此参考:Debian很受欢迎Linux 发行版以其“保守”的软件更新而闻名;它的“稳定”版本提供了一个软件发行版的示例(据我所知)没有 pdfTeX 1.40.20 或更新版本。
编辑以添加:我想这是我第一次在 Stack Exchange 上提问,所以如果我做错了什么,我提前道歉!
答案1
是的,这是预料之中的。x
在\edef
底层,\edef
作为定义命令,期望 a#
后面跟着一个有效的参数编号,或者另一个#
。并且,正如你自己观察到的那样:
如果
\example:x { #1 . }
改为\example:x { ##1 . }
,其行为与完全相同\example:e { #1 . }
。
在“LaTeX3 编程语言” (expl3.pdf
)它说(我强调):
X— 完全展开的标记或带括号的标记列表。这意味着该论点被扩展为替换文本
\edef
,并将扩展作为带括号的标记列表传递给函数。扩展一直进行,直到只剩下不可扩展的标记。-x
类型参数不能嵌套。
埃— 完全展开的标记或括号标记列表,不需要双倍#
标记。此扩展非常类似于x
,但可以嵌套和不需要#
代币加倍。
这意味着x
-type 扩展大致是
\edef \l__exp_internal_tl { <tokens-to-expand> }
\l__exp_internal_tl
这就要求参数标记 ( #
) 加倍,因为\edef
参数标记后面的内容需要 。事实上,如果你尝试
\edef \l__exp_internal_tl { a ## b #1 c }
\show \l__exp_internal_tl
您将得到您所看到的错误:
! Illegal parameter number in definition of \l__exp_internal_tl.
<to be read again>
1
l.3 \edef \l__exp_internal_tl { a ## b #1
c }
?
因为 后面#
跟着1
,而 不是此定义中的有效参数编号( 之前没有参数{
)。然后 TeX 会尝试通过添加第二个缺失的 来修复您的定义#
, 的输出\show
为:
> \l__exp_internal_tl=macro:
-> a ## b ##1 c .
不幸的是,这是 TeX 的底层行为,因此回答您的第二个问题:不,您无法避免在x
-type 扩展中将参数标记加倍。您必须坚持使用e
-type 或旧版本中提供的较慢的模拟。您可以通过执行来expl3
检查您是否拥有更快的-type ;如果输出类似于,那么您就可以开始了。e
\show\expanded
\expanded=\expanded
之间的区别(我在p
这里使用 的变体来明确说明,但它的作用相同\cs_new:Nx
)
\cs_new:Npx \foo:n #1 {<stuff>#1}
\foo:n {<arg>}
和
\cs_new:Npn \foo:n #1 {<stuff>#1}
\cs_generate_variant:Nn \foo:n { x }
\foo:x {<arg>}
虽然很微妙,但却至关重要。
在第一种情况下,<stuff>#1
(<replacement text>
以正确的 TeX 术语调用)扩展为定义时间:x
中的\cs_new:Npx
扩展了<stuff>#1
,然后\foo:n
使用扩展的代码进行定义。然后当您使用 时\foo:n
,它会抓取<arg>
未扩展的 。到目前为止一切顺利。
在第二种情况下<stuff>#1
,不是在定义时展开。相反,只有当你执行时\foo:x
,它才会展开<arg>
,并且作为x
-type 展开,它期望 all#
后面跟着一个有效的参数编号或另一个#
。
命令实际上会向您表明这一点。请注意,在第一种情况下,x
在 中\cs_new:Npx
,因此将触发扩展,而在第二种情况下,x
在 中,当使用\foo:x
时将触发扩展。\foo:x
以下是一个示例文档,说明了以下内容:
\documentclass{article}
\begin{document}
\def\txt{text}
\ttfamily
\ExplSyntaxOn
% expands V here
\cs_set:Npx \foo:n #1 { [#1] [\detokenize{#1}] }
1:\foo:n {\txt}
\par
\cs_set:Npn \foo:n #1 { [#1] [\detokenize{#1}] }
\cs_generate_variant:Nn \foo:n { x }
2:\foo:x {\txt}
% ^ expands here
\par
3:\foo:n {\txt}
% ^ doesn't expand
\end{document}
输出为:
每种情况下会发生以下情况:
\cs_set:Npx
在定义时展开,展开\detokenize{#1}
后变为##1
,定义大致(除了 catcodes)与相同\cs_new:Npn\foo:n#1{[#1][##1]}
。\foo:n {\txt}
仅在第一个括号中使用替换,因为第二个括号只是一个由三个字符组成的字符串(没有特殊含义)。这里定义没有扩展,并且
\detokenize
保留在\foo:n
。然后当您使用\foo:x
变体时,它会扩展\txt
,然后将扩展(text
)传递给\foo:n
,现在它会执行,正如您所见,[text][\detokenize{text}]
它只会打印两次。[text]
这里我们使用了
:n
上面定义的变体,没有预先扩展\txt
,结果是[\txt][\detokenize{\txt}]
。在第一个括号中,\txt
在 TeX 排版时会像往常一样扩展,但在第二个括号中,它\detokenize
会启动并将宏转换\txt
为字符\
t
x
t