包含 ifnum 的嵌套宏有问题

包含 ifnum 的嵌套宏有问题

我在使用包含 ifnum 的宏时遇到了问题。使用命令 \noexpand 后,一个宏可以正常工作,但嵌套两个宏无法编译,而是会导致错误消息


额外\fi。 \fi


如下面代码所示。如何才能让嵌套宏正常工作?

\documentclass{article}

\newcommand{\IFCONDA}{\noexpand{\ifnum1>0}}
\newcommand{\IFCONDB}{\noexpand{\ifnum1=0}}

\begin{document}
\IFCONDA
hello1
\else % \IFCONDA
world1

% next three lines cause trouble (nested ifnum)
\IFCONDB
hello2
\fi % \IFCONDB
\fi % \IFCONDA
\end{document}

答案1

令牌\noexpand什么也不做,因为{不可扩展。因此您的代码与没有它时一样。顺便说一句,用括号括住测试并不是好的编程方式。

所以我会假装\noexpand不存在。你得到

{\ifnum1>0}
hello1
\else
world1
\par
\IFCONDB
hello2
\fi
\fi

(换行只是为了清晰起见)。测试返回 true,因此您仍然

}
hello1
\else
world1
\par
\IFCONDB
hello2
\fi
\fi

该支架与原来的支架保持平衡,然后被移除。

现在 TeX 发送“hello1”和一个空格进行排版,并保留

\else
world1
\par
\IFCONDB
hello2
\fi
\fi

由于测试结果为真,TeX 会丢弃从\else到 匹配的所有内容\fi 无扩展,所以\IFCONDB被丢弃并且匹配的\fi是第一个。

由于从未见过\fi测试,因此您仍然会遇到与任何内容都不匹配的情况。\ifnum1=0

如果第一个测试是,情况会有所不同\ifnum1<0,因为在这种情况下,\IFCONDB在处理“错误文本”时会扩展,但你会得到

### simple group (level 2) entered at line 14 ({)
### simple group (level 1) entered at line 8 ({)

由于牙套不匹配。

我无法再多说,因为不清楚你的目的是什么。

可以使用条件样式,但需进行以下更改:

\documentclass{article}

\newcommand{\CONDA}{TT\fi\ifnum1>0 }
\newcommand{\CONDB}{TT\fi\ifnum1=0 }

\begin{document}

\if\CONDA
hello1
\else % \IFCONDA
world1

% next three lines cause trouble (nested ifnum)
\if\CONDB
hello2
\fi
\fi % \IFCONDA

\end{document}

值得研究它的工作原理……

答案2

您不能隐藏\if宏中的原始标记(除非您小心隐藏匹配的\elseand \fi),使用采用两组{}真和假的语法要简单得多,例如\ifthenelse

\documentclass{article}
\usepackage{ifthen}
\newcommand{\IFCONDA}{\ifthenelse{1>0}}
\newcommand{\IFCONDB}{\ifthenelse{1=0}}

\begin{document}
\IFCONDA
{hello1}
{%
world1

% next three lines cause trouble (nested ifnum)
\IFCONDB
{hello2}
{}%
}% \IFCONDA
\end{document}

答案3

虽然您的代码永远无法工作,原因如 egreg 和 David 所述。可以使用 TeX 宏和巧妙选择的参数文本来近似地实现它(请参阅以下内容之一:TeXbook 第 20 章、TexbyTopic 11.5,或TeX 宏的工作原理:第 4 部分)。

我提供了两种可能的解决方案,不过我想说你可能不想这样做,最好ifthen按照 David 的建议使用。首先,我展示了一个条件是静态的解决方案(例如 1>0),然后展示了一个更通用的解决方案,可以在更现实的条件下工作。

\documentclass{article}

\edef\ifconda #1\fi{\ifnum1>0 #1 \fi}
\edef\ifelseconda #1\else#2\fi{\ifnum1>0 #1 \else #2 \fi}

\edef\ifcondb #1\fi{\ifnum1=0 #1 \fi}
\edef\ifelsecondb #1\else#2\fi{\ifnum1=0 #1 \else #2 \fi}

\begin{document}

% original test case
\ifelseconda{
hello1
}\else{
world1
\ifcondb{
hello2
}\fi
}\fi

---

% more general test case 
\ifelseconda{
    \ifelsecondb{
        hello1
    }\else{
        hello2
    }\fi
}\else{
    \ifelsecondb{
        hello3
    }\else{
        hello4
    }\fi
}\fi

\ifelseconda{
    \ifelseconda{
        world1
    }\else{
        world2
    }\fi
}\else{
    \ifelseconda{
        world3
    }\else{
        world4
    }\fi
}\fi

\end{document}

排版:

hello1
---
hello2
world1

请注意,这仅在定义等时可以并且应该评估条件时才会起作用\ifconda......因为\edef在定义时会扩展其替换文本(例如\edef\ifelseconda #1\else#2\fi{\ifnum1>0 #1 \else #2 \fi}扩展到\edef\ifelseconda #1\else#2\fi{#1})。

如果您不想在定义时评估条件,则可以使用简单的\def,但我们需要更改拼写\fi以避免需要过多的令牌杂耍(这可能是可能的,但超出了我想要推理的范围)。

\documentclass{article}
\def\ifconda #1\fii{\ifnum1>0 #1 \fi}
\def\ifelseconda #1\else#2\fii{\ifnum1>0 #1 \else #2 \fi}

\def\ifcondb #1\fii{\ifnum1=0 #1 \fi}
\def\ifelsecondb #1\else#2\fii{\ifnum1=0 #1 \else #2 \fi}

\begin{document}

% original test case
\ifelseconda{
hello1
}\else{
world1
\ifcondb{
hello2
}\fii
}\fii

...

答案4

\if..\else..\fi-matching 只适用于 TeX 的\if..\else\fi-primitives。
\if..\else..\fi-matching 不适用于名称中包含短语if或的宏标记。IF

对于\if..\else..\fi可扩展标记的匹配扩展,通常仅考虑在分支中表示实际情况的标记,因此这些标记不会被丢弃。

为了您的代码更清晰,我们将 重命名\IFCONDA\MACROA\IFCONDB\MACROB

\documentclass{article}

\newcommand{\MACROA}{\noexpand{\ifnum1>0}}
\newcommand{\MACROB}{\noexpand{\ifnum1=0}}

\begin{document}
\MACROA
hello1
\else
world1

% next three lines cause trouble (nested ifnum)
\MACROB
hello2
\fi
\fi
\end{document}

因此,当处理完序言后\begin{document},您将得到以下内容:

\MACROA
hello1
\else
world1

% next three lines cause trouble (nested ifnum)
\MACROB
hello2
\fi
\fi

现在\MACROA已展开,并且您有如下内容:

\noexpand{\ifnum1>0}%
hello1
\else
world1

% next three lines cause trouble (nested ifnum)
\MACROB
hello2
\fi
\fi

现在\noexpand“告诉”TeX 如果后续标记可扩展,则不要扩展该标记。但是后续标记{不可扩展,因此\noexpand实际上没有效果:

{\ifnum1>0}%
hello1
\else
world1

% next three lines cause trouble (nested ifnum)
\MACROB
hello2
\fi
\fi

现在由于{TeX 打开了一个局部作用域,然后遇到了\ifnum1>0。请注意,括号嵌套 ( {... }) 独立于\if..-- \else-nesting \fi!\ifnum1>0为真,因此 TeX 会处理所有内容,直到找到匹配\fi或匹配的\else(如果\fi遇到匹配,则将其删除。如果\else找到匹配,则该匹配以及该匹配和该匹配\else之间的所有内容以及匹配本身都将被删除。)\else\fi\fi因此 TeX 现在遇到}并关闭了局部作用域。然后 TeX 遇到字符标记hello1空格标记。(空格标记的出现是因为当 .tex 输入文件中发现输入行结束时,TeX 通常将空格标记附加到标记流中,而附加到标记流中的最后一个标记不是控制字标记。1不是控制字标记而是字符标记。)这些标记会被进一步处理,因此它们可能会影响排版/创建 .pdf 文件的结果。然后\else找到匹配项。因此,匹配项\else和匹配项与匹配项之间的所有内容以及\fi匹配项\fi本身都会被丢弃。第一个\fi被视为匹配项\fi。第二个\fi被视为额外的\fi


为了避免在\if..- \else- -\fi嵌套时产生混淆,在许多情况下你可以这样做:

\documentclass{article}

\newcommand\FirstOfTwo[2]{#1}%
\newcommand\SecondOfTwo[2]{#2}%
\newcommand\CheckWhetherConditionA{\ifnum1>0 \expandafter\FirstOfTwo\else\expandafter\SecondOfTwo\fi}%
\newcommand\CheckWhetherConditionB{\ifnum1=0 \expandafter\FirstOfTwo\else\expandafter\SecondOfTwo\fi}%

\begin{document}
\CheckWhetherConditionA{hello1 }{world1\par\CheckWhetherConditionB{hello2}{}}%
\end{document}

(我说“在许多情况下”是因为\FirstOfTwo/ \SecondOfTwo-方法不适用于选择\verb|...|-命令或verbatim-环境或类似物的两个实例之一,它依赖于这些命令/环境引入的类别代码制度的变化,这些变化在从 .tex 输入文件读取并标记其参数/内容时生效。)

相关内容