一般的

一般的

使用 ε-TeX,测试 a 是否为空的常用方法<token-list>是以下测试:

\if\relax\detokenize{<token-list>}\relax
  % empty
\else
  % not empty
\fi

只要可以<token-list>安全地执行,该方法就是万无一失的\detokenize,当它被抓取作为执行测试的其他宏的参数时就是这种情况。

现在查看expl3来源我发现测试实际上是(模_:

\expandafter\ifx\expandafter\qnil\detokenize{#1}\qnil
  % empty
\else
  % not empty
\fi

其中\qnil“夸克”定义为\def\qnil{\qnil},这意味着只有当为时\ifx\qnil<token>才会成立,也就是<token>\qnil除非 #1为空;否则<token>将是任何其他(catcode-10 或 12)标记,这将使测试返回 false。

但是这个条件对于第一个测试也是正确的:\if\relax<token>只有当是另一个控制序列时才会为真<token>,而如果存在任何事物在 - 的里面\detokenize

或者是?

是否有理由选择第二种方法而不是第一种方法?是否存在其中一种会失败的极端情况?

据我所知,这两种方法都对输入的标记列表应用相同的处理,并且对于奇怪的参数都很健壮,例如\iftrue\else\fi(否则会成为一个问题)因为在任何一种情况下<token-list>都是\detokenized,所以参数几乎可以是任何东西。


动机:

我正在编写一些将使用此测试的代码,每个函数调用都应该执行几百次,因此性能很重要。根据我的测试,第一种方法略微(非常,非常比第二个稍微快一点:

\RequirePackage{l3benchmark}
\ExplSyntaxOn
\prg_new_conditional:Npnn \pho_tl_if_empty:n #1 { TF }
  {
    \if:w \scan_stop: \tl_to_str:n {#1} \scan_stop:
      \prg_return_true:
    \else:
      \prg_return_false:
    \fi:
  }
\cs_new:Npn \pho_test:N #1
  {
    \benchmark_tic:
    \int_step_inline:nn { 999999 }
      {
        #1 { } { } { } % Empty
        #1 { X } { } { } % non-empty
        #1 { \iftrue \else \fi } { } { } % just in case
      }
    \benchmark_toc:
  }
\pho_test:N \pho_tl_if_empty:nTF
\pho_test:N \tl_if_empty:nTF
\stop

输出:

(l3benchmark) + TIC
(l3benchmark) + TOC: 2.17 s
(l3benchmark) + TIC
(l3benchmark) + TOC: 2.32 s

...是的,这是一百万次重复中的百分之十五秒:-)

因此,这里的动机是想知道我是否可以使用(不)明显更快的方法而不牺牲稳健性。真实的动机在于知道这种选择将来会给我带来什么麻烦。

答案1

一般的

在考虑 TeX 代码的性能时,有几点需要考虑:

  1. 争吵会浪费时间,没有必要争吵
  2. \expandafter很慢,如果你能用同样数量的扩展来解决这个问题,它会更快,所以
    \if...
      \expandafter\@firstoftwo
    \else
      \expandafter\@secondoftwo
    \fi
    
    我们会使用(这也使用了第一点的一个方面,即如果为假,则只有真分支的内容才会被吞噬)
    \long\def\my@fi@firstoftwo\fi#1#2#3{\fi#2}
    \if...
      \my@fi@firstoftwo
    \fi
    \@secondoftwo
    
  3. 明确地将标记作为参数的分隔符吞噬比将它们作为带分隔符的参数吞噬更快,因此上述示例可以进一步优化:
    \long\def\my@fi@firstoftwo\fi\@secondoftwo#1#2{\fi#1}
    \if...
      \my@fi@firstoftwo
    \fi
    \@secondoftwo
    
    但请注意,这样一来,代码的可读性、可重用性和可维护性都会降低,因此,微小的性能提升是有代价的。

\if...可以表示任何导致 TeX 语法 if 的 if 测试,例如\ifx AB\iftrue等。

此外,\if测试可能会很慢(取决于所使用的测试),因此\detokenize,如果我们可以绕过这些问题,我们应该这样做。另一件需要考虑的事情是,如果测试的参数包含其他测试,则\if测试不健壮,或者\if\else\fi。为了克服这个问题,空参数的标准测试使用\detokenize以下方式执行参数:

\long\def\ifemptyStandard#1%
  {%
    \if\relax\detokenize{#1}\relax
      \expandafter\@firstoftwo
    \else
      \expandafter\@secondoftwo
    \fi
  }

这产生了无与伦比的稳健性,因为唯一可能导致该测试失败的参数是不平衡的输入(可以说这不是真正的参数,请参阅该答案下方的 Phelype 和 Ulrich 的评论),需要主动创建,例如\expandafter\ifemptyStandard\expandafter{\iffalse{\fi}}{true}{false}(但无论如何谁会这样做)。

在 TeX 内置的所有 if 测试中,\ifx可能是最快的。因此,一个简单的测试\ifx <some-token>#1<some-token>会非常快,但不幸的是,这并不可靠。它会失败的情况是,如果\if...\else\fi是参数的一部分,或者如果#1以 开头<some-token>(尽管我们可以让它<some-token>不太可能)。

快速地\ifempty

以下是一个快速测试,其中考虑了上述一些方面。我们不使用任何\if...测试,而是通过 TeX 的参数抓取逻辑进行分支:

\long\def\ifempty@true\ifempty@A\ifempty@B\@secondoftwo#1#2{#1}
\long\def\ifempty@#1\ifempty@A\ifempty@B{}
\long\def\ifempty#1%
  {%
    \ifempty@\ifempty@A#1\ifempty@B\ifempty@true
      \ifempty@A\ifempty@B\@secondoftwo
  }

因此,如果#1为空,\ifempty@则只会吞噬第一个\ifempty@A\ifempty@B并且\ifempty@true将被执行,吞噬以下\ifempty@A\ifempty@B\@secondoftwo和错误分支。另一方面,如果#1不为空,则直到\@secondoftwo(不包括)的所有内容都将被吞噬并\@secondoftwo执行错误分支。

这样我们就得到了一个快速测试宏(大约花费了 70% 的时间)\if\relax\detokenize{#1}\relax),它相当强大(只有包含的输入\ifempty@A\ifempty@B才会使测试失败,这种情况应该很少见)。

当然,我们可以使用比\ifempty@A和更不可能的标记\ifempty@B,例如,为什么不对两者都使用<DEL>字符,但使用不同的类别代码(这应该非常不可能成为有效参数的一部分):

\begingroup
\lccode`\&=127
\lccode`\$=127
\catcode`\&=12
\catcode`\$=11
\lowercase{\endgroup
\long\def\ifempty@true&$\@secondoftwo#1#2{#1}
\long\def\ifempty@#1&${}
\long\def\ifempty#1{\ifempty@&#1$\ifempty@true&$\@secondoftwo}
}

快速地\ifblank

作为一个小补充,我们还可以\ifblank根据上述想法创建一个快速测试。标准\ifblank如下所示:

\long\def\ifblankStandard#1%
  {%
    \if\relax\detokenize\expandafter{\@gobble #1.}\relax
      \expandafter\@firstoftwo
    \else
      \expandafter\@secondoftwo
    \fi
  }

因此本质上与 相同,\ifemptyStandard但添加了\expandafter\@gobble #1.。但是我们可以对快速测试执行相同的操作,\ifempty只需添加一些小内容(我只会将其添加到使用<DEL>标记的略微混淆的变体中)。我们不想使用一些\expandafters(请记住它们很慢),因此我们使用\ifblank@来吞噬一个标记并插入必要的测试\ifempty

\begingroup
\lccode`\&=127
\lccode`\$=127
\catcode`\&=12
\catcode`\$=11
\lowercase{\endgroup
\long\def\ifempty@true&$\@secondoftwo#1#2{#1}
\long\def\ifempty@#1&${}
\long\def\ifempty#1{\ifempty@&#1$\ifempty@true&$\@secondoftwo}
\long\def\ifblank@#1{\ifempty@&}
\long\def\ifblank#1{\ifblank@#1.$\ifempty@true&$\@secondoftwo}
}

快点\ifblank

确实可以创建一个更快的\ifblank测试,但稳定性稍差一些。之前的\ifblank测试对于必须直接相邻的标记组合会失败。如果参数包含单个标记,则此测试会失败。该测试再次使用 TeX 的参数抓取逻辑,也许方式更加巧妙。

速度优势源于以下事实:\ifempty@A如果参数不为空,TeX 必须在快速实现中重新插入第一个标记 token ( )。此实现永远不需要重新插入 token,而是在以下情况下吞噬第一个标记:#1空白时吞噬它,因为它使用两个参数,一个是普通参数,一个是分隔参数。此外,它需要的扩展步骤少了一步。

结果速度提高了约 20%。

\long\def\ifblank#1%
  {%
    \ifblank@#1\ifblank@mark\ifblank@false
      \ifblank@mark\@firstoftwo
  }
\long\def\ifblank@#1#2\ifblank@mark{}
\long\def\ifblank@false\ifblank@mark\@firstoftwo#1#2{#2}

答案2

如果你需要一个可扩展的空测试,它不需要 e-TeX 扩展,也不需要禁止标记,我可以提供这个:

%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \CheckWhetherEmpty{<Argument which is to be checked>}%
%%                   {<Tokens to be delivered in case that argument
%%                     which is to be checked is empty>}%
%%                   {<Tokens to be delivered in case that argument
%%                     which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%
%% Due to \romannumeral-expansion the result is delivered after two
%% expansion-steps/after two "hits" by \expandafter.
\chardef\stopromannumeral=`\^^00
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\CheckWhetherEmpty#1{%
  \romannumeral\expandafter\secondoftwo\string{\expandafter
  \secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \secondoftwo\string}\expandafter\firstoftwo\expandafter{\expandafter
  \secondoftwo\string}\expandafter\stopromannumeral\secondoftwo}%
  {\expandafter\stopromannumeral\firstoftwo}%
}%

就像其他任何在宏方面起作用的东西一样,这不适用于包含以下内容的参数\outer-tokens 的参数。

偏离问题中提出的要求,\CheckWhetherEmpty相当慢。

我认为\CheckWhetherEmpty这是一个毫无意义的事情/一个缓慢的解决方法,因为人们不能理所当然地认为 e-TeX 的\detokenize可用/宏编写挑战条款允许的

我强调的是要点/基本思想“命中”非空参数的第一个标记或空参数后面的右括号\string,并以此方式产生括号匹配的情况,这不是我做的,而是来自Robert R. Schneck 的\ifempty宏观

我刚刚添加了\romannumeral-expansion 和字符串化,并删除了多余的花括号,\expandafter\secondoftwo\string而不是通过 来删除多余的花括号\iffalse..\fi
我这样做是为了确保事情不会在扩展链中途中断,因为在某个阶段会出现不平衡,这可能包含在参数中,也可能由于用... \if..\else..\fi“命中”参数的第一个标记而产生。\string

除此之外,扩展级联的任何阶段中用户提供的宏参数要么被一对匹配的花括号包裹,要么已被删除。这样,如果测试在对齐或环境或类似环境&中执行,则用户提供的包含或类似内容的参数不会干扰扩展级联。tabular

为了解释测试如何工作,让我们用不同的换行方式重写它:

\long\def\CheckWhetherEmpty#1{%
  \romannumeral
  \expandafter\secondoftwo\string{%
  \expandafter\secondoftwo % <- The interesting \secondoftwo
  \expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
  \expandafter{%
  \string#1}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
  \expandafter
  \secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
  \expandafter\firstoftwo\expandafter{\expandafter
  \secondoftwo\string}%
  \expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
  {\expandafter\stopromannumeral\firstoftwo}%
}%

关于“有趣的”右括号的注释\secondoftwo表明有三个有趣的场景。

让我们看看这三种情况:


场景 1: #1不为空并且#1的第一个标记是左括号 — — 例如#1= {foo}bar

\CheckWhetherEmpty{{foo}bar}{empty}{not empty}%

步骤 1:顶层扩展将\CheckWhetherEmpty以下标记传送到 TeX 的消化道:

\romannumeral
\expandafter\secondoftwo\string{%
\expandafter\secondoftwo % <- The interesting \secondoftwo
\expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
\expandafter{%
\string{foo}bar}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 2:\romannumeral—扩展已启动:

%\romannumeral-expansion in progress:
\expandafter\secondoftwo\string{%
\expandafter\secondoftwo % <- The interesting \secondoftwo
\expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
\expandafter{%
\string{foo}bar}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 3:\expandafter“命中”\string并被{字符串化:

%\romannumeral-expansion in progress:
\secondoftwo{12%
\expandafter\secondoftwo % <- The interesting \secondoftwo
\expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
\expandafter{%
\string{foo}bar}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 4:\secondoftwo删除:{12

%\romannumeral-expansion in progress:
\expandafter\secondoftwo % <- The interesting \secondoftwo
\expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
\expandafter{%
\string{foo}bar}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 5:\expandafter-chain “hits” \string,如果参数不为空,则对参数的第一个标记进行字符串化;如果参数为空,则对右括号进行字符串化:

%\romannumeral-expansion in progress:
\secondoftwo % <- The interesting \secondoftwo
{% <- Opening brace of interesting \secondoftwo's first argument.
{%
{12foo}bar}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

第六步:有趣的\secondoftwo动作:

%\romannumeral-expansion in progress:
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 7:\expandafter“命中”\string并被}字符串化:

%\romannumeral-expansion in progress:
\secondoftwo}12% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 8:\secondoftwo删除:}12

%\romannumeral-expansion in progress:
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 9:\expandafter-chain“命中”\string并被}字符串化:

%\romannumeral-expansion in progress:
\firstoftwo{\secondoftwo}12%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤10:\firstoftwo行动:

%\romannumeral-expansion in progress:
\secondoftwo}12%
\expandafter\stopromannumeral\secondoftwo
{empty}{not empty}%

步骤 11:\secondoftwo删除:}12

%\romannumeral-expansion in progress:
\expandafter\stopromannumeral\secondoftwo
{empty}{not empty}%

步骤 12:\expandafter“点击” \secondoftwo

%\romannumeral-expansion in progress:
\stopromannumeral not empty%

步骤 13:在收集组成\romannumeral步骤 13:在收集组成⟨数字⟩\stopromannumeral-quantity TeX 现在遇到表示非正数的标记0,从而阻止 TeX 收集属于⟨数字⟩-quantity。TeX 删除构成⟨数字⟩-quantity 并且 - 因为该数量的值不是正数 - 默默地终止 -process\romannumeral而不提供任何令牌作为返回:

%\romannumeral-expansion terminated:
not empty%

场景 2: #1不为空并且#1的第一个标记不是左括号 — — 例如#1= foobar

\CheckWhetherEmpty{foobar}{empty}{not empty}%

步骤 1:顶层扩展将\CheckWhetherEmpty以下标记传送到 TeX 的消化道:

\romannumeral
\expandafter\secondoftwo\string{%
\expandafter\secondoftwo % <- The interesting \secondoftwo
\expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
\expandafter{%
\string foobar}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 2:\romannumeral—扩展已启动:

%\romannumeral-expansion in progress:
\expandafter\secondoftwo\string{%
\expandafter\secondoftwo % <- The interesting \secondoftwo
\expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
\expandafter{%
\string foobar}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 3:\expandafter“命中”\string并被{字符串化:

%\romannumeral-expansion in progress:
\secondoftwo{12%
\expandafter\secondoftwo % <- The interesting \secondoftwo
\expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
\expandafter{%
\string foobar}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 4:\secondoftwo删除:{12

%\romannumeral-expansion in progress:
\expandafter\secondoftwo % <- The interesting \secondoftwo
\expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
\expandafter{%
\string foobar}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 5:\expandafter-chain “hits” \string,如果参数不为空,则对参数的第一个标记进行字符串化;如果参数为空,则对右括号进行字符串化:

%\romannumeral-expansion in progress:
\secondoftwo % <- The interesting \secondoftwo
{% <- Opening brace of interesting \secondoftwo's first argument.
{%
f12oobar}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

第六步:有趣的\secondoftwo动作:

%\romannumeral-expansion in progress:
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 7:\expandafter-chain“命中”\string并被}字符串化:

%\romannumeral-expansion in progress:
\firstoftwo{\secondoftwo}12%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤8:\firstoftwo行动:

%\romannumeral-expansion in progress:
\secondoftwo}12%
\expandafter\stopromannumeral\secondoftwo
{empty}{not empty}%

步骤 9:\secondoftwo删除:}12

%\romannumeral-expansion in progress:
\expandafter\stopromannumeral\secondoftwo
{empty}{not empty}%

步骤 10:\expandafter“点击” \secondoftwo

%\romannumeral-expansion in progress:
\stopromannumeral not empty%

\romannumeral步骤 11:在收集组成的代币的过程中,仍然处于扩展事物的阶段⟨数字⟩\stopromannumeral-quantity TeX 现在遇到表示非正数的标记0,从而阻止 TeX 收集属于⟨数字⟩-quantity。TeX 删除构成⟨数字⟩-quantity 并且 - 因为该数量的值不是正数 - 默默地终止 -process\romannumeral而不提供任何令牌作为返回:

%\romannumeral-expansion terminated:
not empty%

场景 3: #1是空的:

\CheckWhetherEmpty{}{empty}{not empty}%

步骤 1:顶层扩展将\CheckWhetherEmpty以下标记传送到 TeX 的消化道:

\romannumeral
\expandafter\secondoftwo\string{%
\expandafter\secondoftwo % <- The interesting \secondoftwo
\expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
\expandafter{%
\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 2:\romannumeral—扩展已启动:

%\romannumeral-expansion in progress:
\expandafter\secondoftwo\string{%
\expandafter\secondoftwo % <- The interesting \secondoftwo
\expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
\expandafter{%
\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 3:\expandafter“命中”\string并被{字符串化:

%\romannumeral-expansion in progress:
\secondoftwo{12%
\expandafter\secondoftwo % <- The interesting \secondoftwo
\expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
\expandafter{%
\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 4:\secondoftwo删除:{12

%\romannumeral-expansion in progress:
\expandafter\secondoftwo % <- The interesting \secondoftwo
\expandafter{% <- Opening brace of interesting \secondoftwo's first argument.
\expandafter{%
\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1).
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

步骤 5:\expandafter-chain “hits” \string,如果参数不为空,则对参数的第一个标记进行字符串化;如果参数为空,则对右括号进行字符串化:

%\romannumeral-expansion in progress:
\secondoftwo % <- The interesting \secondoftwo
{% <- Opening brace of interesting \secondoftwo's first argument.
{%
}12% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is an opening brace (Scenario 1) got stringified.
\expandafter
\secondoftwo\string}% <- Closing brace of interesting \secondoftwo's first argument in case #1's first token is not an opening brace (Scenario 2).
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}%
\expandafter\stopromannumeral\secondoftwo}% <- Closing brace of interesting \secondoftwo's first argument in case #1 is empty (Scenario 3).
{\expandafter\stopromannumeral\firstoftwo}%
{empty}{not empty}%

第六步:有趣的\secondoftwo动作:

%\romannumeral-expansion in progress:
\expandafter\stopromannumeral\firstoftwo
{empty}{not empty}%

步骤 7:\expandafter“点击” \firstoftwo

%\romannumeral-expansion in progress:
\stopromannumeral empty%

\romannumeral步骤 8:在收集组成的代币的过程中,仍然处于扩展事物的阶段⟨数字⟩\stopromannumeral-quantity TeX 现在遇到表示非正数的标记0,从而阻止 TeX 收集属于⟨数字⟩-quantity。TeX 删除构成⟨数字⟩-quantity 并且 - 因为该数量的值不是正数 - 默默地终止 -process\romannumeral而不提供任何令牌作为返回:

%\romannumeral-expansion terminated:
empty%

基于此,您可以执行\ifblank如下测试:

%%-----------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%-----------------------------------------------------------------------------
%% -- Take advantage of the fact that TeX discards space tokens when
%%    "fetching" _un_delimited arguments: --
%% \CheckWhetherBlank{<Argument which is to be checked>}%
%%                   {<Tokens to be delivered in case that
%%                     argument which is to be checked is blank>}%
%%                   {<Tokens to be delivered in case that argument
%%                     which is to be checked is not blank}%
\long\def\CheckWhetherBlank#1{%
  \romannumeral\expandafter\expandafter\expandafter\secondoftwo
  \expandafter\CheckWhetherEmpty\expandafter{\firstoftwo#1{}.}%
}%

\firstoftwo仅当为空白时,点才会成为 的第二个参数#1。因此,仅当为空白时,点才会被删除#1
因此,仅当为#1空白时, 的参数才为空\CheckWhetherEmpty


根据实现的要点,\CheckWhetherEmpty您可以实现检查非分隔参数的第一个标记是否是类别代码 1(开始组)的显式字符标记:只需通过附加一个点来确保在\string执行“有趣的”之前执行的\secondoftwo永远不会“击中”右括号(这意味着消除场景 3)并实现场景 1 和场景 2 之间的分叉:

%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%-----------------------------------------------------------------------------
%% \CheckWhetherBrace{<Argument which is to be checked>}%
%%                   {<Tokens to be delivered in case that argument
%%                     which is to be checked has leading
%%                     catcode-1-token>}%
%%                   {<Tokens to be delivered in case that argument
%%                      which is to be checked has no leading
%%                      catcode-1-token>}%
%%
%% Due to \romannumeral0-expansion the result is delivered after two
%% expansion-steps/after two "hits" by \expandafter.
%%
\long\def\CheckWhetherBrace#1{%
  \romannumeral\expandafter\secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\firstoftwo\expandafter{\expandafter
  \secondoftwo\string}\expandafter\stopromannumeral\firstoftwo}%
  {\expandafter\stopromannumeral\secondoftwo}%
}%

相关内容