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}
16
  • 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, 2019 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. May 12, 2019 at 18:30
  • 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, 2019 at 18:36
  • 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 May 12, 2019 at 18:41
  • 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, 2019 at 19:10

3 Answers 3

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

7
  • 1
    You forgot the E argument :( May 12, 2019 at 18:56
  • @PhelypeOleinik you might go for it, I don't like E types very much (but they are so powerful, why is it considered questionable syntax?) and they still are experimental.
    – Skillmon
    May 12, 2019 at 18:58
  • Just kidding ;) The E type, even though experimental, seems very robust, and is not considered questionable. How one use it, on the other hand... May 12, 2019 at 19:13
  • Why did you use \ExplSyntaxOn in star syntax?
    – bp2017
    May 12, 2019 at 19:25
  • 1
    @bp2017 I used \ExplSyntaxOn on all of them because they all were in that regime in my full document. Also the meaning of ~ would change and you'd have to make sure not to introduce parasitic spaces.
    – Skillmon
    May 12, 2019 at 19:44
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

3
  • 1
    Why is it a bad syntax?
    – bp2017
    May 13, 2019 at 0:11
  • 1
    @bp2017 Because you are specifying the first optional argument when you don't want to set a value for it.
    – egreg
    May 13, 2019 at 6:16
  • 1
    This is (almost) exactly the same as my first code block in my answer.
    – Skillmon
    May 13, 2019 at 6:50
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

You must log in to answer this question.

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