如何控制宏在另一个定义期间是否扩展以及何时扩展?

如何控制宏在另一个定义期间是否扩展以及何时扩展?

我希望定义一个元命令,无论何时调用,它都会定义另一个命令的实例;但是这个内部命令必须包含一个渐进的数字(有点类似于编号方程、定理等的情况)。

但是,在我迄今为止的尝试中,数字似乎被评估得太晚了(在处理内部命令时,而不是在定义它时),因为所有内部命令都以计数器的最终值(偏差一,不少于一)而不是定义每个内部命令时的计数器值。

也许解释这个问题的最好方法是用一个带注释的小例子,它由两个文件组成。第一个文件应该被其他几个文件包含。第二个文件是包含第一个文件的几个其他文件之一。

第一个文件(testdef.tex):

% This file may be included by several other files.

% In this example, we are creating a number of proverb-like "rules"
% that other documents will need to refer to.

% All the rules are defined in this file, each with a RULENAME and a
% text. Each rule is given a number, starting from 1, based on the
% order in which they appear here. By defining a rule, we create a new
% internal macro \ruleRULENAME that expands to the text of the rule,
% prefixed by the rule number. The command \showrule{RULENAME}
% displays the rule in the approved way (say in a box) and defines the
% anchor for the label RULENAME: \ref{RULENAME} gives the rule number
% and \pageref{RULENAME} gives the page where \showrule{RULENAME} was
% first issued. Because \showrule{RULENAME} is the anchor for the
% matching label, you cannot issue \showrule{RULENAME} more than
% once. If you need to show the rule without (re)defining the label, use
% \showrulenolabel{RULENAME}.

\newcounter{rule}

\newcommand{\defrule}[2]{
  \stepcounter{rule}
  \expandafter\newcommand\csname rule #1\endcsname{\textbf{Rule \therule:} #2}
}

\newcommand{\showrulenolabel}[1]{
  \noindent\framebox{\csname rule #1\endcsname}
}

\newcommand{\showrule}[1]{
  \label{#1}
  \refstepcounter{rule}
  \showrulenolabel{#1}
}

% the actual rules are defined here

\defrule{vegetables}{Eat more vegetables}

\defrule{smile}{Smile}

\defrule{stairs}{Take the stairs}

第二个文件,包含第一个文件:

\documentclass{report}
\begin{document}
\input{testdef}

This is one of several documents that will include the rules file. 

In here I should be able to display any rule by name, for example\ldots

\showrule{smile} (should be 2)

%The actual text of that rule is ``\rulesmile'' but I am not really supposed
%to be using that. Strangely, if I do, it says undefined control sequence.

The rules I know about are, in order:

\showrulenolabel{vegetables} (should be 1)

\showrulenolabel{smile} (should be 2)

\showrulenolabel{stairs} (should be 3)

and I should be able to say that 

\end{document}

处理第二个文件会生成一个文档,其中所有规则的编号都为 4,而不是预期的 1、2、3。

我对 Latex 相当熟悉,但对普通的 Tex 不太熟悉,我怀疑这就是我无法得到正确答案的原因。我找到的最相关的建议来自另一个问题我尝试应用其中的一些,但是我对原始 Tex 命令的理解有限,导致我无法获得预期的结果。

答案1

你需要扩大定义你的宏的完整:这需要\edef(一个 TeX 原语):

\newcommand{\defrule}[2]{%
  \stepcounter{rule}%
  \expandafter\edef\csname rule #1\endcsname{\noexpand\textbf{Rule \therule:} #2}%
}

由于这将扩大一切,你可能需要对 采取一些预防措施#2。假设 e-TeX 可用(它在任何现代系统上都可以):

\newcommand{\defrule}[2]{%
  \stepcounter{rule}%
  \expandafter\edef\csname rule #1\endcsname{\noexpand\textbf{Rule \therule:}   
    \unexpanded{#2}}%
}

如果 e-TeX 不可用,则需要使用令牌寄存器进行更多工作。

答案2

免费\unexpanded版本:

\newcommand{\defrule}[2]{%
  \stepcounter{rule}%
  \edef\temp{\noexpand\textbf{Rule \therule:}}%
  \expandafter\newcommand\csname rule#1\expandafter\endcsname
    \expandafter{\temp\space #2}}

这里我们使用\expandafterjust before\endcsname来访问下一个将要扩展的元素\temp(使用 before 定义\edef,因此数字是正确的)。该\expandafter\endcsname技巧在许多情况下非常方便。

这还有一个好处,我们可以执行\newcommand命令是否尚未定义的检查。

如果担心某些控制序列被重新定义,那么可以使用适当的临时控制序列

\makeatletter
\newcommand{\defrule}[2]{%
  \stepcounter{rule}%
  \edef\@tempa{\noexpand\textbf{Rule \therule:}}%
  \expandafter\newcommand\csname rule#1\expandafter\endcsname
    \expandafter{\@tempa\space #2}}
\makeatother

控制序列\@tempa多次使用仅是临时使用,使用后任何人都不应该依赖它的含义。

它是如何工作的?首先\defrule收集它的参数;假设调用的是\defrule{smile}{Just smile!}

第一个任务是步进计数器rule。假设当前值变为 42。然后我们执行

\edef\@tempa{\noexpand\textbf{Rule \therule:}}

在当前条件下相当于

\def\@tempa{\textbf{Rule 42:}}

因为使(仅一次)\noexpand的可扩展性无效。\textbf

现在到了有趣的部分:

\expandafter\newcommand\csname <token list>\endcsname

首先进行扩展\csname,以便\newcommand呈现一个可以操作的真实标记。<token list>一直展开到包括立即起作用的,触发括号后\expandafter的扩展。然后开始行动并发现\@tempa\newcommand

\newcommand\rulesmile{\textbf{Rule 42:}\space Just smile!}

相关内容