我有带有嵌套 ifx (纯 TeX)的代码:
\def\test#1#2%
{
\edef\cmpa{#1}
\edef\cmpb{x}
\ifx\cmpa\cmpb
it is x
\edef\cmpa{#2}
\edef\cmpb{1}
\ifx\cmpa\cmpb
and 1
\else
and something else
\fi
\else
it is something else
\fi
}
\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % prints: it is something else
它运行良好,但是它在 \test 中执行的操作需要大量的代码。
我尝试整理如下:
\def\ifEq#1#2%
{
\edef\cmpa{#1}
\edef\cmpb{#2}
\ifx\cmpa\cmpb%
}
\def\test#1#2%
{
\ifEq{#1}{x}%
it is x
\ifEq{#2}{1}%
and 1
\else
and something else
\fi
\else
it is something else
\fi
}
\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % -> error
\test 的代码更加简单,但是它会对 y 产生错误:
! Extra \else.
\test ...and 1 \else and something else \fi \else
it is something else \fi
l.54 \test{y}{1}
为什么第一个有效,而第二个无效?
(我必须注意,\test 和测试文本的参数可以是更长的文本,而不仅仅是一个字母;\test 的参数可以是宏)
谢谢,彼得
编辑:
谢谢大家的回答!最重要的提示是:“TeX 在错误情况下搜索 \else 或 \fi 时不会扩展宏。”接受答案中的解决方案似乎是最简单和最聪明的 :-)
编辑2:(请忽略,纯属胡言乱语)
我刚才看到,我不能if .. else .. if
这样做:
\def\Eq#1#2%
{%
TT\fi%
\edef\cmpa{#1}%
\edef\cmpb{#2}%
\ifx\cmpa\cmpb%
}
\def\test#1#2%
{
\if\Eq{#1}{x}%
it is x
\if\Eq{#2}{1}%
and 1
\else
and something else
\fi
\else
\if\Eq{#1}{y}%
it is y
\if\Eq{#2}{1}%
and 1
\else
and something else
\fi
\fi
}
我的项目的以下代码深处抛出了一个错误\test{x}{1}
。似乎现在有一个 if 未关闭。
答案1
和\test{y}{1}
你一起得到
\ifEq{y}{x}%
it is x
\ifEq{1}{1}%
and 1
\else
and something else
\fi
\else
it is something else
\fi
然后变成
\edef\cmpa{y}
\edef\cmpb{x}
\ifx\cmpa\cmpb%
it is x
\ifEq{1}{1}%
and 1
\else
and something else
\fi
\else
it is something else
\fi
执行赋值操作后,\ifx
结果为 false,因此匹配的所有内容都\else
将被丢弃无需任何宏扩展;只考虑条件,但直到第一个 之前没有任何条件\else
。所以你仍然
and something else
\fi
\else
it is something else
\fi
显示“额外\else
”。
解决方法:\Eq
改为定义。
\def\Eq#1#2{%
TT\fi
\edef\cmpa{#1}%
\edef\cmpb{#2}%
\ifx\cmpa\cmpb
}
\def\test#1#2{%
\if\Eq{#1}{x}%
it is x
\if\Eq{#2}{1}%
and 1
\else
and something else
\fi
\else
it is something else
\fi
}
\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % prints: it is something else
\bye
评论
为什么TT\fi
而不仅仅是\fi
?
当跳过\if
前面的内容时\Eq
,不会考虑 的扩展\Eq
。如果不跳过,TeX 会扩展\Eq
并找到TT\fi
。实际上,这里可以使用任何两个不可扩展的标记。如果我们省略它们会怎么样?我们仍然在评估\if
和
\if\fi
是一个未完成的条件,因此 TeX 插入了一个“冻结\relax
”标记。现在它查看
\if\relax\fi
还没有完成,所以\relax
又加了一个“冻结”。现在
\if\relax\relax\fi
完成并消失。TT\fi
条件句一开始就是完成的。
看https://tex.stackexchange.com/a/57417/4427了解有关冷冻食品的详情\relax
。
选择
如果您使用pdftex
,您还可以使这个等式文本完全可扩展:
\def\Eq#1#2{%
TT\fi
\ifnum\pdfstrcmp{#1}{#2}=0
}
\def\test#1#2{%
\if\Eq{#1}{x}%
it is x
\if\Eq{#2}{1}%
and 1
\else
and something else
\fi
\else
it is something else
\fi
}
\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % prints:
\bye
答案2
\else
TeX 在搜索或时不会扩展宏\fi
。在 的例子中y
,\ifx xy
为假。因此 TeX 会在同一级别搜索下一个\else
或而不进行扩展。因此第二个不会扩展,TeX 也不知道 内部会有。因此,下一个应该与 内部 相关的仍然是 TeX 找到的第一个。因此,第二个,也就是您期望与第一个 相关的是额外的。\fi
\ifEq
\ifx
\else
\ifEq
\else
\ifEq
\else
\ifEq
为了避免这种情况,你永远不应该在宏内部开始低级 TeX 条件,如\if
、\ifx
、\ifcase
、\ifnum
或,\iftrue
而是在宏外部完成它。相反,可以定义一个测试宏,让(预定义的)条件为或:\iffalse
\iffalse
\iftrue
\let\iftestresult\iffalse
\def\testresulttrue{\let\iftestresult\iftrue}
\def\testresultfalse{\let\iftestresult\iffalse}
\def\isEq#1#2%
{
\edef\cmpa{#1}%
\edef\cmpb{#2}%
\ifx\cmpa\cmpb
\testresulttrue
\else
\testresultfalse
\fi
}
\def\test#1#2%
{
\isEq{#1}{x}%
\iftestresult
it is x
\isEq{#2}{1}%
\iftestresult
and 1
\else
and something else
\fi
\else
it is something else
\fi
}
\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % -> error
\bye
或者使用参数定义测试:
\def\ifEq#1#2%
{%
\edef\cmpa{#1}%
\edef\cmpb{#2}%
\ifx\cmpa\cmpb
\expandafter\firstoftwo
\else
\expandafter\secondoftwo
\fi
}
\long\def\firstoftwo#1#2{#1}
\long\def\secondoftwo#1#2{#2}
\def\test#1#2%
{
\ifEq{#1}{x}{it is x \ifEq{#2}{1}{and 1}{and something else}}{is is
something else}
}
\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % -> error
\bye
答案3
原因是 TeX\if
在跳过文本时看不到宏内部的隐藏,因此它会丢失匹配的跟踪\if ... \fi
。您需要定义测试宏,以便在使用时公开原始条件。
当 egreg 发布第一个更正常的方法时,我正在做一个双选项答案,所以我只做第二个方法。(并对他的评论。)
一种方法是在定义中包含 \if-thing 作为宏参数语法
\let\then=\iftrue
\def\ifEq#1#2\then
{% no spaces
\edef\cmpa{#1}%
\edef\cmpb{#2}%
\ifx\cmpa\cmpb
}
使用类似
\ifEq{#1}{yes}\then Great!\else Sorry\fi
当被跳过时, 被\then
视为\if
并匹配后面的\fi
。当执行时,\then
被吸收并丢弃,但 内部\ifx
被扩展并匹配后面的一些\fi
。
答案4
只要您只比较单个标记,就可以\ifx...\else...\fi
通过使用替代语法创建测试来避免语法缺陷\tctestifx{tokens to compare}{true-condition}{false-condition}
。此类语法已存在于tokcycle
包中,否则,如 MWE 中所示,可以明确重新创建它。
采用这种方法,嵌套就不再是问题。
\documentclass[]{article}
%\usepackage{tokcycle}% OR ELSE
\makeatletter
\long\def\tc@exfirst#1#2{#1}
\long\def\tc@exsecond#1#2{#2}
\long\def\tctestifcon#1{#1\expandafter\tc@exfirst\else\expandafter\tc@exsecond\fi}
\long\def\tctestifx#1{\tctestifcon{\ifx#1}}
\makeatother
\begin{document}
\def\test#1#2%
{
\tctestifx{#1x}%
{it is x%
\tctestifx{#21}%
{ and 1}%
{ and something else}%
}%
{it is something else}%
}
\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % -> error
\end{document}
该tokcycle
包也适用于 Plain Tex:
\input tokcycle.tex% OR ELSE
%\catcode`\@=11
%\long\def\tc@exfirst#1#2{#1}
%\long\def\tc@exsecond#1#2{#2}
%\long\def\tctestifcon#1{#1\expandafter\tc@exfirst\else\expandafter\tc@exsecond\fi}
%\long\def\tctestifx#1{\tctestifcon{\ifx#1}}
%\catcode`\@=12
\def\test#1#2%
{
\tctestifx{#1x}%
{it is x%
\tctestifx{#21}%
{ and 1}%
{ and something else}%
}%
{it is something else}%
}
\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % -> error
\bye