是否没有记录表明`\cs_new:Nx'(等等)的`x'类型参数相对于`#'是特殊的,我应该如何解决这个问题?

是否没有记录表明`\cs_new:Nx'(等等)的`x'类型参数相对于`#'是特殊的,我应该如何解决这个问题?

我想定义一个expl3命令:

  1. n包含参数编号的代码作为 -type 参数,例如#1
  2. 有采用此方法的变体相同的代码作为x-type 或e-type 参数;以及
  3. 不需要可扩展。

这是一个相当简单的工作示例来说明这样的命令:

\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:Npnnx

现在,虽然我不熟悉 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.pdf2020-10-05 版本的 I-IV 部分,没有记录(和系列) 和生成的变体命令在接受-type 与-type 参数\cs_new:Nx中的参数编号方面的行为方面的任何差异。xn这种行为上的差异是否没有记录,或者我遗漏了什么?

我的实际应用是-

  1. 我有一个expl3命令,它包装了xparse\NewDocumentCommand\NewExpandableDocumentCommand以定义一个-style 命令,当需要时,xparse该命令会自动替换为可扩展的等效命令hyperref\pdfstringdef
  2. 我发现这个命令定义命令能够将命令的主体代码定义为 -typexe-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}

输出为:

在此处输入图片描述

每种情况下会发生以下情况:

  1. \cs_set:Npx在定义时展开,展开\detokenize{#1}后变为##1,定义大致(除了 catcodes)与相同\cs_new:Npn\foo:n#1{[#1][##1]}\foo:n {\txt}仅在第一个括号中使用替换,因为第二个括号只是一个由三个字符组成的字符串(没有特殊含义)。

  2. 这里定义没有扩展,并且\detokenize保留在\foo:n。然后当您使用\foo:x变体时,它会扩展\txt,然后将扩展(text)传递给\foo:n,现在它会执行,正如您所见,[text][\detokenize{text}]它只会打印两次。[text]

  3. 这里我们使用了:n上面定义的变体,没有预先扩展\txt,结果是[\txt][\detokenize{\txt}]。在第一个括号中,\txt在 TeX 排版时会像往常一样扩展,但在第二个括号中,它\detokenize会启动并将宏转换\txt为字符\txt

相关内容