嵌套 \ifx 带有额外的 \else 错误

嵌套 \ifx 带有额外的 \else 错误

我有带有嵌套 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

\elseTeX 在搜索或时不会扩展宏\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

相关内容