4

My package rec-thy has a number of macros that allow the optional specification of both a stage (as part of a stagewise approximation) as well as a relativization. I've adopted the convention that optional stage arguments are supplied in square brackets and relativizations in parens.

For instance, the \REset command can be called as

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

Giving the following desired results

But, while the convention is easy to remember and NewDocumentCommand makes these macros easy to define, it's not as easy to remember to always put the relativization argument before the stage argument. I'd like to allow them to be specified in any order. So I want

  \REset[s](X){e}

to produce the same result as

  \REset(X)[s]{e}

Is there an easy and clean way to do this? Note that I don't need any optional arg to switch it's position with respect to the mandatory args (I'm fine with just doing them last). I want something that lets me refer to the first paren arg and first bracket arg without having to consider cases about which came first (e.g. the obvious NewDocumentCommand solution by duplicating the paren optional arg again after the square arg).

4
  • The fact that braces {} are a different catcode than parens () and brackets [] will make this a real challenge. It would be much easier if the syntax were something like \REset{(X)[s]<e>}, with interchangeable units within a single braced argument.. Commented Jun 6, 2019 at 23:57
  • I only care about switching the order of the optional arguments. The parens and brackets. I mean this CAN be done by just adding a second paren optional arg in NewDocumentCommand after the square bracket arg and before the braces if you willing to test which one is filled...that would be fine if I only had to write one command like this. Surely it wouldn't be that hard to stack a solution on top of NewDocumentCommand that lets you build macros which refer to the arguments as \ParenArg{1} etc.. I'm just hoping someone already has. Commented Jun 7, 2019 at 0:03
  • But I see how the title may have made that unclear. I hope it's more apparent now what I meant. Also made it clear that I'm fine with leaving all mandatory args at the end. So you can reorder the optional args as you wish but as soon as you hit the first bracket arg they are done. Commented Jun 7, 2019 at 0:04
  • Just FYI reason I went with keeping the standard args as normal bracket args is to avoid problems with sneaky errors because complex args weren't put inside braces. In this case it's less of an issue but the stage and relativization args are almost always simple while the mandatory args can get complex enough that I didn't want to leave it to user to put braces guards around them. Commented Jun 7, 2019 at 0:11

3 Answers 3

5

Please, don't.

Enforce a syntax and stick to it, otherwise users will only get more confused. One way to have multiple arguments without a specific order is to use a key-value syntax:

\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}

But if you find that too wordy for passing the arguments, then you can use xparse's e argument and define a ^ argument and another _ argument, which can appear in any order in the input:

\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}

And if that didn't please you either, and you insist on keeping the [] and () syntax, then you can make the command have a signature []()[]{} (or ()[](){} or any other combination you like) and then check if the first [] or the second [] was used (and if both were used you can issue an error message):

\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}

All documents produce the same output:

enter image description here

4
  • But I did just give a completely precise syntax. I didn’t mention it before I had different commands that demanded different orders so I had to settle on one particular syntax and both order matters and only delimiter type matters are equally well defined syntaxes. Put arguments in the appropriate delimiters and put all braces at the end. I agree it’s important to have a single syntax but whether or not that syntax should be order dependent or not is s choice like white space sensitivity. Just a matter of what kind of syntax you prefer. Commented Jun 7, 2019 at 4:39
  • Ignore suggested edit..used bad client that made it look like I could directly edit not just suggest as usual. Commented Jun 7, 2019 at 4:43
  • @PeterGerdes Sure, it should be what makes most sense in the context of the package ou are writing, and since I don't know it I showed you a few different ways to do it for you to choose (and if I left something out, please say). Personally I don't like the []() syntax because I would never know which one will be subscript and which one superscript; for me ^_ feels more natural. Of course, as I said, it depends heavily on the context, of which I know nothing :-) (and no worries about the edit: I think you need 2000 rep to do it. You were trying to fix something?) Commented Jun 7, 2019 at 9:50
  • No, I was just confused by the client I tried and was trying to figure out what was going on. Anyway I finally got back to this and had time to read your suggestions in more detail. Thanks! Commented Sep 5, 2020 at 0:26
4

You could use TeX's #{-syntax for having TeX grab everything before the opening curly brace.

Then you can have TeX examine the material grabbed by means of delimited and undelimited arguments...

\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}

enter image description here

1
  • Whoa! (9 more to go...) Commented Jun 7, 2019 at 3:29
2

You can, but you shouldn't. Your problem is a symptom of weak syntax and you should rethink to how the main syntax conventions of your package are laid out.

\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}}%
}

enter image description here

This doesn't prevent mistakes such as \REset(X)[s](Y){e}, but I don't think it's important.

1
  • The reason here for allowing arbitrary order is that in the actual math notation one often just writes [s] to indicate the same thing as subscripting s and in the mathematical context either there is no obvious order or multiple orders would all mean the same thing. If I was defining the notation from scrath I would do it differently but given adding [s] has a well-defined mathematical meaning and no inherent order it seems desirable that they all map to the same symbol. Commented Sep 5, 2020 at 0:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .