使用 的解析循环得到了意外结果\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
。\iffalse
TeX 在处理上面列表中的前五个命令时会进行宏扩展;例如,在\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}
利用此属性在特定情况下添加不平衡的括号。