我的包 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}
但是如果您发现传递参数太冗长,那么您可以使用xparse
的e
参数并定义一个^
参数和另一个_
参数,它们可以在输入中以任何顺序出现:
\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}
,但我认为这并不重要。