3

I have defined a command low that adds a subscript to an argument:

\newcommand{\low}[1]{{#1}_{l_{\mathcal{A}}}}

However, if the argument of low contains subscripts itsself (for example in the case of \low{\low{\Sigma}}) it is not easy to see that the subscripts introduced by the outer low belong to the whole argument and not just to the first subscript. Therefore I would like to automatically introduce parentheses if the argument of low contains a subscript (so \low{\low{\Sigma}} should look like \low{(\low{\Sigma})}.

In order to achieve this I tried the following using the xifthen package:

\newcommand{\low}[1]{\ifthenelse{\isin{_}{#1}}{{(#1)}_{l_{\mathcal{A}}}}{{#1}_{l_{\mathcal{A}}}}}

However to my surprise this command only sometimes inserts the parantheses. In particular it does not do so in the example given above. Why does this happen and what can I do to fix this?

EDIT: It seems that the problem is that \isin does not unroll the definitions of commands. @egreg has already provided an answer that allows me to check for nested calls of \low but does not work for arguments that contain other commands with subscripts. Does anyone have a solution that works for arbitrary arguments?

2 Answers 2

4

In some cases applying \protected@edef and \@onelevel@sanitize before checking for a "stringified" _ might work out:

\documentclass{article}
\usepackage{amsmath}
\usepackage{xifthen}

\makeatletter    
\DeclareRobustCommand\DetectUnderscore[1]{%
  \begingroup
  \protected@edef\@tempa{#1}%
  \@onelevel@sanitize\@tempa
  \expandafter\expandafter\expandafter\endgroup
  \expandafter\expandafter\expandafter\ifthenelse
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\expandafter\isin
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\string_%
  \expandafter}%
  \expandafter{%
  \@tempa}}{{(#1)}}{{#1}}%
}%
\newcommand\low[1]{%
  \DetectUnderscore{#1}%
  _{l_{\mathcal{A}}}%
}
\makeatother

\begin{document}

\[
\low{\Sigma} \qquad 
\low{\low{\Sigma}} \qquad
\low{\low{\low{\Sigma}}} \qquad
\]
\[
\low{\Sigma_b} \qquad 
\low{\low{\Sigma_b}} \qquad
\low{\low{\low{\Sigma_b}}} \qquad
\]
\[
\low{{\Sigma_b}} \qquad
\low{b_{\low{c_{\low{\Sigma_d}}}}} \qquad
(\Sigma_b)_{l_\mathcal{A}}
\]
\[
\low{\low{1} + 2}
\]    
\end{document}

enter image description here

\expandafter causes that the next but one token —if expandable—gets expanded exactly once before the next token gets expanded if expandable. (La)TeX considers the work of \expandafter done when expansion of the next but one token is done. Therefore you can use chains/sequences of \expandafter to have (La)TeX "jump" over k tokens for first expanding the (k+1)-th token.

\@onelevel@sanitize\macro changes the definition of \macro so that \macro spits out a sequence of character tokens of category code 12(other) that looks like the token-sequence that would have been "spit out" by \macro before applying \@onelevel@sanitize. It is almost like redefining \macro to what you get by applying \string to each token of \macro's definition.

\protected@edef defines a macro but before doing so, it expands all expandable tokens of the definition-text except those that either are defined via \DeclareRobustCommand or are preceded by the token \protect. You might say: \protected@edef does "unroll" the definitions of the tokens contained in its definition-text before actually performing the assignment.

\@tempa is a scratch-macro which gets defined by means of \protected@edef to expand to the argument #1 with all definitions in #1 "unrolled".

The \ifthenelse{\isin...}-test does not find _ that are nested in curly-braces as curly braces usually have a special function. Therefore \@onelevel@sanitize is applied for turning all tokens, and thus also the curly braces, into ordinary harmless character-tokens of category code 12(other) which do not disturb the \ifthenelse{\isin...} test.


And here is a routine which does not check for (stringified) underscore of category code 12 (other) via xifthen's \ifthenelse{\isin...}-thingie but does check for tokens of category code 8 (subscript) without stringification.

The routine recursively calls itself when examining the tokens that form the argument.

The routine still does not expand the argument—this still has to be done via \protected@edef.

\documentclass{article}
\usepackage{amsmath}

\makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia ;-) :
%%.............................................................................
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%%   \UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  {AB}
%%
%% !!! The argument of \UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via \UD@CheckWhetherNull before applying
%% \UD@ExtractFirstArg.
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  { #1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% 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\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has leading
%%                        catcode-1-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has no leading
%%                        catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@firstoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is a
%%                               space-token>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is not
%%                               a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
  {\UD@exchange{\UD@firstoftwo}}{\UD@exchange{\UD@secondoftwo}}%
  {\UD@exchange{ }{\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter}\expandafter\expandafter
   \expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%.............................................................................
%% Check whether brace-balanced argument starts with a token of
%% category code 8 (subscript)
%%.............................................................................
%% \UD@CheckWhetherFirstTokenHasCatcodeSubscript{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that 
%%                         <argument which is to be checked> has a first
%%                         token of catcode 8>}%
%%                      {<Tokens to be delivered in case that
%%                         <argument which is to be checked> does not have
%%                         a first token of catcode 8>}%
%%
\newcommand\UD@CheckWhetherFirstTokenHasCatcodeSubscript[1]{%
  \romannumeral0%
  \UD@CheckWhetherNull{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
    \UD@CheckWhetherBrace{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
      \UD@CheckWhetherLeadingSpace{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
        \expandafter\expandafter\expandafter\UD@@CheckWhetherFirstTokenHasCatcodeSubscript
        \UD@ExtractFirstArg{#1}%
      }%
    }%
  }%
}%
\newcommand\UD@@CheckWhetherFirstTokenHasCatcodeSubscript[1]{%
  \expandafter\ifcat_#1%
  \expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
  {\UD@exchange{ }{\expandafter}\UD@firstoftwo}%
  {\UD@exchange{ }{\expandafter}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument does contain underscore/tokens of 
%% category code 8 (subscript), no matter if nested in braces or not.
%%
%% \UD@CheckWhetherSubscriptTokens{<Argument which is to be checked>}%
%%                  {<Tokens to be delivered in case that 
%%                     <argument which is to be checked> contains
%%                     some token(s) of catcode 8 (subscript)>}%
%%                  {<Tokens to be delivered in case that
%%                     <argument which is to be checked> contains
%%                     no token of catcode 8 (subscript)>}%
%%
%%-----------------------------------------------------------------------------
\newcommand\UD@CheckWhetherSubscriptTokens{\romannumeral0\UD@@CheckWhetherSubscriptTokens}%
\newcommand\UD@@CheckWhetherSubscriptTokens[3]{%
  \UD@CheckWhetherNull{#1}{ #3}{%
    \UD@CheckWhetherLeadingSpace{#1}{%
      \expandafter\UD@@CheckWhetherSubscriptTokens\expandafter{\UD@removespace#1}{#2}{#3}%
    }{%
      \UD@CheckWhetherBrace{#1}{%
        \expandafter\expandafter\expandafter\UD@CheckWhetherSubscriptTokens
        \UD@ExtractFirstArg{#1}{ #2}{%
          \expandafter\UD@@CheckWhetherSubscriptTokens\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
        }%
      }{%
        \UD@CheckWhetherFirstTokenHasCatcodeSubscript{#1}{ #2}{%
           \expandafter\UD@@CheckWhetherSubscriptTokens\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
        }%
      }%
    }%
  }%
}%

\DeclareRobustCommand\UD@CallUD@CheckWhetherSubscriptTokensOnExpansion[1]{%
  \begingroup
  \protected@edef\@tempa{#1}%
  \expandafter\endgroup
  \expandafter\UD@CheckWhetherSubscriptTokens\expandafter{\@tempa}{{(#1)}}{{#1}}%
}%

\newcommand\low[1]{%
  \UD@CallUD@CheckWhetherSubscriptTokensOnExpansion{#1}_{l_{\mathcal{A}}}%
}
\makeatother

\begin{document}

% Let`s use | and \myunderscore in the same way as _ :

\catcode`\|=8

\let\myunderscore=_

\[
\low{\Sigma} \qquad 
\low{\low{\Sigma}} \qquad
\low{\low{\low{\Sigma}}} \qquad
\]
\[
\low{\Sigma\myunderscore b} \qquad 
\low{\low{\Sigma\myunderscore b}} \qquad
\low{\low{\low{\Sigma|b}}} \qquad
\]
\[
\low{{\Sigma_b}} \qquad
\low{b_{\low{c_{\low{\Sigma|d}}}}} \qquad
(\Sigma_b)_{l_\mathcal{A}}
\]
\[
\low{\low{1} + 2}
\]    
\end{document}

enter image description here

Be aware that this routine does not aim at detecting (stringified) _-character-tokens of category-code 12 (other) but at detecting all character-tokens (be they explicit or implicit) of category code 8 (subscript).

1
  • For the sake of having fun I just added another routine which required a bit more of -eh- crocheting and knitting by hand. ;-) That routine takes into account that you might use _ with a different catcode for other purposes than math-subscripting, and it also takes into account that you may assign catcode 8 to characters other than _ for using them for math-subscripting... Mar 13, 2019 at 15:11
1

An idea (not straight forward solution) is to place the argument inside a box and check its height with the height of a character you suppose is not too tall to need parenthesis but not too short to add parenthesis to your \Sigma.

And guess what will be our default argument: \Sigma... P

The code (containing some tests) is this:

\documentclass{article}
\def\DefLowArg{$\Sigma$}
\let\oldDefLowArg\DefLowArg
\newsavebox{\myAbox}
\newsavebox{\myBbox}
\newcommand{\low}[2][\DefLowArg]{\savebox\myAbox{\vbox{#1}}\savebox\myBbox{\vbox{\ensuremath{#2}}}
\ifdim\dimexpr\ht\myAbox+\dp\myAbox<\dimexpr\ht\myBbox+\dp\myBbox\relax
\left({#2}\right)_{l_{\mathcal{A}}}
\else {#2}_{l_{\mathcal{A}}}\fi
}
\begin{document}

\[\low{\low{\Sigma}}\]

\[\low{\Sigma}\]
\[
\low{\sum_{i=3}^5 F(x)}
\]

\[
\low{\frac{F(x)}{x+5}}
\]

\[\low{F_x}\]

\[\low[1/4]{F(x)}\]

\[\low{x^2}\]

\[
\low{G_x}
\]

These commands may be should add without parentheses

\[
\low{g(z)}
\]
\[
\low{F(x)}
\]

{\bfseries Solution 1 Add an tall optional argument in the command like: \verb|\low[/]{F(x)}|}

\[
\low[/]{g(z)}
\]
\[
\low[/]{F(x)}
\]

{\bfseries Solution 2 Change the Default argument \verb|\DefLowArg| to something tall enough (return with \verb|\let\DefLowArg\oldDefLowArg|):}

\xdef\DefLowArg{/}

\[
\low{g(z)}
\]
\[
\low{F(x)}
\]
\let\DefLowArg\oldDefLowArg


{\bfseries And back to default}

\[
\low{F(X)}
\]

\end{document}

That produces:

enter image description here

PS:Of course manual solutions should be added in special cases but anyway in your command I am sure you would have exceptions for many cases.

You must log in to answer this question.

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