基于位置的可选参数

基于位置的可选参数

我们都知道它是如何\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}

在此处输入图片描述

相关内容