\ifx 比较条件与其他标记的方式是否不同?

\ifx 比较条件与其他标记的方式是否不同?

使用 的解析循环得到了意外结果\ifx。我的理解是,对于控制序列,\ifx寻找“顶级”等价性和与 相同的修饰符\long。换句话说,没有普通的扩展。但是,如果被比较的标记之一是条件标记\ifnum,例如 ,则循环失败。为什么?

下面是一个最小示例。您可以看到,除了 之外,任何 cs 标记似乎都可以很好地进行比较\ifnum。如果删除该标记,则循环将以比较顺利结束\b

\def\b{\undefined}
\def\aloop#1{%
  \ifx#1\b
    \wlog{true: \noexpand#1}%
    \let\next\empty
  \else
    \wlog{false: \noexpand#1}%
    \let\next\aloop
  \fi
  \next}

\aloop x~#{y}\alpha\relax\end\ifnum\b

结果:

false: x
false: ~
false: ##
false: y
false: \alpha
false: \relax
false: \end

! Incomplete \ifx; all text was ignored after line 16.
<inserted text>
                \fi
<to be read again>
...

答案1

你刚刚掉进了一个陷阱,我们很多人都陷入过这个陷阱。;-) 所以你不是第一个:

  • \if..- \else- -\fi匹配与组嵌套无关。
  • 当您将\if...\else...\fi-constructs 放入宏定义中时,您需要以某种方式进行,以确保在扩展宏时这些\if...\else...\fi-constructs 的任何组件都不会被错误地匹配/“平衡”,而这些组件不会被可能来自宏的参数的不匹配标记的组合所匹配\if../ \else“平衡” \fi

对于问题“\ifx 对条件的比较是否与其他标记不同”?:

-primitive\ifx使 TeX 比较(不事先触发对它们的扩展)下一个标记的含义与下一个标记的含义,并评估这些含义相等的条件是否得到满足。如果这两个标记中的一些是条件基元或不匹配\else或不匹配\fi或是根据\outer花括号定义的标记或不匹配的花括号,它也会这样做。

(严格来说,以下内容不是\ifx关于 TeX 在实际执行时所做的事情的陈述\ifx。以下是关于 TeX 在跳过\if.. ...\else...\fi条件表达式的分支时所做的事情的陈述。)

但是,当通过\ifx不匹配的\if..- 或\else- 或\fi-token 进行比较时,请注意,如果将这种比较放在另一个\if..-expression 的跳过分支中,则该跳过分支中的所有\if../ \else/\fi标记都会被考虑用于\if..- - \else-\fi匹配以找到表示跳过分支结束的\else或。对于所有用于 -forking 的原语,用于检测终止要跳过的分支的或 的例程不会检查- 或- 或-token 本身是否是 -token 的参数。\fi\if..\else\fi\if..\else\fi\ifx

例如,
\iffalse \ifx\ifx Z y\fi\fi This is not printed.\else This is printed.\fi\bye 收益为:这是打印的。

例如,
\iftrue This is printed. \else \ifx\ifx Z y\fi\fi This is not printed. \fi This is printed, too.\bye
收益为:这是打印出来的。这也是打印出来的。


当 TeX 处理你的代码时会发生什么:

因为\ifnum参数\aloop\ifx条件为假。

因此,会跳过真分支,直到找到匹配的\else\fi

因此,\if..\else..\fi在需要跳过的真正分支内部也会进行匹配,同时\if..\else..\fi匹配与组嵌套无关。

因此,第一个\ifnum来自 的\aloop参数与\else来自 的\aloop宏定义匹配 -\else来自 的\aloop宏定义的 -branch 被视为嵌套在要跳过的真正分支内的\ifnum...\else...\fi表达式(其\ifnum来自 的参数)的组成部分。在跳过该-branch 时,会发现第二个来自 的宏参数。它与来自的宏定义匹配。因此,第一个来自 的宏参数的匹配和来自 的宏定义的匹配仍然缺失。并且继续跳过被视为 的真正分支的组成部分的内容,因此,作为来自宏定义的最后一个标记,之后的标记被跳过。可能一直跳过直到到达文件末尾或定义的命令,并出现有关不完整的消息。\aloop\ifx\else\ifnum\aloop\fi\aloop\fi\ifnum\aloop\fi\ifx\aloop\ifx\next\bye\outer\ifx

\aloop\ifnum产量:

\ifx\ifnum\b %<- The condition is false, thus the true branch is not evaluated but skipped.
             %  \if...\else..\fi are matched while skipping.
             % Brace-nesting is independent from \if...\else..\fi-matching.
  \wlog{true: \noexpand\ifnum}%<- This is the 1st \ifnum
  \let\next\empty
\else %<- This matches the 1st \ifnum
  \wlog{false: \noexpand\ifnum}%<- This is the 2nd \ifnum
  \let\next\aloop
\fi%<- This matches the 2nd \ifnum
\next

% A \fi matching the 1st \ifnum is still not found here.
% A \fi matching the \ifx is still not found here.
% This situation is while skipping components of the true-branch of
% an \ifx-condition.
% Thus the error-message about incomplete `\ifx` when reaching
% either the end of the file or the next token that is defined
% in terms of `\outer`. (E.g., \bye is defined in terms of \outer.)


尝试一下:

\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\def\b{\undefined}%
\def\aloop#1{%
  \ifx\b#1\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
  {%
    \wlog{true: \detokenize{#1}}%
  }{%
    \wlog{false: \detokenize{#1}}%
    \aloop
  }%
}%

\aloop x~#{y}\alpha\relax\end\ifnum\b

\bye

我还从 改为 ,\ifx#1\b\ifx\b#1确保循环不会被诸如{\same\same}where you would get之类的参数所欺骗,该参数\ifx\same\same\b为真(并导致尝试求值\b...)。
但这仍然不完美,因为如果参数不是其含义等于 含义的单个标记,\b而是一个其含义等于 含义的前导标记,\b后面跟着一些最终进入真分支的标记,从而尝试求值,循环也可能会停止/进入真分支。
恶意用户仍然可以提供一些包含不平衡\if..- 或\else\fi-表达式的标记,#1以引发不可预测的行为和意外错误消息。

因此,您最好\b不要通过\ifx而是通过分隔参数进行检查,例如:

\long\def\fot#1#2{#1}% first of two
\long\def\sot#1#2{#2}% second of two
\long\def\gobbletoexclam#1!{}%
\long\def\CsbFork#1!\b!#2#3!!!!{#2}%
\long\def\aloop#1{%
   \ifcat$\detokenize\expandafter{\gobbletoexclam#1!}$\expandafter\fot\else\expandafter\sot\fi
   {\CsbFork!#1!{\fot}!\b!{\sot}!!!!}{\sot}%
   {\wlog{This is the stop-condition \string\b: \detokenize{#1}}}%
   {\wlog{This is not the stop condition: \detokenize{#1}}\aloop}%
}%

\aloop x~#{y}\alpha\relax\end{\b\b}{\same\same}!{!\b!}\ifnum\b

\bye
This is not the stop condition: x
This is not the stop condition: ~
This is not the stop condition: ##
This is not the stop condition: y
This is not the stop condition: \alpha 
This is not the stop condition: \relax 
This is not the stop condition: \end 
This is not the stop condition: \b \b 
This is not the stop condition: \same \same 
This is not the stop condition: !
This is not the stop condition: !\b !
This is not the stop condition: \ifnum 
This is the stop-condition \b: \b 

这样,当参数为单个标记时,循环就会停止\b。它必须恰好是\b,而不是含义等于 含义的标记\b

或者让 TeX 进行迭代,直到某个列表参数保存参数为空:

\long\def\noo#1{}% none of one
\long\def\foo#1{#1}% first of one
\long\def\fot#1#2{#1}% second of two
\long\def\initiatealoop#1{\aloop{#1}#1}%
\long\def\aloop#1#2{%
  % \fot is needed in case #1 has trailing spaces...
  \ifcat$\detokenize\expandafter{\fot#1{}{}}$%
    \expandafter\noo
  \else
    \expandafter\foo
  \fi
   {%
      \wlog{argument: \detokenize{#2}}%
      \expandafter\aloop\expandafter{\noo#1}%
   }%
}%

\initiatealoop{x~#{y}\alpha\relax\end\ifnum}%

\bye

答案2

false: x
false: ~
false: #
false: y
false: \alpha
false: \relax
false: \end
false: \ifnum
true: \b

如果#1\ifx,那么你需要跳过

\wlog{true: \noexpand\ifx}%

失败了。您可以像这样隐藏它,这样就会出现上面显示的日志。

\def\b{\undefined}
\def\aloop#1{%
\edef\qqq{\string#1}%
  \ifx#1\b
    \wlog{true: \qqq}%
    \let\next\empty
  \else
    \wlog{false: \qqq}%
    \let\next\aloop
  \fi
  \next}

\aloop x~#{y}\alpha\relax\end\ifnum\b


\bye

答案3

总结

你的

\aloop\ifnum

变成

\ifx\ifnum\b\wlog{true: \noexpand\ifnum}…\else…\fi

并且 TeX 跳过了真实文本,但是“当然”它会\fi与匹配\ifnum


更详细一点。原始条件具有以下形式:

<IF> <test> <true text> \else <false text> \fi

其中代表、、、、、、<IF>之中的一者。(还有,但它是另一种情况。)\if\ifcat\ifnum\ifdim\ifodd\ifx\iftrue\iffalse\ifcase

<test>是可以确定条件是真还是假的最短标记列表。 和 的测试为空\iftrue\iffalseTeX 在处理上面列表中的前五个命令时会进行宏扩展;例如,在\ifnum它想要查看一个整数之后,然后是和另一个整数之间的比较标记=<>

<true text>根据其中的条件平衡的最短标记列表,并以正在处理的结束\else\fi平衡结束。<IF>

如果是\ifx 执行宏扩展,并且<test>由以下两个标记组成,无论它们是什么。它们不会以任何方式进行解释,只是相互比较。

就你的情况而言

\ifx\ifnum\b\wlog{true: \noexpand\ifnum}…\else…\fi

根据 的比较规则,\ifnum和不同。 ` 是\b\ifx

\wlog{true: \noexpand\ifnum}…

问题就在这里。TeX 跳过了,<true text>不做任何解释,但考虑到条件. 因此 TeX看到 \ifnum\else并用和平衡\fi<true text><false text>不是在扫描时确定<IF>,但随着处理的进行而动态地进行。

而且,为了让工作更有趣,括号不被考虑。例如,latex.ltx充满了诸如

{\ifnum0=`}\fi
\ifnum0=`{\fi}

利用此属性在特定情况下添加不平衡的括号。

相关内容