你的思想如何屈从于它的意志?

你的思想如何屈从于它的意志?

扩展通常被认为是 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。下一行使用\EXnew\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}

更通用的算法是:

  1. 建立扩展顺序:
  • 折叠扩展:我们必须确保知道标记的数量,\expandafter如果需要跳过它们,我们可以在它们之间插入。因此,像这样的构造\csname需要在较早的级别进行扩展。这也允许在内部使用\csname具有未知数量标记的参数,因为\csname构造在一个扩展步骤后变成一个命令标记。

    Note: See also the trick below, that `\csname` can be used to expand stuff afterwards.
    
  • 扩展扩展(例如\cslist上面的右侧)必须首先进行,因为我们无法跳过未知数量的标记。

  1. 现在我们可以\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}
    

一种简单的方法需要四波扩张:

  1. 扩展第一个\csname以获得一个令牌\foo
  2. 展开第二个\csname得到\cslist
  3. 扩展\cslist
  4. 扩展\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附注:第一段代码少了两个,但根据人工检查,我的版本似乎是正确的。

相关内容