1

I'd like to make a macro named \mycmd by \NewDocumentCommand from xparse, and use it like this:

\mycmd-[options1]{action1}
\mycmd+[options2]{action2}
\mycmd/[options3]{action3}

The xparse mandatory argument "l" seems like what I want. However, the optional arguments can not be placed closed to the "l" argument(in my case, they are "-", "+", "/"), because "l" is "An argument which reads everything up to the first open group token: in standard LATEX this is a left brace."

So, is there any better way to solve this problem?

  • 1
    It much depends on whether one among -, + or / is required to appear or not. – egreg May 16 '18 at 8:01
2

Use the t specifier:

\documentclass{article}
\usepackage{xparse}

\NewDocumentCommand{\mycmd}{t- t+ t/ O{} m}{%
  \IfBooleanTF{#1}
    {called with -, options are #4, mandatory is #5}
    {\IfBooleanTF{#2}
       {called with +, options are #4, mandatory is #5}
       {\IfBooleanTF{#3}
          {called with /, options are #4, mandatory is #5}
          {no symbol, options are #4, mandatory is #5}%
       }%
    }%
}

\begin{document}

\mycmd[ABC]{def}

\mycmd-[ABC]{def}

\mycmd+[ABC]{def}

\mycmd/[ABC]{def}

\end{document}

This assumes at most one of the characters is present; if more than one is given, the first in the order -, + and / is used.

enter image description here

  • I think something should be invented so that it accepts t{+-/} or something like that. – Manuel May 16 '18 at 19:30
  • @Manuel There's pending discussion about this. – egreg May 16 '18 at 20:08
  • And a new case scenario like \IfTokenTF {#1} { {+}{do this} {-}{do that} {/}{do slash} } { true } { false }. – Manuel May 16 '18 at 20:15
  • @Manuel I guess a lower level interface with \str_case:nnF would be better for such a complex setup. – egreg May 16 '18 at 20:25
  • But if it's integrated in xparse it does need an interface, and what I proposed I think is pretty simple honestly, not a complex setup, just intuitive interface. – Manuel May 16 '18 at 21:48
3

Done using standard \newcommand nomenclature.

It calls two macros successively. the first absorbs a mandatory first argument, that I call the "case": +, -, or /, which it saves in a macro. The first macro then calls a 2nd macro that absorbs the optional and action arguments. While I don't show it, branching tests can be performed on the case with tests like \if+\mycase<codeA>\else<codeB>\fi.

\documentclass{article}
\newcommand\mycmd[1]{\def\mycase{#1}\mycmdaux}
\newcommand\mycmdaux[2][]{Case is ``\mycase'', options are ``#1'' and action is ``#2''}

\begin{document}
\mycmd-[options1]{action1}

\mycmd+[options2]{action2}

\mycmd/[options3]{action3}
\end{document}

enter image description here

  • Many thanks!! It works very well and it is a bit difficult to understand. Why "\mycmd-" would not make error since the macro \mycmd just define another macro-\mycase. Could you provide with a more detail description? Thank you. – lyl May 16 '18 at 2:49
  • @lyl I have added some explanation. – Steven B. Segletes May 16 '18 at 2:55
  • Thank you so much for your further explaination! And: according to Latex syntax, after \newcommand\mycmd[1]{...}, I think \mycmd{-} has to be used. Why "\mycmd-" would not cause syntax error? – lyl May 16 '18 at 3:05
  • @lyl If no braces are present, then TeX takes the next single token as the argument. Thus, you could, but are not required to, place the "case" in braces. – Steven B. Segletes May 16 '18 at 9:43
  • Many thanks again for your great solution and your patience! – lyl May 16 '18 at 13:02
3

This answer actually does not provide an answer to your question which is about accomplishing things by using the xparse-package.

Ways for accomplishing things without xparse are presented instead.

Without xparse you can use \kernel@ifnextchar for (non-expandably) checking whether the meaning of the next token in the input-stream equals the meaning of the token provided as first argument to \kernel@ifnextchar while leaving that next token in the input-stream.

Be aware that not the tokens themselves are checked. Only their meanings are checked.

This can be used for checking whether the meaning of the token following the control-word-token \mycmd in the input-stream is that of the opening brace character of category-code 1.

If it is, we already know that under normal catcode-régime the token following the control-word-token \mycmd in the input-stream cannot be a minus/plus/slash.

If it is not, we know that under normal catcode-régime the token following the control-word-token \mycmd in the input-stream can be processed as an undelimited macro-argument where brace-stripping/stripping of pairs of tokens of catcode 1 and 2 cannot occur.

Thus in that case the token following the control-word-token \mycmd in the input-stream can be fetched as undelimited macro-argument, and checking for minus/plus/slash on that undelimited argument can take place by means of fully expandable macros that internally use delimited arguments. In case it is neither minus nor plus nor slash, it can be put back. ;-)

By using delimited macro-arguments you can expandably (i.e., without performing further temporary assignments) check for the tokens themselves instead of just their meanings. Thus you cannot easily fool the mechanism, e.g., by using a token other than minus/plus/slash whose meaning equals that of the minus/plus/slash character-token of category-code 12.

Be aware that surrounding braces are taken into account with the example below.

E.g., \mycmd+... will be distinguished from \mycmd{+}....
The further will trigger the action desired with leading plus-sign.
The latter will trigger the action desired when there is neither a leading minus sign, nor a leading plus-sign, nor a leading slash and no optional argument, taking the plus for the (first) undelimited/mandatory argument.

Only the first leading non-space-token will be taken into account. E.g., with \mycmd+-... the action desired with leading plus-sign will be triggered, taking the minus-sign for the (first) undelimited/mandatory argument.

Be aware that in the example below I did not implement checking for the token trailing in the input-stream the control-word-token \mycmd being a closing brace:
Within the question the condition of \mycmd processing optional arguments and undelimited macro-arguments was given implicitly.
Under normal catcode-régime unblalanced closing braces with macro-arguments in any case yield error-messages as braces need to be balanced with macro-arguments.

Also it is relied on the macros being used under normal catcode-régime where the only input-character which category-code 1 is assigned to is the opening-brace-character.

\documentclass{article}

\begingroup
\makeatletter
\@firstofone{%
  \endgroup
  %%-------------------------------------------------------------------------
  %% Check whether argument is empty:
  %%.........................................................................
  %% \@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>}%
  \newcommand\@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 contains no exclamation-mark on top-brace-level:
  %%.........................................................................
  %% \@CheckWhetherNoExclamationMark{<argument which is to be checked>}%
  %%                   {<tokens to be delivered in case that
  %%                     argument which is to be checked does not contain !>}%
  %%                   {<tokens to be delivered in case that
  %%                     argument which is to be checked does contain !>}%
  \newcommand\@RemoveToExclamationMark{}%
  \long\def\@RemoveToExclamationMark#1!{}%
  \newcommand\@CheckWhetherNoExclamationMark[1]{%
    \expandafter\@CheckWhetherNull\expandafter{\@RemoveToExclamationMark#1!}%
  }%
  %%-------------------------------------------------------------------------
  %% Fork depending on minus, plus, slash, something else.
  %%.........................................................................
  %% (First via \kernek@ifnextchar check whether the next token is an 
  %%  opening brace.
  %%  If it is, we know that it is neither a minus nor a plus nor a slash.
  %%  If it is not, it can be processed as an undelimited macro-argument 
  %%  where under normal catcode-régime brace-stripping cannot occur, thus
  %%  in this ase use \mycmd@furthercheck for checking for
  %%  minus/plus/slash/something else.
  %%  Make the command robust as due to the not fully expandable
  %%  \kernel@ifnextchar it should not be carried out in 
  %%  pure-expansion-contexts.)
  \newcommand\mycmd{}%
  \DeclareRobustCommand\mycmd{%
    \kernel@ifnextchar\bgroup
                      {\mycmd@else}%<-Next token is opening brace or \bgroup,
                                   %  thus something other than minus/plus/slash.
                      {\mycmd@furthercheck}%<-Check for minus/plus/slash.
  }%
  %
  \newcommand\@mycmdfork{}%
  \long\def\@mycmdfork#1!!-!+!/!#2#3!!!!{#2}%
  %
  \newcommand\mycmd@furthercheck[1]{%
    \@CheckWhetherNoExclamationMark{#1}{%
      \@mycmdfork
      !#1!-!+!/!{\mycmd@else#1}%<-Else #1 empty (actually this can't happen.)
      !!#1!+!/!{\mycmd@minus}%<-minus
      !!-!#1!/!{\mycmd@plus}%<-plus
      !!-!+!#1!{\mycmd@slash}%<-slash
      !!-!+!/!{\mycmd@else#1}%<-ELSE no exclamation-mark
      !!!!%
    }{\mycmd@else#1}%<-ELSE Exclamation-mark
  }%
  \newcommand\mycmd@minus[2][Optional argument's default at minus]{%
    Case minus-sign:\\%
    Optional argument is: \texttt{#1}\\%
    Mandatory argument is: \texttt{#2}%
  }%
  \newcommand\mycmd@plus[2][Optional argument's default at plus]{%
    Case plus-sign:\\%
    Optional argument is: \texttt{#1}\\%
    Mandatory argument is: \texttt{#2}%
  }%
  \newcommand\mycmd@slash[2][Optional argument's default at slash]{%
    Case slash-sign:\\%
    Optional argument is: \texttt{#1}\\%
    Mandatory argument is: \texttt{#2}%
  }%
  \newcommand\mycmd@else[2][Optional argument's default at other cases]{%
    Some other case:\\
    Optional argument is: \texttt{#1}\\%
    Mandatory argument is: \texttt{#2}%
  }%
}%

\pagestyle{empty}

\begin{document}

\vspace*{-1.33in}%
\parskip=\baselineskip
\parindent=0pt
\enlargethispage{1in}%
\footnotesize

\verb|\mycmd X| yields:\\
\mycmd X

\verb|\mycmd!| yields:\\
\mycmd!

\verb|\mycmd{!}| yields:\\
\mycmd{!}

\verb|\mycmd{-}| yields:\\
\mycmd{-}

\verb|\mycmd{+}| yields:\\
\mycmd{+}

\verb|\mycmd{/}| yields:\\
\mycmd{/}

\verb|\mycmd{Some other case without given optional argument}| yields:\\
\mycmd{Some other case without given optional argument}

\verb|\mycmd[Given optional argument]{Some other case with given optional argument}| yields:\\
\mycmd[Given optional argument]{Some other case with given optional argument}

\verb|\mycmd+{Plus case without given optional argument}| yields:\\
\mycmd+{Plus case without given optional argument}

\verb|\mycmd+[Given optional argument]{Plus case with given optional argument}| yields:\\
\mycmd+[Given optional argument]{Plus case with given optional argument}

\verb|\mycmd-{Minus case without given optional argument}| yields:\\
\mycmd-{Minus case without given optional argument}

\verb|\mycmd-[Given optional argument]{Minus case with given optional argument}| yields:\\
\mycmd-[Given optional argument]{Minus case with given optional argument}

\verb|\mycmd/{Slash case without given optional argument}| yields:\\
\mycmd/{Slash case without given optional argument}

\verb|\mycmd/[Given optional argument]{Slash case with given optional argument}| yields:\\
\mycmd/[Given optional argument]{Slash case with given optional argument}

\end{document}

enter image description here


In case with \mycmd<optional - or + or />[optional]{mandatory}, the (first) mandatory argument is always to be nested in curly braces, it is possible to implement fully expandable forking depending on whether a brace-delimited argument has a leading - or + or /:

\documentclass{article}

\begingroup
\makeatletter
\@firstofone{%
  \endgroup
  %%----------------------------------------------------------------------
  %% Paraphernalia:
  %%......................................................................
  \newcommand\@Exchange[2]{#2#1}%
  %%----------------------------------------------------------------------
  %% Check whether argument is empty:
  %%......................................................................
  %% \@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>}%
  \newcommand\@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's leading tokens form a specific token-sequence:
  %%......................................................................
  %% \@CheckWhetherLeadingTokens{<token sequence>}%
  %%                           {<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\@CheckWhetherLeadingTokens[4]{%
    \romannumeral0\@CheckWhetherNull{#4}%
    {\expandafter\expandafter\@firstoftwo{ }{}\@secondoftwo}%
    {\expandafter\@secondoftwo\string{#3#2#4#1}{}}%
  }%
  \newcommand\@@CheckWhetherLeadingTokens[1]{%
    \expandafter\@CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
    {\@Exchange{\@firstoftwo}}{\@Exchange{\@secondoftwo}}%
    {\@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
     \expandafter\expandafter\expandafter}\expandafter\expandafter
     \expandafter}\expandafter\@secondoftwo\expandafter{\string}%
  }% 
  %%----------------------------------------------------------------------
  %% \@internaltokencheckdefiner{<internal token-check-macro>}%
  %%                           {<remover-macro>}%
  %%                           {<token sequence>}%
  %% - defines <internal token-check-macro> to snap everything 
  %%   until reaching <token sequence>-sequence and pass that to
  %%   \@@CheckWhetherLeadingPhrase.
  %% - defines <remover-macro> to gobble <token sequence>.
  %%   If <token sequence> is not there, this will raise an error.
  %%......................................................................
  \newcommand\@internaltokencheckdefiner[3]{%
    \newcommand#1{}\long\def#1##1#3{\@@CheckWhetherLeadingTokens{##1}}%
    \newcommand#2{}\long\def#2#3{}%
  }%
  \@internaltokencheckdefiner{\@CheckMinus}{\@gobbleminus}{-}%
  \@internaltokencheckdefiner{\@CheckPlus}{\@gobbleplus}{+}%
  \@internaltokencheckdefiner{\@CheckSlash}{\@gobbleslash}{/}%
  %%----------------------------------------------------------------------
  %% \mycmd<optional - or + or />[optional argument]{<mandatory argument>}
  %%
  %% !!! <mandatory argument> must be nested in curly braces !!!
  %% !!! <mandatory argument> must be nested in curly braces !!!
  %%......................................................................
  \newcommand\mycmd{}%
  % make \mycmd brace-delimited:
  \long\def\mycmd#1#{%
    \@CheckWhetherLeadingTokens{-}{.}{\@CheckMinus}{#1}{%
      \expandafter\mycmd@minus\@gobbleminus
    }{%
      \@CheckWhetherLeadingTokens{+}{.}{\@CheckPlus}{#1}{%
        \expandafter\mycmd@plus\@gobbleplus
      }{%
        \@CheckWhetherLeadingTokens{/}{.}{\@CheckSlash}{#1}{%
          \expandafter\mycmd@slash\@gobbleslash
         }{\mycmd@else}%
      }%
    }#1%
  }%
  \newcommand\mycmd@minus[2][Optional argument's default at minus]{%
    Case minus-sign:\\%
    Optional argument is: \texttt{#1}\\%
    Mandatory argument is: \texttt{#2}%
  }%
  \newcommand\mycmd@plus[2][Optional argument's default at plus]{%
    Case plus-sign:\\%
    Optional argument is: \texttt{#1}\\%
    Mandatory argument is: \texttt{#2}%
  }%
  \newcommand\mycmd@slash[2][Optional argument's default at slash]{%
    Case slash-sign:\\%
    Optional argument is: \texttt{#1}\\%
    Mandatory argument is: \texttt{#2}%
  }%
  \newcommand\mycmd@else[2][Optional argument's default at other cases]{%
    Some other case:\\
    Optional argument is: \texttt{#1}\\%
    Mandatory argument is: \texttt{#2}%
  }%
}%

\begin{document}

\vspace*{-1.33in}%
\parskip=\baselineskip
\parindent=0pt
\enlargethispage{1in}%
\footnotesize

\verb|\mycmd{-}| yields:\\
\mycmd{-}

\verb|\mycmd{+}| yields:\\
\mycmd{+}

\verb|\mycmd{/}| yields:\\
\mycmd{/}

\verb|\mycmd{Some other case without given optional argument}| yields:\\
\mycmd{Some other case without given optional argument}

\verb|\mycmd[Given optional argument]{Some other case with given optional argument}| yields:\\
\mycmd[Given optional argument]{Some other case with given optional argument}

\verb|\mycmd+{Plus case without given optional argument}| yields:\\
\mycmd+{Plus case without given optional argument}

\verb|\mycmd+[Given optional argument]{Plus case with given optional argument}| yields:\\
\mycmd+[Given optional argument]{Plus case with given optional argument}

\verb|\mycmd-{Minus case without given optional argument}| yields:\\
\mycmd-{Minus case without given optional argument}

\verb|\mycmd-[Given optional argument]{Minus case with given optional argument}| yields:\\
\mycmd-[Given optional argument]{Minus case with given optional argument}

\verb|\mycmd/{Slash case without given optional argument}| yields:\\
\mycmd/{Slash case without given optional argument}

\verb|\mycmd/[Given optional argument]{Slash case with given optional argument}| yields:\\
\mycmd/[Given optional argument]{Slash case with given optional argument}

\end{document}

enter image description here

  • Thank you Anno for your so detail suggestion. I think I need time to understand it. – lyl May 17 '18 at 0:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.