理论问题/争论点:
场景:你希望全局定义一个命令。但你希望只有当它在当前范围内未定义时才定义它以及所有上级范围。
问:有没有可靠地做到这一点的策略?
问题:您能否可靠地检测出当前范围内未定义的命令在所有上级范围内是否也未定义?
这是一个最小的例子,它不是这样做:
\documentclass{article}
\newcommand\foo{This is foo's original definition}
\makeatletter
\begin{document}
\@ifundefined{foo}{\gdef\foo{This redefinition of foo is not applied.}}{}
\begingroup
\let\foo=\UndeFiNeD
\@ifundefined{foo}{\gdef\foo{This redefinition of foo was applied although foo was defined in a superordinate scope.}}{}
\endgroup
\show\foo
\end{document}
终端输出:
> \foo=macro:
->This redefinition of foo was applied although foo was defined in a superordin
ate scope..
l.32 \show\foo
答案1
在 luatex 中,您可以做类似的事情,通过查看哈希表来检查该命令是否曾经定义过。
\documentclass{article}
\def\test#1{%
\directlua{
local tst = false
for i,v in pairs (tex.hashtokens()) do
if v=='foo' then tst=true end
end
if tst then
print('\string\n#1:' .. 'DEFINED')
else
print('\string\n#1:' .. 'UNDEFINED')
end
}
}
\test{A}
\newcommand\foo{This is foo's original definition}
\test{B}
\makeatletter
\begin{document}
\@ifundefined{foo}{\gdef\foo{This redefinition of foo is not applied.}}{}
\begingroup
\test{C}
\let\foo=\UndeFiNeD
\test{D}
\@ifundefined{foo}{\gdef\foo{This redefinition of foo was applied although foo was defined in a superordinate scope.}}{}
\endgroup
\test{E}
\show\foo
\let\foo\undefinedagain
\test{F}
\end{document}
但请注意,最后一个条目 F 仍然是 DEFINED,也就是说,\foo
即使它在顶层未定义,仍然有一个哈希表条目。
答案2
以下是我评论中这个想法的简单实现。
我们检查宏是否已定义。如果已定义,则发出警告,忽略赋值。如果没有定义,则现在在本地进行定义,并创建一个全局内部宏,将其从当前组中偷运出来\aftergroup
。在该宏中,我们只需再次调用原始宏即可。
我们可以通过检查全局内部宏是否已定义来检查我们是否处于宏的第一次调用。如果这不是第一次调用,则内部宏将已被定义,并且我们错误地进行了赋值,因此现在我们抛出一个错误(当然,您可以更改这一点)。最后,如果我们到达顶层,我们在最后一次进行局部定义后取消定义内部宏(前提是从未达到错误情况,在这种情况下内部宏仍将被定义)。
\documentclass[]{article}
\makeatletter
\@ifdefinable\globalnewassignment
{
\protected\def\globalnewassignment#1#2%
{%
\ifdefined#1%
\ifx#1\relax
\expandafter\@gobble
\else
\@ifundefined{globalnewassignment@@\detokenize{#1}}
{%
\PackageWarning{globalnewassignment}
{Macro \string#1 is already defined}%
}%
{%
\PackageError{globalnewassignment}
{Macro \string#1 was already defined, now it's too late}{}%
}%
\fi
\else
\globalnewassignment@do#1{#2}%
\fi
}
}
\protected\def\globalnewassignment@do#1#2%
{%
#2%
\ifnum\currentgrouplevel>\z@
\expandafter\xdef\csname globalnewassignment@@\detokenize{#1}\endcsname
{\unexpanded{\globalnewassignment#1{#2}}}%
\expandafter
\aftergroup\csname globalnewassignment@@\detokenize{#1}\endcsname
\else
\global\expandafter\let
\csname globalnewassignment@@\detokenize{#1}\endcsname
\globalnewassignment@undefined
\fi
}
\newcommand\foo{This is foo's original definition}
\begin{document}
\globalnewassignment\foo{\def\foo{This is a redefinition}}
\begingroup
\let\foo\UNDEFined
\globalnewassignment\foo{\def\foo{This is a redefinition}}
\endgroup
\end{document}
答案3
如果您依赖于表示定义状态的标志宏,该标志宏从未被以其他方式定义且从未被触及并且\globaldefs
具有值 0,并且依赖于仅根据而定义的所讨论的宏\DoOnlyIfUndefinedInAllscopes
,那么您可以执行如下操作:
\documentclass{article}
\makeatletter
\newcommand\exchange[2]{#2#1}%
\newcommand\DoOnlyIfUndefinedInAllscopes[2]{%
\begingroup
\escapechar=-1 %
\@ifundefined{definedflag@@\string#1@@WeirdPostfix}{%
\@ifundefined{\string#1}{%
\exchange{#2}%
}{%
%\GenericError{}{Control sequence \@backslashchar\string#1 already defined in the current scope}%
% {Action won't be performed if control sequence is already defined}{}%
\@firstofone
}%
{%
\expandafter\expandafter\expandafter\endgroup
\@namedef{definedflag@@\string#1@@WeirdPostfix}{defined}%
}%
}{%
%\GenericError{}{Control sequence \@backslashchar\string#1 already defined in the current or some superordinate current scope}%
% {Action won't be performed if control sequence is already defined.}{}%
\endgroup
}%
}%
\makeatother
\begin{document}
\begingroup
%\DoOnlyIfUndefinedInAllscopes{\foo}{\gdef\foo{This is a definition}}
\DoOnlyIfUndefinedInAllscopes{\foo}{\def\foo{This is a definition}}
\message{^^JMessage 1: \string\foo: \meaning\foo}%
\begingroup
\let\foo\UNDEFined
\DoOnlyIfUndefinedInAllscopes{\foo}{\gdef\foo{This is a redefinition}}
\message{^^JMessage 2: \string\foo: \meaning\foo}%
\endgroup
\DoOnlyIfUndefinedInAllscopes{\foo}{\gdef\foo{This is another definition}}
\message{^^JMessage 3: \string\foo: \meaning\foo}%
\endgroup
\DoOnlyIfUndefinedInAllscopes{\foo}{\gdef\foo{This is a definition after the group.}}
\message{^^JMessage 4: \string\foo: \meaning\foo}%
\end{document}
终端输出:
Message 1: \foo: macro:->This is a definition
Message 2: \foo: undefined
Message 3: \foo: macro:->This is a definition
Message 4: \foo: macro:->This is a definition after the group.
所讨论的宏仅根据 进行定义这一要求\DoOnlyIfUndefinedInAllscopes
是限制此方法实际可用性的关键点。
因此,这种方法并不值得赞成。