扩展通常被认为是 TeX 最神秘的方面之一,更像是巫术,而不是新手容易掌握的东西。网站上有很多关于扩展的精彩问题和精彩答案,但尽管我认为自己在这方面做得越来越好,但我仍然发现自己被更复杂的情况难住了。
例如,以 LaTeX 内部调用为例\in@
。Martin Scharrer 的优秀内部 LaTeX2e 宏列表解释\in@
如下:
\in@{⟨1⟩}{⟨2⟩}
检查第一个参数是否出现在第二个参数中并相应地设置开关
\ifin@
。参数未展开。这必须事先完成。
因为我想将一次可扩展的宏作为参数传递给,所以我需要在扩展\in@
之前将每个参数扩展一次。\in@
我设法处理了第一个参数需要扩展一次而第二个参数不需要扩展的情况(参见下面的代码)。
但是,我无法理解\expandafter
处理两个参数都需要扩展一次的情况所需的技巧。我认为(如果我错了请纠正我)我必须保持括号不变,直到\in@
处理完它本身,而这正是我觉得困难的地方。如果您能帮助解决这个特定的扩展问题,我将不胜感激。
\documentclass{article}
\let\ex=\expandafter % <--- more readable \expandafter
\begin{document}
\makeatletter
\in@{foo}{foo,bar,baz} % <--- works as expected
\ifin@ true\else false\fi
\def\foo{foo}
\ex\in@\ex{\foo}{foo,bar,baz} % <--- so far, so good...
\ifin@ true\else false\fi
\def\cslist{foo,bar,baz}
\in@{\foo}{\cslist} % <--- What combination of \expandafter is needed here?
\ifin@ true\else false\fi
\makeatother
\end{document}
但是,更一般地,我想知道扩展大师们在头脑中(可能在纸上)遵循什么一般程序来按所需顺序扩展标记序列。Stephan v. Bechtolsheim 的关于\expandafter
给了我一些启发,但我还远远没有掌握它。我认为分享你的技巧和食谱可能会帮助我和其他人提高我们的扩展技能。
那么,哪些认知过程可以让你\expandafter
按照自己的意愿行事?
笔记:我坚持只\expandafter
在这里使用,而不使用任何那种\edef
/\noexpand
诡计。:)
答案1
\in@{\foo}{\cslist} % <--- What combination of \expandafter is needed here?
如果\foo
首先扩展,那么我们就会遇到一个问题,即\expandafter
无法一次跳过多个令牌,而且令牌的数量也是未知的。因此,首先扩展最新的令牌。但在这个阶段,我们不能添加\expandafter
,因为我们必须首先插入\expandafter
令牌\foo
:
\ex\in@\ex{\foo}{\cslist}
然后我们添加最外层的\expandafter
链来展开\cslist
。下一行使用\EX
new\expandafter
来使各个阶段之间的差异可见:
\EX\ex\EX\in@\EX\ex\EX{\EX\foo\EX}\EX{\cslist}
结果:
\ex\ex\ex\in@\ex\ex\ex{\ex\foo\ex}\ex{\cslist}
更通用的算法是:
- 建立扩展顺序:
折叠扩展:我们必须确保知道标记的数量,
\expandafter
如果需要跳过它们,我们可以在它们之间插入。因此,像这样的构造\csname
需要在较早的级别进行扩展。这也允许在内部使用\csname
具有未知数量标记的参数,因为\csname
构造在一个扩展步骤后变成一个命令标记。Note: See also the trick below, that `\csname` can be used to expand stuff afterwards.
扩展扩展(例如
\cslist
上面的右侧)必须首先进行,因为我们无法跳过未知数量的标记。
现在我们可以
\expandafter
从头开始添加链到需要扩展的 token。上一步的顺序现在被反转了。首先是最后的插入扩展内容,例如:0. \a\b\last\c\first 1. \EX\a\EX\b\last\c\first % \EX inserted
然后我们按时间倒退来扩展最后一个之前需要扩展的 token:
=1. \ex\a\ex\b\last\c\first
2. \EX\ex\EX\a\EX\ex\EX\b\EX\last\EX\c\first % \EX inserted
=2. \ex\ex\ex\a\ex\ex\ex\b\ex\last\ex\c\first
“诡计”
有时 TeX 有助于节省一些\expandafter
。
扩展后
\csname
:我们假设
\foo
和\cslist
没有明确给出,而是通过构造\csname
:\in@{\csname foo\endcsname}{\csname cslist\endcsname}
一种简单的方法需要四波扩张:
- 扩展第一个
\csname
以获得一个令牌\foo
- 展开第二个
\csname
得到\cslist
- 扩展
\cslist
- 扩展
\foo
结果:从 2 4 -1 \expandafter
(= 15)开始。
这可以减少:TeX 扩展和之间的标记\csname
,\endcsname
直到没有可扩展的标记来形成命令序列。下面使用它来获得\cslist
及其扩展前 \foo
构造:
\csname foo\ex\ex\ex\endcsname
\ex\ex\ex}\ex\ex\ex{\csname cslist\endcsname}
整个表达式的展开如下\foo
:
\ex\ex\ex\in@\ex\ex\ex{\csname foo\ex\ex\ex\endcsname
\ex\ex\ex}\ex\ex\ex{\csname cslist\endcsname}
结果\expandafter
一共是15个。
扩展一些 TeX 基元的参数,例如
\uppercase
。假设
\foo
扩展为一个应该转换为大写的单词:\ex\uppercase\ex{\foo}
这里我们可以保存第一个
\expandafter
,因为\uppercase
已经扩展了下一个标记直到它得到左括号:\uppercase\ex{\foo}
其他原语:
\detokenize
,,\scantokens
。\message
警告:如果有人重新定义
\uppercase
为宏,这个技巧显然会失败。
答案2
您可以利用\unexpanded
:
\documentclass{article}
\makeatletter
% both arguments are expanded once
\newcommand{\xxin@}[2]{%
\begingroup\edef\x{\endgroup
\noexpand\in@{\unexpanded\expandafter{#1}}{\unexpanded\expandafter{#2}}%
}\x
}
% the first argument is expanded once
\newcommand{\xnin@}[2]{%
\begingroup\edef\x{\endgroup
\noexpand\in@{\unexpanded\expandafter{#1}}{\unexpanded{#2}}%
}\x
}
% the second argument is expanded once
\newcommand{\nxin@}[2]{%
\begingroup\edef\x{\endgroup
\noexpand\in@{\unexpanded{#1}}{\unexpanded\expandafter{#2}}%
}\x
}
\makeatletter
\begin{document}
\makeatletter
\def\foo{foo}
\def\cslist{foo,bar,baz}
\in@{foo}{foo,bar,baz}
\ifin@ true\else false\fi
\xnin@{\foo}{foo,bar,baz}
\ifin@ true\else false\fi
\nxin@{foo}{\cslist}
\ifin@ true\else false\fi
\xxin@{\foo}{\cslist}
\ifin@ true\else false\fi
\makeatother
\end{document}
这会在所有情况下打印“true”。
强制性的 LaTeX3 解决方案,其中\tl_if_in:nnTF
提供了(及变体)。
\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_generate_variant:Nn \tl_if_in:nnTF { oo }
\ExplSyntaxOff
\begin{document}
\def\foo{foo}
\def\cslist{foo,bar,baz}
\ExplSyntaxOn
\tl_if_in:nnTF{foo,bar,baz}{foo}{true}{false}\par
\tl_if_in:noTF{foo,bar,baz}{\foo}{true}{false}\par
\tl_if_in:onTF{\cslist}{foo}{true}{false}\par
\tl_if_in:ooTF{\cslist}{\foo}{true}{false}\par
\ExplSyntaxOff
\end{document}
答案3
\in@{\foo}{\cslist}
如果你不需要纯粹的扩展,并且可以负担得起任务,那么这可以简化事情
\def\tmp{\expandafter\in@\expandafter{\foo}}
\expandafter\tmp\expandafter{\cslist}
只需要四个\expandafter
答案4
我编写了一些代码来生成这样的 expandafter 链。
如果您编译以下代码...
%! TEX program = lualatex
\documentclass{article}
\usepackage{prettytok}
\ExplSyntaxOn \prettyinit: \ExplSyntaxOff
\usepackage{imperative}
\AutoGenerateVariants
\errorcontextlines=100
\begin{document}
\ExplSyntaxOn
\makeatletter
\debug_putnextwithexpand {
\in@ {\foo} {\cslist}
} {
\expandat \cslist
\expandat \foo
}
\debug_putnextwithexpand {
\in@{ ! \csname foo * \endcsname}{ ^ \csname cslist\endcsname}
} {
\expandatlabel !
\expandatlabel !
\onlabel * \expandatlabel ^
\onlabel * \expandatlabel ^
}
\debug_putnextwithexpand {
\uppercase { \foo }
} {
\after \uppercase \expandat \foo
}
\ExplSyntaxOff
\makeatother
\end{document}
您将获得可以在以下位置找到的结果代码上面的答案:
“编译器”本身仅适用于 LuaLaTeX,尽管“编译代码”可以在任何编译器中使用。
您需要从以下网址下载一些 .sty 文件https://github.com/user202729/TeXlib但实际上运行代码。
(我希望即使没有文档,代码也是不言自明的......)
\ex
附注:第一段代码少了两个,但根据人工检查,我的版本似乎是正确的。