6

So I'm trying to implement a COOL-style \Sum macro which is supposed to support the following syntax:

\Sum{\ldots}  % -> \sum \ldots
\Sum[i]{\ldots}  % -> \sum_{i} \ldots
\Sum[i \in I]{\ldots}  % -> \sum_{i \in I} \ldots
\Sum[i, 0, N]{\ldots}  % -> \sum_{i=0}^{N} \ldots
\Sum[{i,j}, 0, N]{\ldots}  % -> \sum_{i,j=0}^{N} \ldots
\Sum[{i,j,k}]{\ldots}  % -> \sum_{i,j,k} \ldots

The important bit is that I would like braces to protect commas in the first argument. The actual implementation is in expl3 with the user interface using xparse. Unfortunately, xparse appears to always strip braces inside arguments if they surround the whole argument such that this protection will not work in all cases, no matter what my internal code does. Looking at the docs, I couldn't find a way to prevent this.

I tried macros signatures of the forms

\DeclareDocumentCommand \Sum { s >{ \SplitArgument{2}{,} } o m }
  { ... }
\DeclareDocumentCommand \Sum { s o m }
  { ... }

Am I missing something here, is there a (undocumented) way to make xparse not strip such braces short of circumventing xparse and implementing the interface manually (maybe using argument processors?)? If not, would this warrant a bug report/feature request at the latex github?

  • Not a solution: You could input \Sum[{{i,j,k}}]{\ldots}. – Skillmon Dec 15 '18 at 13:57
  • 3
    Why not simply using ; as delimiter? You would need no braces. – egreg Dec 15 '18 at 14:03
  • Using ; would likely work for my usecases, I don't think I've ever used a semicolon in sum limits. Still, wrapping the argument in braces was the first and obvious (to me) thing I tried to protect arguments that contain the delimiter, and I was surprised that this didn't work. Maybe I'll go for ';' plus the workaround from the other answers (even though that might be overkill) – Wisperwind Dec 16 '18 at 13:05
5

You can trick this by inserting another token after the opening bracket if there is one (proof of concept like answer). Replace \Wisperwind_Sum_original_code:nnn with your original code to typeset the sum (including the handling of the optional star with \IfBooleanTF).

\documentclass[]{article}

\usepackage{xparse}
\ExplSyntaxOn
\clist_new:N \l_Wisperwind_clist
\NewDocumentCommand \Sum { s t[ }
  {
    \IfBooleanTF { #2 }
      { \Sum_two { #1 } [ \use_none:n {} }
      { \Sum_two { #1 } }
  }
\NewDocumentCommand \Sum_two { m O{ \use_none:n {} } m }
  {
    \Wisperwind_Sum_original_code:non { #1 } { #2 } { #3 }
  }
\cs_new:Npn \Wisperwind_Sum_original_code:nnn #1 #2 #3
  {
    \sum
    \clist_set:Nn \l_Wisperwind_clist { #2 }
    \int_case:nn { \clist_count:N \l_Wisperwind_clist }
      {
        { 1 } { \sb { \clist_item:Nn \l_Wisperwind_clist { \c_one_int } } }
        { 2 }
          {
            \sb { \clist_item:Nn \l_Wisperwind_clist { \c_one_int } }
            \sp { \clist_item:Nn \l_Wisperwind_clist { 2 } }
          }
        { 3 }
          {
            \sb
              {
                \clist_item:Nn \l_Wisperwind_clist { \c_one_int }
                =
                \clist_item:Nn \l_Wisperwind_clist { 2 }
              }
            \sp { \clist_item:Nn \l_Wisperwind_clist { 3 } }
          }
      }
    #3
  }
\cs_generate_variant:Nn \Wisperwind_Sum_original_code:nnn { non }
\ExplSyntaxOff

\begin{document}
$\Sum{\ldots}$  % -> \sum \ldots
$\Sum[i]{\ldots}$  % -> \sum_{i} \ldots
$\Sum[i \in I]{\ldots}$  % -> \sum_{i \in I} \ldots
$\Sum[i, 0, N]{\ldots}$  % -> \sum_{i=0}^{N} \ldots
$\Sum[{i,j}, 0, N]{\ldots}$  % -> \sum_{i,j=0}^{N} \ldots
$\Sum[{i,j,k}]{\ldots}$  % -> \sum_{i,j,k} \ldots
\end{document}

enter image description here

  • Thanks for this suggestion (although this is pretty much the 'hack' I had hoped to avoid)! The solution I had in mind would have been closer to @steven-b-segletes answer, but your code preserves the advanced parsing of nested optional arguments that xparse offers. – Wisperwind Dec 16 '18 at 12:55
4

Here using LaTeX2e (see below for Plain TeX):

\documentclass{article}
\usepackage{listofitems}
\makeatletter
\newcommand\Sum{\@ifnextchar[{\Sumaux[\@gobble}{\sum}}
\makeatother
\def\Sumaux[#1]#2{
  \sum
  \setsepchar{,}
  \readlist\sumargs{#1}
  \ifnum\listlen\sumargs[]>1\relax
    _{\sumargs[1]=\sumargs[2]}
    \ifnum\listlen\sumargs[]>2\relax^\sumargs[3]\fi
  \else
    _{\sumargs[1]}
  \fi
  #2
}
\begin{document}
\[\Sum{\ldots}  \]% -> \sum \ldots
\[\Sum[i]{\ldots}  \]% -> \sum_{i} \ldots
\[\Sum[i \in I]{\ldots}  \]% -> \sum_{i \in I} \ldots
\[\Sum[i, 0, N]{\ldots}  \]% -> \sum_{i=0}^{N} \ldots
\[\Sum[{i,j}, 0, N]{\ldots}  \]% -> \sum_{i,j=0}^{N} \ldots
\[\Sum[{i,j,k}]{\ldots}  \]% -> \sum_{i,j,k} \ldots
\end{document}

enter image description here

This approach can also be implemented in Plain TeX:

\input listofitems
\def\gobble#1{}
\def\Sum{\futurelet\next\doSum}
\def\doSum{\ifx[\next%
  \expandafter\Sumaux\expandafter[\expandafter\gobble\else\sum\fi}
\def\Sumaux[#1]#2{
  \sum
  \setsepchar{,}
  \readlist\sumargs{#1}
  \ifnum\listlen\sumargs[]>1\relax
    _{\sumargs[1]=\sumargs[2]}
    \ifnum\listlen\sumargs[]>2\relax^\sumargs[3]\fi
  \else
    _{\sumargs[1]}
  \fi
  #2
}
$$\Sum{\dots}  $$% -> \sum \ldots
$$\Sum[i]{\ldots}  $$% -> \sum_{i} \ldots
$$\Sum[i \in I]{\ldots}  $$% -> \sum_{i \in I} \ldots
$$\Sum[i, 0, N]{\ldots}  $$% -> \sum_{i=0}^{N} \ldots
$$\Sum[{i,j}, 0, N]{\ldots}  $$% -> \sum_{i,j=0}^{N} \ldots
$$\Sum[{i,j,k}]{\ldots}  $$% -> \sum_{i,j,k} \ldots
\bye
  • Thanks for the input, I'm writing this using expl3 + xparse, though (obviously I could translate your code to expl3) and for the reason mentioned in another comment will likely prefer @Skillmon 's suggestion – Wisperwind Dec 16 '18 at 12:57
3

The brace stripping is quite deliberate as \newcommand-generated commands requires braces for the case

\foo[{]}]{bar}

whereas xparse-generated ones do not. Without brace stripping,

\foo[{bar}]

and

\foo[bar]

could be treated differently.

In your case, you have a comma list with one entry. The obvious solution is to provide a second, empty, entry

\Sum[{i,j},]{\ldots}

as this is a no-op.

  • 1
    I actually find this behavior unexpected AND annoying. I would prefer \foo[{bar}] and \foo[bar] to be (potentially) treated differently. But I also see the need, as in the initial case you present, to embrace the bracket. – Steven B. Segletes Dec 15 '18 at 16:45
  • 2
    @StevenB.Segletes I guess the reason is as (almost) always of historical nature. Since optional arguments created with \newcommand behave this way (as well as those created with \def\foo[#1]) it is the expected behaviour for optional arguments, because it always was this way. – Skillmon Dec 15 '18 at 16:47
  • @StevenB.Segletes As we are not changing the catcode of [/], the only way to have a TeX <balanced text> argument as an optional argument is to use a brace pair. Thus when the optional argument is exactly one balanced text we strip braces. It's the only way to handle stuff 'reasonably'. – Joseph Wright Dec 15 '18 at 18:17
  • @JosephWright I think I don't really understand what exactly wouldn't work without stripping braces. Wrt the first example, I guess you meant to say \foo[\bar[baz]]{test} does not require braces if defined through xparse, because \foo[{]}]{bar} does. Is the reason mostly historical in order to match \newcommand as @skillmon suggests? For most macros, I would expect additional braces not being stripped to not be an issue, thus this was also rather unexpected to me. – Wisperwind Dec 16 '18 at 12:51
  • The workaround you suggest is what I'm currently doing, but I'd prefer not to have such quirks in the user interface. – Wisperwind Dec 16 '18 at 12:52

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.