允许可选的分隔参数以任何顺序出现(轻松)(例如 \REset[s](X){e}=\REset(X)[s]{e})

允许可选的分隔参数以任何顺序出现(轻松)(例如 \REset[s](X){e}=\REset(X)[s]{e})

我的包 rec-thy 有许多宏,允许可选地指定阶段(作为分阶段近似的一部分)以及相对化。我采用了以下惯例:可选阶段参数用方括号提供,相对化用圆括号提供。

例如,\REset 命令可以这样调用

 \REset{e}
 \REset[s]{e}
 \REset(X){e}
 \REset(X)[s]{e}

给出以下期望结果

但是,虽然这个惯例很容易记住,而且 NewDocumentCommand 使这些宏易于定义,但记住始终将相对化参数放在阶段参数之前却不那么容易。我希望允许以任何顺序指定它们。所以我想

  \REset[s](X){e}

产生与

  \REset(X)[s]{e}

有没有一种简单干净的方法来做到这一点?请注意我不需要任何可选参数来切换它相对于强制参数的位置(我最后才做就好了)。我想要一些东西,让我可以引用第一个括号参数和第一个方括号参数,而不必考虑哪个先出现的情况(例如,明显的 NewDocumentCommand 解决方案是在正方形参数之后再次复制括号可选参数)。

答案1

, 不。

强制执行语法并坚持下去,否则用户只会得到更多的困惑。有一种不按特定顺序使用多个参数的方法是使用键值语法:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\tl_new:N \l_recthy_reset_sup_tl
\tl_set_eq:NN \l_recthy_reset_sup_tl \c_novalue_tl
\tl_new:N \l_recthy_reset_sub_tl
\tl_set_eq:NN \l_recthy_reset_sub_tl \c_novalue_tl
\keys_define:nn { rec-thy / REset }
  {
    , sup .tl_set:N = \l_recthy_reset_sup_tl
    , sub .tl_set:N = \l_recthy_reset_sub_tl
  }
\prg_generate_conditional_variant:Nnn \tl_if_novalue:n { V } { T , F , TF }
\NewDocumentCommand\REset{om}
  {
    \group_begin:
      \IfValueT {#1} { \keys_set:nn { rec-thy / REset } {#1} }
      W \tl_if_novalue:VF \l_recthy_reset_sup_tl { ^{\l_recthy_reset_sup_tl} }
        \tl_if_novalue:VTF \l_recthy_reset_sub_tl
          { \c_math_subscript_token {#2} }
          { \c_math_subscript_token {#2, \l_recthy_reset_sub_tl} }
    \group_end:
  }
\ExplSyntaxOff
\begin{document}
$\REset{e}$\par
$\REset[sub=s]{e}$\par
$\REset[sup=X]{e}$\par
$\REset[sub=s,sup=X]{e}$\par
$\REset[sup=X,sub=s]{e}$\par
\end{document}

但是如果您发现传递参数太冗长,那么您可以使用xparsee参数并定义一个^参数和另一个_参数,它们可以在输入中以任何顺序出现:

\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand\REset{e{^_}m}
  {%
    W\IfValueT{#1}{^{#1}}%
    \IfValueTF{#2}
      {_{#3, #2}}
      {_{#3}}%
  }
\begin{document}
$\REset{e}$\par
$\REset_{s}{e}$\par
$\REset^{X}{e}$\par
$\REset^{X}_{s}{e}$\par
$\REset_{s}^{X}{e}$\par
\end{document}

如果这也不能让您满意,并且您坚持保留[]()语法,那么您可以让命令具有签名[]()[]{}(或()[](){}或任何您喜欢的其他组合),然后检查是否使用了第一个[]或第二个[](如果两者都使用,您可以发出错误消息):

\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand\REset{od()om}
  {%
    \IfValueTF{#1}
      {%
        \IfValueTF{#3}
          {\def\recthyOarg{ERROR}%
           \PackageError{rec-thy}{Two []-delimited arguments used!}{}}
          {\def\recthyOarg{#1}}%
      }
      {\def\recthyOarg{#3}}%
    W\IfValueT{#2}{^{#2}}%
    \expandafter\IfValueTF\expandafter{\recthyOarg}
      {_{#4, \recthyOarg}}
      {_{#4}}%
  }
\begin{document}
$\REset{e}$\par
$\REset[s]{e}$\par
$\REset(X){e}$\par
$\REset(X)[s]{e}$\par
$\REset[s](X){e}$\par
\end{document}

所有文档产生相同的输出:

在此处输入图片描述

答案2

您可以使用 TeX 的#{-syntax 让 TeX 抓取左花括号之前的所有内容。

然后,您可以让 TeX 通过分隔和非分隔参数检查抓取的材料......

\documentclass{article}

\makeatletter
%%----------------------------------------------------------------------
%% Error-messages:
%%----------------------------------------------------------------------
%% \PreambleMacroError
%%......................................................................
%% This macro takes three arguments:
%% A macro name. An error message. The help information.
%% It displays the error message, and sets the error help (the result of
%% typing h to the prompt).
%%----------------------------------------------------------------------
\newcommand*\PreambleMacroError[3]{%
  \GenericError{%
    \space\space\space\@spaces\@spaces\@spaces
  }{%
    LaTeX Error: Inapproriate usage of macro \string#1\on@line.\MessageBreak
    (\string#1 is defined in the document's preamble.)\MessageBreak
    Problem: #2%
  }{%
    Have a look at the comments in the preamble of this document.%
  }{#3}%
}%
\newcommand\REset@error{%
  \PreambleMacroError{\REset}{Incorrect Syntax}%
  %\PackageError{MyPackage}{\string\REset: Incorrect Syntax\on@line}%
  %\@latex@error{\string\REset: Incorrect Syntax\on@line}%
  {%
    Syntax of \string\REset\space is:\MessageBreak
    \string\REset(X)[s]{e} or\MessageBreak
    \string\REset[s](X){e} or\MessageBreak
    \string\REset(X){e} or\MessageBreak
    \string\REset[s]{e} or\MessageBreak
    \string\REset{e}\MessageBreak
    Spaces between arguments are allowed.\MessageBreak
    When it comes to nesting, wrap contents of arguments into\MessageBreak
    another level of braces.\MessageBreak
    Checking for matching closing brackets/parentheses is\MessageBreak
    not implemented, thus you may encounter inscrutable\MessageBreak
    error messages if a closing thing is missing.
  }%
}%
%%----------------------------------------------------------------------
%% Check whether argument is empty:
%%......................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\expandafter\expandafter\@firstoftwo{ }{}%
  \@secondoftwo}{\expandafter\expandafter\@firstoftwo{ }{}\@firstoftwo}%
}%
%%------------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%..............................................................................
%% -- Take advantage of the fact that TeX discards space tokens when
%%    "fetching" _un_delimited arguments: --
%% \UD@CheckWhetherBlank{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that
%%                        argument which is to be checked is blank>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked is not blank}%
\newcommand\UD@CheckWhetherBlank[1]{%
  \romannumeral\expandafter\expandafter\expandafter\@secondoftwo
  \expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo#1{}.}%
}%
%%----------------------------------------------------------------------
%% Exchange two arguments
%%......................................................................
\newcommand\UD@Exchange[2]{#2#1}%
%%----------------------------------------------------------------------
%% Check whether argument's leading tokens form a specific 
%% token-sequence that does not contain explicit character tokens of 
%% category code 1 or 2:
%%......................................................................
%% \UD@CheckWhetherLeadingTokens{<a <token sequence> without explicit 
%%                                character tokens of category code 1 or 2>}%
%%                              {a <single non-space token> that does 
%%                                _not_ occur in <token sequence> >}%
%%                              {<internal token-check-macro>}%
%%                              {<argument which is to be checked>}%
%%                              {<tokens to be delivered in case <argument
%%                                which is to be checked> has <token sequence>
%%                                as leading tokens>}%
%%                              {<tokens to be delivered in case <argument
%%                                which is to be checked> does not have
%%                                <token sequence> as leading tokens>}%
\newcommand\UD@CheckWhetherLeadingTokens[4]{%
  \romannumeral0\UD@CheckWhetherNull{#4}%
  {\expandafter\expandafter\@firstoftwo{ }{}\@secondoftwo}%
  {\expandafter\@secondoftwo\string{\expandafter
   \UD@@CheckWhetherLeadingTokens#3#2#4#1}{}}%
}%
\newcommand\UD@@CheckWhetherLeadingTokens[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
  {\UD@Exchange{\@firstoftwo}}{\UD@Exchange{\@secondoftwo}}%
  {\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter}\expandafter\expandafter
   \expandafter}\expandafter\@secondoftwo\expandafter{\string}%
}% 
%%----------------------------------------------------------------------
%% \UD@internaltokencheckdefiner{<internal token-check-macro>}%
%%                              {<token sequence>}%
%% Defines <internal token-check-macro> to snap everything 
%% until reaching <token sequence>-sequence and spit that out
%% nested in braces.
%%......................................................................
\newcommand\UD@internaltokencheckdefiner[2]{%
  \newcommand#1{}\long\def#1##1#2{{##1}}%
}%
\UD@internaltokencheckdefiner{\UD@CheckLeftParenthese}{(}%
\UD@internaltokencheckdefiner{\UD@CheckLeftBracket}{[}%
\UD@internaltokencheckdefiner{\UD@CheckSpace}{ }%
%%----------------------------------------------------------------------
\@ifdefinable\UD@gobblesp{\@firstofone{\def\UD@gobblesp} {}}%
\@ifdefinable\UD@gobbletoRightParenthese{%
  \long\def\UD@gobbletoRightParenthese#1){}%
}%
\@ifdefinable\UD@gobbletoRightBracket{%
  \long\def\UD@gobbletoRightBracket#1]{}%
}%
\@ifdefinable\REsetExtractParentheseArg{%
    \long\def\REsetExtractParentheseArg(#1)#2#{{#2}{#1}}%
}%
\@ifdefinable\REsetExtractBracketArg{%
    \long\def\REsetExtractBracketArg[#1]#2#{{#2}{#1}}%
}%
\@ifdefinable\REset{%
  \long\def\REset#1#{\REset@{#1}}%
}%
\newcommand\REset@[2]{% #2-brace-arg
  % \REset@@final{<brace>}{<bracket>}{<parenthese>}
  \UD@CheckWhetherNull{#1}{\REset@@final{#2}{}{}}{%
     \UD@CheckWhetherLeadingTokens{ }{.}{\UD@CheckSpace}{#1}{%
       \expandafter\REset@\expandafter{\UD@gobblesp#1}{#2}%
     }{%
        \UD@CheckWhetherLeadingTokens{[}{.}{\UD@CheckLeftBracket}{#1}{%
          \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletoRightBracket#1]}{%
            \REset@error
          }{%
            \expandafter\@REsetExtractBracketArg\REsetExtractBracketArg#1{#2}%
          }%
        }{%
           \UD@CheckWhetherLeadingTokens{(}{.}{\UD@CheckLeftParenthese}{#1}{%
             \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletoRightParenthese#1)}{%
               \REset@error
             }{%
               \expandafter\@REsetExtractParentheseArg\REsetExtractParentheseArg#1{#2}%
             }%
            }{%
             \REset@error
           }%
        }%
     }%
  }%
}%
\newcommand\@REsetExtractBracketArg[3]{%
  \UD@CheckWhetherNull{#1}{%
    \REset@@final{#3}{#2}{}%
  }{%
    \UD@CheckWhetherLeadingTokens{ }{.}{\UD@CheckSpace}{#1}{%
      \expandafter\@REsetExtractBracketArg\expandafter{\UD@gobblesp#1}{#2}{#3}%
    }{%
      \UD@CheckWhetherLeadingTokens{(}{.}{\UD@CheckLeftParenthese}{#1}{%
        \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletoRightParenthese#1)}{%
          \REset@error
        }{%
          \expandafter\@@REsetExtractBracketArg\REsetExtractParentheseArg#1{#2}{#3}%
        }%
      }{%
        \REset@error
      }%
    }%
  }%
}%
\newcommand\@@REsetExtractBracketArg[4]{%
  \UD@CheckWhetherBlank{#1}{%
    \REset@@final{#4}{#3}{#2}%
  }{%
    \REset@error
  }%
}%
\newcommand\@REsetExtractParentheseArg[3]{%
  \UD@CheckWhetherNull{#1}{%
    \REset@@final{#3}{}{#2}%
  }{%
    \UD@CheckWhetherLeadingTokens{ }{.}{\UD@CheckSpace}{#1}{%
      \expandafter\@REsetExtractParentheseArg\expandafter{\UD@gobblesp#1}{#2}{#3}%
    }{%
      \UD@CheckWhetherLeadingTokens{[}{.}{\UD@CheckLeftBracket}{#1}{%
        \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletoRightBracket#1]}{%
          \REset@error
        }{%
          \expandafter\@@REsetExtractParentheseArg\REsetExtractBracketArg#1{#2}{#3}%
        }%
      }{%
        \REset@error
      }%
    }%
  }%
}%
%\@@REsetExtractParentheseArg{<remaining arg>}{bracketarg}{parenthesearg}{brace-arg}%
\newcommand\@@REsetExtractParentheseArg[4]{%
  \UD@CheckWhetherBlank{#1}{%
    \REset@@final{#4}{#2}{#3}%
  }{%
    \REset@error
  }%
}%
\newcommand\REset@@final[3]{%
  \UD@CheckWhetherNull{#1}{%
    \UD@CheckWhetherNull{#2}{%
      \UD@CheckWhetherNull{#3}{$W$}{$W^{#3}$}%
    }{%
      \UD@CheckWhetherNull{#3}{$W_{#2}$}{$W_{#2}^{#3}$}%
    }%
  }{%
    \UD@CheckWhetherNull{#2}{%
      \UD@CheckWhetherNull{#3}{$W_{#1}$}{$W_{#1}^{#3}$}%
    }{%
      \UD@CheckWhetherNull{#3}{$W_{#1, #2}$}{$W_{#1, #2}^{#3}$}%
    }%
  }%
}%
\makeatother

\usepackage{array}

\begin{document}

\begin{minipage}{6cm}
\begin{tabular}{|ll<{ $\to$\rule[-1.5\dp\strutbox]{0mm}{2\ht\strutbox}}l|}\hline
\verb|\REset [s]  (X) {e}|&&\REset [s]  (X) {e}\\\hline
\verb|\REset (X) [s]  {e}|&&\REset (X) [s]  {e}\\\hline
\verb|\REset (X) {e}|&&\REset (X) {e}\\\hline
\verb|\REset [s]  {e}|&&\REset [s]  {e}\\\hline
\verb|\REset {e}|&&\REset {e}\\\hline
\verb|\REset [s]  (X) {}|&&\REset [s]  (X) {}\\\hline
\verb|\REset (X) [s]  {}|&&\REset (X) [s]  {}\\\hline
\verb|\REset (X) {}|&&\REset (X) {}\\\hline
\verb|\REset [s] {}|&&\REset [s] {}\\\hline
\verb|\REset {}|&&\REset {}\\\hline
\end{tabular}
\end{minipage}

\end{document}

在此处输入图片描述

答案3

你可以,但你不应该。你的问题是语法薄弱的表现,你应该重新考虑你的包的主要语法约定是如何布局的。

\NewDocumentCommand{\REset}{%
  D(){}   % optional argument in (), default empty
  o       % optional argument in [], no default
  D(){#1} % optional argument in (), default is #1
  m       % mandatory argument
}{%
  W^{#3}_{#4\IfValueT{#2}{,#2}}%
}

在此处输入图片描述

这并不能防止诸如此类的错误\REset(X)[s](Y){e},但我认为这并不重要。

相关内容