3

If \myB accepts two optional arguments, \myB sets both of them to default values, \myB[5] sets first to 5 and second to default value, \myB[5][10] sets first to 5 and second to 10. But how do we set #2 argument only? Doing \myB[][10] does NOT set first argument to default value (it just makes it disappear).

\documentclass{standalone}
\usepackage{xparse}
% each of two optional arguments defaults to zero
\NewDocumentCommand{\myB}{O{0}O{0}}
  {%
    myB: #1, #2%
  }
\begin{document}
% how to call `myB` so that it only has #2 set? (not #1)
\myB[][6] % this is no good since `[]` removes default zero
\end{document}
bp2017
  • 3,756
  • 1
  • 13
  • 33
  • This is the general issue with two optional arguments back-to-back: if they are no co-dependent, giving the second alone is always an issue. – Joseph Wright May 12 '19 at 18:19
  • @bp2017 You could check if the first argument is empty: \tl_if_empty:nTF{#1}{myB:~0,~#2}{myB:~#1,~#2}. – Phelype Oleinik May 12 '19 at 18:23
  • @PhelypeOleinik, why do you need ~ before args? – bp2017 May 12 '19 at 18:28
  • 1
    @bp2017 you don't need it here, but if you turn \ExplSyntaxOn a normal space is ignored and ~ will be a normal space instead. I guess he wrote that out of habit (or to be more precise, you'll need \ExplSyntaxOn to use \tl_if_empty:nTF). – Skillmon May 12 '19 at 18:29
  • 1
    @Skillmon I thought of that. But \IfNoValueTF checks only for -NoValue-. It won't work for [][6] because #1 will be empty. – Phelype Oleinik May 12 '19 at 18:30
  • @PhelypeOleinik You're right, my bad. We could do something like O{0} s O{0} and then you can use \foo*[5] to only set the second one. – Skillmon May 12 '19 at 18:31
  • @Skillmon In that case \IfNoValue would work :-) – Phelype Oleinik May 12 '19 at 18:32
  • @PhelypeOleinik but wouldn't make sense if the defaults are to be set anyway. – Skillmon May 12 '19 at 18:33
  • If one doesn't care too much about conventions of argument types, one could use D(){0} O{0}, too. Then \foo(5) would only set the first argument, \foo[5] the second, and \foo(5)[5] both. – Skillmon May 12 '19 at 18:34
  • 1
    @bp2017 normally you could use processors, but because of reasons not understandable to myself, the -NoValue- flag wouldn't ever be passed to an argument processor. – Skillmon May 12 '19 at 18:36
  • I think the best solution is to include an optional single token between the two arguments which you don't really need. This way you get the full versatility without introducing too questionable syntax. – Skillmon May 12 '19 at 18:38
  • @bp2017 if you want to introduce a key=value syntax you can take a look at the l3keysmodule (or any other key-value package), but the #2=foo syntax doesn't look like a good idea. – Skillmon May 12 '19 at 18:40
  • 2
    @Skillmon Did you ask for questionable syntax?! Define \NewDocumentCommand{\myB}{E{12}{{0}{0}}}{myB: #1, #2} and use \myB2{6}. Problem. Solved. :D – Phelype Oleinik May 12 '19 at 18:41
  • @PhelypeOleinik we don't do that here :) – Skillmon May 12 '19 at 18:42
  • @bp2017 \NewDocumentCommand \foo { O{0} } { myB:~#1,~\baz } \NewDocumentCommand \baz { O{0} } { #1 }?! Or do you mean that you need to pass optional arguments on to another macro like \NewDocumentCommand \foo { o o } { \IfNoValueTF { #2 } { \IfNoValueTF { #1 } { \baz } { \baz [ { #1 } ] } } { \baz [ { #1 } ] [ { #2 } ] } } – Skillmon May 12 '19 at 19:00
  • 1
    @bp2017 if you meant the latter, I might do this with argument processors as well: \cs_new_protected:Npn \__bdmmxvii_Oarg_pass_proc:n #1 { \tl_if_empty:nTF { #1 } { \cs_set:Npn \ProcessedArgument {} } { \cs_set:Npn \ProcessedArgument { [ { #1 } ] } } } \NewDocumentCommand \foo { >{ \__bdmmxvii_Oarg_pass_proc:n } O{} O{0} } { \baz #1 { #2 } } if the first argument of \baz is optional and the second mandatory. – Skillmon May 12 '19 at 19:10

3 Answers3

5

As Phelype Oleinik suggested you could use an empty first optional argument and assign the default if it is empty. I'd do that with an argument processor:

\ExplSyntaxOn
\cs_new_protected:Npn \__bdmmxvii_Oarg_proc:nn #1 #2
  {
    \tl_if_empty:nTF { #2 }
      { \cs_set:Npn \ProcessedArgument { #1 } }
      { \cs_set:Npn \ProcessedArgument { #2 } }
  }
\NewDocumentCommand \twooptsA { >{ \__bdmmxvii_Oarg_proc:nn { 0 } }O{} O{0} }
  {
    myB:~#1,~#2
  }
\ExplSyntaxOff

Alternatively my suggestion with the star:

\ExplSyntaxOn
\NewDocumentCommand \twooptsB { O{0} s O{0} }
  {
    myB:~#1,~#3
  }
\ExplSyntaxOff

And as a third option a key=value syntax:

\ExplSyntaxOn
\keys_define:nn { bdmmxvii }
  {
    ,1 .tl_set:N  = \__bdmmxvii_arg_a_tl
    ,1 .initial:n = 0
    ,2 .tl_set:N  = \__bdmmxvii_arg_b_tl
    ,2 .initial:n = 0
  }
\NewDocumentCommand \twooptsC { O{} }
  {
    \group_begin:
    \keys_set:nn { bdmmxvii } { #1 }
    \__bdmmxvii_twooptsC:VV \__bdmmxvii_arg_a_tl \__bdmmxvii_arg_b_tl
    \group_end:
  }
\cs_new:Npn \__bdmmxvii_twooptsC:nn #1 #2
  {
    myB:~#1,~#2
  }
\cs_generate_variant:Nn \__bdmmxvii_twooptsC:nn { VV }
\ExplSyntaxOff

Everything together in a single document:

\documentclass[]{article}

\usepackage{xparse}

\ExplSyntaxOn
\cs_new_protected:Npn \__bdmmxvii_Oarg_proc:nn #1 #2
  {
    \tl_if_empty:nTF { #2 }
      { \cs_set:Npn \ProcessedArgument { #1 } }
      { \cs_set:Npn \ProcessedArgument { #2 } }
  }
\NewDocumentCommand \twooptsA { >{ \__bdmmxvii_Oarg_proc:nn { 0 } }O{} O{0} }
  {
    myB:~#1,~#2
  }
\NewDocumentCommand \twooptsB { O{0} s O{0} }
  {
    myB:~#1,~#3
  }
\keys_define:nn { bdmmxvii }
  {
    ,1 .tl_set:N  = \__bdmmxvii_arg_a_tl
    ,1 .initial:n = 0
    ,2 .tl_set:N  = \__bdmmxvii_arg_b_tl
    ,2 .initial:n = 0
  }
\NewDocumentCommand \twooptsC { O{} }
  {
    \group_begin:
    \keys_set:nn { bdmmxvii } { #1 }
    \__bdmmxvii_twooptsC:VV \__bdmmxvii_arg_a_tl \__bdmmxvii_arg_b_tl
    \group_end:
  }
\cs_new:Npn \__bdmmxvii_twooptsC:nn #1 #2
  {
    myB:~#1,~#2
  }
\cs_generate_variant:Nn \__bdmmxvii_twooptsC:nn { VV }
\ExplSyntaxOff

\begin{document}
\twooptsA\ \twooptsA[1][2] \twooptsA[1] \twooptsA[][2]

\twooptsB\ \twooptsB[1][2] \twooptsB[1] \twooptsB*[2]

\twooptsC\ \twooptsC[1=1,2=2] \twooptsC[1=1] \twooptsC[2=2]
\end{document}

Everything results in the same:

enter image description here

Skillmon
  • 60,462
2

This is bad syntax and you should consider to avoid it.

Anyway, you can preprocess the argument. Of course, you cannot set the first optional argument to empty.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\IfEmpty}{mm}
 {
  \tl_if_blank:nTF { #2 }
   { \tl_set:Nn \ProcessedArgument { #1 } }
   { \tl_set:Nn \ProcessedArgument { #2 } }
 }
\ExplSyntaxOff

\NewDocumentCommand{\myB}{>{\IfEmpty{0}}O{0}O{0}}{%
  \#1 is #1 -- \#2 is #2
}

\begin{document}

\myB

\myB[1]

\myB[1][2]

\myB[][3]

\end{document}

enter image description here

egreg
  • 1,121,712
0

Another option: use different argument delimiters, with the specifier D:

\NewDocumentCommand{\myB}{ D(){0} O{0} }{%
  \#1 = #1, \#2 = #2%
}

\myB % #1 = 0, #2 = 0 \myB(1) % #1 = 1, #2 = 0 \myB[2] % #1 = 0, #2 = 2 \myB(1)[2] % #1 = 1, #2 = 2

O'Neil
  • 205