我需要根据条件给宏赋予特定的名称。
第一个例子有效,第二个例子无效。
使用\csname
/ \endcsname
(即示例 1) 是唯一正确的方法吗?还有其他方法可以使示例 2 正常工作吗?
\count255=0
\def\newdef{\advance\count255 by1
\expandafter\def\csname
\ifnum\count255=1
foo%
\else
bar%
\fi
\endcsname
}
\newdef{hello}
\newdef{world}
\foo\ \bar
\bye
\count255=0
\def\newdef{\advance\count255 by1
\ifnum\count255=1
\def\foo
\else
\def\bar
\fi
}
\newdef{hello}
\newdef{world}
\foo\ \bar
\bye
答案1
在您的第二个示例中,当\ifnum
接受第一个分支时,TeX 会执行\def\foo\else...
,这将带来很大问题,我们将在下面解释。接受另一个分支时,情况类似:\def\bar\fi...
。
具体发生了什么
\ifnum
让我们以测试为真的情况为例。在这种情况下,\foo
会得到定义,但不是您所期望的方式。实际定义是:
\def\foo\else\def\bar\fi{hello}
这是一个有效的宏定义,其中参数文本是\else\def\bar\fi
,并且替换文本是hello
。因此,每当\foo
在该定义之后展开 时,TeX 都希望看到\else\def\bar\fi
紧随其后的标记。但是在您的示例中, 调用之后的标记\foo
是控制空间\
,因此您会收到错误:
./faulty.tex:11: Use of \foo doesn't match its definition.
l.11 \foo\
\bar
Runaway argument?
另一种情况定义\bar
为:
\def\bar\fi{world}
当在 TeX 运行结束时展开时,由于相同的原因而失败(\bye
与预期的标记不同\fi
)。
补救
修复该问题的简单方法是在适当的时候扩展\else
或\fi
,以便在 TeX 执行之前\def
(即,\def
在所选分支的到达 TeX 的胃之前)从输入流中删除不需要的标记:
\count255=0
\def\newdef{\advance\count255 by1
\ifnum\count255=1
\expandafter\def\expandafter\foo
\else
\expandafter\def\expandafter\bar
\fi
}
\newdef{hello}
\newdef{world}
\foo\ \bar
\bye
这本质上和经典的使用 LaTeX\@firstoftwo
和\@secondoftwo
。
\else
当条件为真时,扩展将删除\else
和匹配之间的所有标记\fi
(包括两者;请注意,如果条件仍未确定,则冻结\relax
将被插入)。扩展\fi
只会将其删除。因此,在第一种情况下,输入流中剩下的是:
\def\foo{hello}
\newdef{world}
\foo\ \bar
\bye
第二种情况:
\def\bar{world}
\foo\ \bar
\bye
更进一步
我上面说过,如果你\else
在条件仍未确定时尝试扩展标记,\relax
则会插入冻结。在这种情况下,要实现这一点只需要在 后添加一个百分号\ifnum\count255=1
,因为 TeX 会继续扩展 之后的标记1
(符号后面的 〈number〉=
尚未完成),因此会扩展两个\expandafter
s前这个 〈number〉 已经被完全读取了。因此,删除这里不重要的代码的末尾部分,测试代码可以是:
\count255=0
\def\newdef{\advance\count255 by1
\ifnum\count255=1% bug here: the second <number> isn't finished!
\expandafter\def\expandafter\foo
\else
\expandafter\def\expandafter\bar
\fi
}
\newdef{hello}\show\foo
\bye
其中显示了插入的 freeze \relax
inside\foo
的定义:
> \foo=macro:
\relax \else \expandafter \def \expandafter \bar \fi ->hello.
l.9 \newdef{hello}\show\foo
答案2
问题在于,正如 Frougon 的出色回答中所述,TeX 中的条件句工作方式相当奇特:当测试结果为真时,\else...\fi
只会因扩展 而消失\else
,这会删除匹配之前的所有内容\fi
(不进行扩展)。当测试结果为假时,\else
(或\fi
)之前的所有内容都将被删除(不进行扩展)。悬空\fi
无论如何都会消失,因为它的扩展为空。
该方法\csname
有效,因为进行了扩展一路直到只剩下字符标记(其他不可扩展的标记将会引发错误Missing \endcsname
),所以\else
和\fi
将会如上所述消失。
解决方法?由于您正在进行定义,因此可扩展性不是问题,您可以使用 Knuth 首选的带有尾部递归和的构造\next
。
\count255=0
\def\newdef{\advance\count255 by1
\ifnum\count255=1
\def\next{\def\foo}%
\else
\def\next{\def\bar}%
\fi
\next
}
\newdef{hello}
\newdef{world}
\foo\ \bar
\bye