我们都知道它是如何\newtheorem
工作的。它需要第三个可选参数,并且将其放在哪里很重要:
\newtheorem{lem}[thm]{Lemma}
不同于
\newtheorem{lem}{Lemma}[thm]
我正在研究宏,想以自己的方式复制它。我了解 LaTeX 的可选参数,并阅读了这篇 TeX 文章https://www.tug.org/TUGboat/tb22-1-2/tb70eijk.pdf但我发现它令人难以忍受,并且想知道:
到底是怎么回事 \newtheorem
已定义?
据我所知,\newcommand
我不知道如何从 LaTeX 实现这种位置依赖性。我知道我可以使用 xparse 等解决这个问题。我只是好奇制造商如何在没有 xparse 的情况下做到这一点。
答案1
的默认方法\newcommand
是将可选参数作为第一的元素
\mycmd[<opt>]{<arg1>}...{<argn>}
但是,你可以\newcommand
按照你想要的任何方式链接 s 来放置可选参数:
\newcommand{\mycmd}{\firstcmd}
\newcommand{\firstcmd}[1]{first #1\space \secondcmd}
\newcommand{\secondcmd}[2][opt]{second (opt: #1) #2}
%...
\mycmd{1st}[OPT]{2nd}
在上面的代码中,\mycmd
仅提供了“用户界面”,\firstcmd
它接受一个强制参数。在 的末尾\firstcmd
,我们链接了\secondcmd
,它需要 2 个参数,其中第一个是可选的。由于这两个是链接的(\secondcmd
在 的末尾调用\firstcmd
),因此它们的参数可以链接起来。为此,从用户的角度来看, 似乎\mycmd
需要 3 个参数,中间一个是可选的。
您会注意到,这串命令定义并链接在ltthm.dtx
或者latex.ltx
(LaTeX 内核)搜索时\newtheorem
:
%%% From File: ltthm.dtx
\def\newtheorem#1{%
\@ifnextchar[{\@othm{#1}}{\@nthm{#1}}}
\def\@nthm#1#2{%
\@ifnextchar[{\@xnthm{#1}{#2}}{\@ynthm{#1}{#2}}}
\def\@xnthm#1#2[#3]{%
\expandafter\@ifdefinable\csname #1\endcsname
{\@definecounter{#1}\@newctr{#1}[#3]%
\expandafter\xdef\csname the#1\endcsname{%
\expandafter\noexpand\csname the#3\endcsname \@thmcountersep
\@thmcounter{#1}}%
\global\@namedef{#1}{\@thm{#1}{#2}}%
\global\@namedef{end#1}{\@endtheorem}}}
\def\@ynthm#1#2{%
\expandafter\@ifdefinable\csname #1\endcsname
{\@definecounter{#1}%
\expandafter\xdef\csname the#1\endcsname{\@thmcounter{#1}}%
\global\@namedef{#1}{\@thm{#1}{#2}}%
\global\@namedef{end#1}{\@endtheorem}}}
\def\@othm#1[#2]#3{%
\@ifundefined{c@#2}{\@nocounterr{#2}}%
{\expandafter\@ifdefinable\csname #1\endcsname
{\global\@namedef{the#1}{\@nameuse{the#2}}%
\global\@namedef{#1}{\@thm{#2}{#3}}%
\global\@namedef{end#1}{\@endtheorem}}}}
\def\@thm#1#2{%
\refstepcounter{#1}%
\@ifnextchar[{\@ythm{#1}{#2}}{\@xthm{#1}{#2}}}
\def\@xthm#1#2{%
\@begintheorem{#2}{\csname the#1\endcsname}\ignorespaces}
\def\@ythm#1#2[#3]{%
\@opargbegintheorem{#2}{\csname the#1\endcsname}{#3}\ignorespaces}
\def\@thmcounter#1{\noexpand\arabic{#1}}
\def\@thmcountersep{.}
\def\@begintheorem#1#2{\trivlist
\item[\hskip \labelsep{\bfseries #1\ #2}]\itshape}
\def\@opargbegintheorem#1#2#3{\trivlist
\item[\hskip \labelsep{\bfseries #1\ #2\ (#3)}]\itshape}
\def\@endtheorem{\endtrivlist}
每个定义都使用条件作为其一部分\newtheorem
并\@nthm
利用\@ifnextchar[
。当使用 定义宏时,这些条件是隐式的\newcommand
;\newtheorem
使用\def
并因此明确检查以下参数是否以 a 开头[
(这应该是可选的)。
这些技巧可以通过以下方式简化:xparse
您可以在定义中混合使用可选参数,而无需链接。例如:
\usepackage{xparse}
\NewDocumentCommand{\mycmd}{m O{opt} m}{first #1\space second (opt: #2) #3}
使用链接显示与上面定义相同的输出。
答案2
LaTeX 内核中如何定义带有可选参数的命令?
关键函数是\@ifnextchar
,它测试以下标记(吞噬空格)并可以执行不同的操作。请参阅理解 \@ifnextchar了解更多信息。
在您的例子中,我们希望一个命令在被调用为、或\foo
时执行不同的操作。这两个可选参数是互斥的。\foo{m1}{m2}
\foo{m1}[o1]{m2}
\foo{m1}{m2}[o2]
让我们开始吧:宏\foo
将收集第一个强制参数并测试[
:
\def\foo#1{\@ifnextchar[{\foo@firstopt{#1}}{\foo@nofirstopt{#1}}}
\@ifnextchar<token>{<true>}{<false>}
最后这一点非常重要。
我们现在必须定义\foo@firstopt
,它必须吸收另一个强制参数;[
不会从主输入列表中删除,因此我们可以这样做
\def\foo@firstopt#1[#2]#3{%
<the code for the "o1" case>%
\@ifnextchar[{\foo@badsecondopt}{}%
}
最后,我们添加代码来测试第二个可选参数是否存在,以引发错误并删除有问题的部分:
\def\foo@badsecondopt[#1]{<raise an error>}
现在让我们解决这个问题\foo@nofirstopt
;我们需要检查尾随的可选参数:
\def\foo@nofirstopt#1#2{%
\@ifnextchar[{\foo@secondopt{#1}{#2}}{\foo@nosecondopt{#1}{#2}}%
}
现在很简单:
\def\foo@secondopt#1#2[#3]{%
<the code for the "o2" case>%
}
\def\foo@nosecondopt#1#2{%
<the code for the "no optional arguments" case>%
}
的描述\newtheorem
稍微复杂一些,因为内核试图避免代码重复。
请注意已经被吸收的论点如何能够延续到下一阶段。
我们怎样才能做同样的事情xparse
?
\NewDocumentCommand{\foo}{m o m o}{%
\IfNoValueTF{#1}%
{% no o1
\IfNoValueTF{#2}%
{% no o2
<code for the "no optional arguments" case>%
}%
{% o2
<code for the "o2" case>%
}%
}%
{% o1
<code for the "o1" case>%
\IfNoValueF{#2}{<error message>}%
}%
}
两个强制参数称为#1
和#3
,两个可选参数称为#2
和#4
。
答案3
沃纳在他的回答中已经展示并解释了 LaTeX 2ε-kernel 的代码序列,其中\newtheorem
定义。
正如你在他的回答中所看到的,这不是通过 来完成的\newcommand
。
说实话,我对方括号中带有许多可选参数的宏并不太感兴趣。
通常值得考虑使用类似键值或者键值或者进程kv或者pdfkeys或者任何用于 key=value 处理的内容,并且只有一个可选参数,您可以在其中传递一个 key=value 列表,其中的键和值表示您希望哪些值与其默认值有何不同。
是因为它可能。
无论如何,我强烈建议以一种方式定义宏,即可选参数永远不会位于其他可选参数之前和/或之后。换句话说:可选参数永远不应直接相邻。
该规则的一个例外是,只有当最左边的所有相邻可选参数也都提供时,在最右边提供相邻的可选参数才有意义。
如果您有兴趣,这里有我自己的用于定义处理几个可选参数的宏的小型个人工具包。
要点是:
使用我的工具包来处理几个可选参数的机制包括两部分:
- “包装器”宏首先将可选和非可选参数收集到通过宏维护的非可选参数列表中
\UD@CollectedArguments
,然后将扩展传递\UD@CollectedArguments
给执行实际工作的内部宏。“包装器宏”必须是健壮的/不可扩展的,因为根据 LaTeX 2ε 内核设计,处理可选参数的宏不能在纯扩展上下文中执行。 - 将所有参数作为非可选参数处理并执行实际工作的内部宏。
单个宏:
- 该宏
\UD@ClearCollectedArguments
将列表定义\UD@CollectedArguments
为空。 - 宏将保存的参数列表传递给并定义为空。
\UD@PassAndClearCollectedArguments{⟨internal macro⟩}
\UD@CollectedArguments
⟨internal macro⟩
\UD@CollectedArguments
- 该宏收集一个可选参数,其默认值为,并将构成该可选参数的标记作为另一个非可选参数附加到 中保存的非可选参数列表中,然后传递。
\UD@AddOptArgToCollectedArguments{⟨default value⟩}{⟨continue⟩}
⟨default value⟩
\UD@CollectedArguments
⟨continue⟩
- 该宏收集一个非可选参数,并将构成该非可选参数的标记作为另一个非可选参数附加到 中保存的非可选参数列表中,然后传递。
\UD@AddNonOptArgToCollectedArguments{⟨continue⟩}
\UD@CollectedArguments
⟨continue⟩
因此,您可以通过在 -argument 中嵌套对\UD@AddOptArgToCollectedArguments
/ 的调用来收集参数。在最内层的嵌套级别中,-argument 包含-directive,用于将收集到的参数传递给执行实际工作的 。\UD@AddNonOptArgToCollectedArguments
⟨continue⟩
⟨continue⟩
\UD@PassAndClearCollectedArguments
⟨internal macro⟩
\documentclass[landscape, a4paper]{article}
%-------------------[adjust margins/layout for the example]--------------------
\csname @ifundefined\endcsname{pagewidth}{}{\pagewidth=\paperwidth}%
\csname @ifundefined\endcsname{pdfpagewidth}{}{\pdfpagewidth=\paperwidth}%
\csname @ifundefined\endcsname{pageheight}{}{\pageheight=\paperheight}%
\csname @ifundefined\endcsname{pdfpageheight}{}{\pdfpageheight=\paperheight}%
\textwidth=\paperwidth
\oddsidemargin=2cm
\marginparsep=.125\oddsidemargin
\marginparwidth=\oddsidemargin
\advance\marginparwidth-2\marginparsep
\advance\textwidth-2\oddsidemargin
\advance\oddsidemargin-1in
\evensidemargin=\oddsidemargin
\textheight=\paperheight
\topmargin=2cm
\footskip=.5\topmargin
{\normalfont\global\advance\footskip.5\ht\strutbox}%
\advance\textheight-2\topmargin
\advance\topmargin-1in
\headheight=0ex
\headsep=0ex
\pagestyle{plain}
\parindent=0ex
\parskip=\bigskipamount
%------------------[eof margin-adjustments]------------------------------------
\makeatletter
%========[This is my personal toolkit for gathering optional arguments]========
% (As macros with optional arguments by LaTeX2e-kernel-design cannot be used in
% full-expansion-contexts, let's gather arguments within a macro
% \UD@CollectedArguments and in a last step pass the arguments gathered in
% that macro to an internal macro which does process non-optional
% arguments only.)
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@CollectedArguments{}%
\newcommand\UD@ClearCollectedArguments{\long\gdef\UD@CollectedArguments{}}%
\newcommand\UD@PassAndClearCollectedArguments[1]{%
\expandafter\UD@exchange\expandafter{\UD@CollectedArguments}{\UD@ClearCollectedArguments#1}%
}%
%------------------------------------------------------------------------------
% \UD@AddOptArgToCollectedArguments{<default value>}{<continue>}
%
% Grabs an optional argument whose default value is <default value>
% , wraps it in curly braces and adds it to the macro \UD@CollectedArguments
% and delivers <continue>.
%..............................................................................
\newcommand\UD@AddOptArgToCollectedArguments[2]{%
\@testopt{\UD@@AddOptArgToCollectedArguments{#2}}{#1}%
}%
\@ifdefinable\UD@@AddOptArgToCollectedArguments{%
\begingroup
% Check the availability of the \unexpanded-primitive:
\edef\@tempa{\meaning\unexpanded}%
\edef\@tempb{\string\unexpanded}%
\expandafter\endgroup
\ifx\@tempa\@tempb\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{% <- \unexpanded is available:
\long\def\UD@@AddOptArgToCollectedArguments#1[#2]{%
\xdef\UD@CollectedArguments{\unexpanded\expandafter{\UD@CollectedArguments{#2}}}%
#1%
}%
}{% <- \unexpanded is not available:
\long\def\UD@@AddOptArgToCollectedArguments#1[#2]{%
\expandafter\UD@exchange\expandafter{\expandafter
\toks@\expandafter{\the\toks@}}{%
\toks@\expandafter{\UD@CollectedArguments{#2}}%
\xdef\UD@CollectedArguments{\the\toks@}%
}%
#1%
}%
}%
}%
%------------------------------------------------------------------------------
% \UD@AddNonOptArgToCollectedArguments{<continue>}
%
% Grabs a non-optional argument, wraps it in curly braces and adds it to the
% macro \UD@CollectedArguments and delivers <continue>.
%..............................................................................
\newcommand\UD@AddNonOptArgToCollectedArguments[2]{%
\UD@@AddOptArgToCollectedArguments{#1}[{#2}]%
}%
%------------------------------------------------------------------------------
% Explanation:
%
% You can gather up arguments within the macro \UD@CollectedArguments by nesting
% calls to \UD@AddOptArgToCollectedArguments / \UD@AddNonOptArgToCollectedArguments
% within the <continue>-arguments.
%========[eof personal toolkit]================================================
% Now let's use the personal toolkit for creating a mechanism which
% gathers 9 arguments, whereof the 1st, 3rd, 5th, 7th and 9th are optional
% while the 2nd, 4th, 6th and 8th are non-optional:
%
\newcommand\ProcessNineArgumentsInternal[9]{%
\textbf{Arguments are:}\\
\texttt{[#1]\{#2\}[#3]\{#4\}[#5]\{#6\}[#7]\{#8\}[#9]}
}%
% Now the argument-gathering wrapper for \ProcessNineArgumentsInternal -
% !!!!! this must be robust !!!!
\@ifdefinable\ProcessNineArguments{%
\DeclareRobustCommand\ProcessNineArguments{%
\UD@ClearCollectedArguments
\UD@AddOptArgToCollectedArguments{DEFAULT 1}{%
\UD@AddNonOptArgToCollectedArguments{%
\UD@AddOptArgToCollectedArguments{DEFAULT 2}{%
\UD@AddNonOptArgToCollectedArguments{%
\UD@AddOptArgToCollectedArguments{DEFAULT 3}{%
\UD@AddNonOptArgToCollectedArguments{%
\UD@AddOptArgToCollectedArguments{DEFAULT 4}{%
\UD@AddNonOptArgToCollectedArguments{%
\UD@AddOptArgToCollectedArguments{DEFAULT 5}{%
\UD@PassAndClearCollectedArguments{\ProcessNineArgumentsInternal}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
\makeatother
\begin{document}
\verb|\ProcessNineArguments{1st non-opt}{2nd non-opt}{3rd non-opt}{4th non-opt}| yields:\\
\ProcessNineArguments{1st non-opt}{2nd non-opt}{3rd non-opt}{4th non-opt}
\verb|\ProcessNineArguments[1st opt]{1st non-opt}{2nd non-opt}[3rd opt]{3rd non-opt}{4th non-opt}| yields:\\
\ProcessNineArguments[1st opt]{1st non-opt}{2nd non-opt}[3rd opt]{3rd non-opt}{4th non-opt}
\verb|\ProcessNineArguments[1st opt]{1st non-opt}{2nd non-opt}[3rd opt]{3rd non-opt}{4th non-opt}[5th opt]| yields:\\
\ProcessNineArguments[1st opt]{1st non-opt}{2nd non-opt}[3rd opt]{3rd non-opt}{4th non-opt}[5th opt]
\verb|\ProcessNineArguments[1st opt]{1st non-opt}[2nd opt]{2nd non-opt}[3rd opt]{3rd non-opt}[4th opt]{4th non-opt}[5th opt]| yields:\\
\ProcessNineArguments[1st opt]{1st non-opt}[2nd opt]{2nd non-opt}[3rd opt]{3rd non-opt}[4th opt]{4th non-opt}[5th opt]
\end{document}