0

Using expl3 syntax, I want in my \coolgloss command to use as an optional argument one or several formatting commands such as \small and \itshape.

With a command like \newcommand{\coolgloss}[3][\small\itshape]{text}{gloss} I have no problems, but I specifically want to use expl3 in order to have a split optional which includes the negative raise value for \rule.

It works if I only want to pass one command name (e.g. small) through \csname...\endcsname, but I want support for multiple commands (e.g. \small\itshape), and I want the arguments to be given with the backslash included.

Thus, the command must take a split optional argument, whose the second subargument (argument #2) is dedicated to the formatting:

\coolgloss[5,\small\itshape]{text}{gloss}

But when I input it, the \small command (not the \itshape one) triggers a TeX capacity exceeded error.

This has to be an issue with expansion and variable types, i.e. somewhere I should replace a n with some other type.

MWE

\documentclass{article}

\usepackage{gb4e}

\NewDocumentCommand{\coolglossaux}{mmmm}{%
    \renewcommand{\eachwordtwo}{\rule[-#1pt]{0pt}{0pt}#2}% !! #2 is the one !!
    \getwords(\lineone,\eachwordone)#3 \\%
    \getwords(\linetwo,\eachwordtwo)#4 \\%
    \loop\lastword{\eachwordone}{\lineone}{\wordone}%
    \lastword{\eachwordtwo}{\linetwo}{\wordtwo}%
    \global\setbox\gline=\hbox{\unhbox\gline
        \hskip\glossglue
        \vtop{\box\wordone
            \nointerlineskip
            \box\wordtwo
        }%
    }%
    \testdone
    \ifnotdone
    \repeat
    {\hskip -\glossglue}\unhbox\gline
}

\ExplSyntaxOn

\NewDocumentCommand{\coolgloss}{>{\SplitArgument{1}{,}}omm}
{
    \my_xcoolgloss:nnnn #1 {#2} {#3}
}
\cs_new_protected:Nn \my_xcoolgloss:nnnn
{
    \__my_xcoolgloss:eenn
    { \tl_if_novalue:nTF { #1 } { 10 } { #1 } }
    { \tl_if_novalue:nTF { #2 } {} { #2 } }
    { #3 }
    { #4 }
}

\cs_set_eq:NN \__my_xcoolgloss:nnnn \coolglossaux
\cs_generate_variant:Nn \__my_xcoolgloss:nnnn {ee}

\ExplSyntaxOff

\begin{document}
    
    \coolgloss[5,\itshape]{I like \LaTeX}{I like \LaTeX}
    
    %\coolgloss[5,\small\itshape]{I like \LaTeX}{I like \LaTeX} => TeX capacity exceeded
    
\end{document}
3
  • 1
    You can't expand \small (you get the same error with just \expanded{\small}). You need to either protect \small or not try and expand it. Commented Jan 18, 2023 at 5:03
  • I understand, \small is not expandable because in its definition, \selectfont has already expanded \fontsize{}{}. Commented Jan 22, 2023 at 16:16
  • The issue is actually a bit more subtle than that. \itshape and \small both ultimately call \selectfont, which is unexpandable since it calls \def. The difference is that \itshape is a \protected macro while \small is \protect'ed. \protected macros never expand (except with \expandafter) while \protect'ed macros only don't expand if they are used in certain LaTeX constructs (like \protected@edef). It's a fairly confusing topic, not at all helped by the confusing names. Commented Jan 23, 2023 at 5:54

3 Answers 3

4

As MaxChernoff said, you can't expand \small. For this reason the following uses \exp_not:n for your two optional sub-arguments, so that the \tl_if_novalue:nTF tests are completely processed, but their results don't expand any further.

EDIT: I changed the auxiliary \coolglossaux to be defined \protected via \@ifdefinable and \protected\def. This might not be ideal as well, but unfortunately LaTeX2e has no good interface to \protected outside the expl3 and \NewDocumentCommand routes. Also I've fixed a bug in the argument splitter if the optional argument isn't used at all.

\documentclass{article}

\usepackage{gb4e}

\makeatletter
% not perfect as well, but LaTeX2e itself has no good interface to `\protected`.
% The `\@ifdefinable` makes this behave like `\newcommand` even though we use
% a primitive `\def` for the definition.
\@ifdefinable{\coolglossaux}
    {%
        \protected\def\coolglossaux#1#2#3#4{%
            \renewcommand{\eachwordtwo}{\rule[-#1pt]{0pt}{0pt}#2}% !! #2 is the one !!
            \getwords(\lineone,\eachwordone)#3 \\%
            \getwords(\linetwo,\eachwordtwo)#4 \\%
            \loop\lastword{\eachwordone}{\lineone}{\wordone}%
            \lastword{\eachwordtwo}{\linetwo}{\wordtwo}%
            \global\setbox\gline=\hbox{\unhbox\gline
                \hskip\glossglue
                \vtop{\box\wordone
                    \nointerlineskip
                    \box\wordtwo
                }%
            }%
            \testdone
            \ifnotdone
            \repeat
            {\hskip -\glossglue}\unhbox\gline
        }
    }
\makeatother

\ExplSyntaxOn

\NewDocumentCommand{\coolgloss}{>{\SplitArgument{1}{,}}omm}
{
    \tl_if_novalue:nTF {#1}
        { \my_xcoolgloss:nnnn {#1} {#1} {#2} {#3} }
        { \my_xcoolgloss:nnnn #1 {#2} {#3} }
}
\cs_new_protected:Nn \my_xcoolgloss:nnnn
{
    \__my_xcoolgloss:eenn
    { \tl_if_novalue:nTF { #1 } { 10 } { \exp_not:n {#1} } }
    { \tl_if_novalue:nTF { #2 } {} { \exp_not:n {#2} } }
    { #3 }
    { #4 }
}

\cs_set_eq:NN \__my_xcoolgloss:nnnn \coolglossaux
\cs_generate_variant:Nn \__my_xcoolgloss:nnnn {ee}

\ExplSyntaxOff

\begin{document}
    
    \coolgloss[5,\itshape]{I like \LaTeX}{I like \LaTeX}
    
    \coolgloss[5,\small\itshape]{I like \LaTeX}{I like \LaTeX} => TeX capacity exceeded
    
\end{document}
9
  • 1
    @VincentKrebs Just a note: You shouldn't use \NewDocumentCommand for internals such as your auxiliary, there is some overhead to it, even if all arguments are just m or you're using no argument at all. Better to use a protected normal macro with 4 arguments in this case.
    – Skillmon
    Commented Jan 18, 2023 at 6:45
  • 1
    @VincentKrebs imho, the \cs_new_eq:NN ... \cs_generate_variant:Nn approach makes sense if you want to access existing functions, but I'd not define a completely new function that's only ever used as an internal via \NewDocumentCommand (which seems to be the case for your \coolglossaux).
    – Skillmon
    Commented Jan 18, 2023 at 8:19
  • 1
    \newcommand doesn't define the macros protected, but your auxiliary should be, so no, not \newcommand. You don't need expl3 to split your argument, but the full expansion to use the defaults via the \cs_generate_variant:Nn route is much more handy than doing that by hand.
    – Skillmon
    Commented Jan 18, 2023 at 10:16
  • 1
    @VincentKrebs note that the code above has a bug due to a strange design decision: Argument processors (like \SplitArgument) are not used on o types if the optional argument was not used.
    – Skillmon
    Commented Jan 18, 2023 at 10:17
  • 1
    @VincentKrebs \DeclareRobustCommand doesn't define \protected but using LaTeX2e's older \protect mechanism.
    – Skillmon
    Commented Jan 18, 2023 at 11:35
1

Obligatory self-advertisement since key=value solutions were mentioned: expkv

An implementation with a key=value solution and smartish unknown-key handling (literal numeric input will be assumed to be space, else it's assumed to be style -- if this is unwanted remove the block in \makeatletter...\makeatother) using the expkv family:

\documentclass{article}

\usepackage{expkv-unravel}

% has to be loaded before `gb4e` as the latter changes the category code of ^
\usepackage{expkv-cs}

% defining the key=value interface
\NewDocumentCommand\coolgl{O{}mm}
  {\coolglKV{#1}{#2}{#3}}
\ekvcSplitAndForward\coolglKV\coolglossaux
  {%
    space = 10,
    style = \small
  }

% defining a special rule how to handle unknown keys without a value
\makeatletter
\ekvdefunknownNoVal{\string\coolglKV}
  {%
    \coolglKV@ifnumber{#1}% will be true only for literal numerical input
      {\ekvcPass\coolglKV{space}}%
      {\ekvcPass\coolglKV{style}}%
        {#2}%
  }
% defining a smartish parser
\ExplSyntaxOn
\cs_new_eq:NN \coolglKV@ifempty \tl_if_empty:nTF
\ExplSyntaxOff
\protected\long\def\coolglKV@ifnumber#1%
  {%
    \begingroup
      \afterassignment\coolglKV@ifnumber@aux
      \count\z@=\iffalse{\fi0#1}%
  }
\protected\def\coolglKV@ifnumber@aux
  {%
    \endgroup
    \expandafter\coolglKV@ifempty\expandafter{\iffalse}\fi
  }
\makeatother

\protected\def\coolglossaux#1#2#3#4{%
    \renewcommand{\eachwordtwo}{\rule[-#1pt]{0pt}{0pt}#2}% !! #2 is the one !!
    \getwords(\lineone,\eachwordone)#3 \\%
    \getwords(\linetwo,\eachwordtwo)#4 \\%
    \loop\lastword{\eachwordone}{\lineone}{\wordone}%
    \lastword{\eachwordtwo}{\linetwo}{\wordtwo}%
    \global\setbox\gline=\hbox{%
        \unhbox\gline\hskip\glossglue\vtop{%
            \box\wordone\nointerlineskip\box\wordtwo
        }%
    }%
    \testdone
    \ifnotdone
    \repeat
    {\hskip -\glossglue}\unhbox\gline
}

% evil package changing catcodes in the preamble... :P
\usepackage{gb4e}

\begin{document}
\coolgl[style=\itshape\bfseries\footnotesize,space=30]{%
i try something very long in order to get two lines this is so wonderful and now you see the vertical space optional argument is perfectly working}{%
i try something very long in order to get two lines this is so wonderful and now you see the vertical space optional argument is perfectly working}

\coolgl[\itshape\bfseries\footnotesize,60]{%
i try something very long in order to get two lines this is so wonderful and now you see the vertical space optional argument is perfectly working}{%
i try something very long in order to get two lines this is so wonderful and now you see the vertical space optional argument is perfectly working}
\end{document}

enter image description here

10
  • 1
    @VincentKrebs you did nothing wrong, I just used a feature of expkv that is in the current development version, forgetting I didn't yet publish that particular feature. You can get the up-to-date version on GitLab, or just wait for a few days (maybe a week) until I release it... The current version doesn't support picking up the un-\detokenized key names of unknown keys, hence using them for formatting stuff won't work with it.
    – Skillmon
    Commented Jan 19, 2023 at 5:36
  • 1
    @VincentKrebs Exactly as I've written, open a command line in that directory (if you're on Windows: While being in the directory with the Explorer hold Shift and right-click on a blank space, then select "open in command line" (or something like that, no idea how it's translated)), then type l3build install and hit <Enter>. The l3build program should be part of your LaTeX-distribution.
    – Skillmon
    Commented Jan 19, 2023 at 17:54
  • 1
    @VincentKrebs if you want to later remove the package run l3build uninstall, afterwards you can delete the folder.
    – Skillmon
    Commented Jan 19, 2023 at 17:56
  • 1
    @VincentKrebs give the poor MikTeX maintainer a few days :) And yes, CTAN-uploads don't include the build scripts...
    – Skillmon
    Commented Jan 25, 2023 at 5:01
  • 1
    @VincentKrebs depends. If I upload the thing to CTAN it takes roughly 24h to be on the mirrors. It is a new package (earlier I uploaded the packages individually, now it's bundled into one upload), so no automatic routine for TeXLive and MikTeX maintainers, afaik.
    – Skillmon
    Commented Jan 25, 2023 at 10:45
0

I thought it could come in handy to other people, to show that, with a key=value system, no such issues arise.

Note that it is most often the better approach in many aspects.

\documentclass{article}

\usepackage{gb4e}

\protected\def\coolglossaux#1#2#3#4{%
    \renewcommand{\eachwordtwo}{\rule[-#1pt]{0pt}{0pt}#2}% !! #2 is the one !!
    \getwords(\lineone,\eachwordone)#3 \\%
    \getwords(\linetwo,\eachwordtwo)#4 \\%
    \loop\lastword{\eachwordone}{\lineone}{\wordone}%
    \lastword{\eachwordtwo}{\linetwo}{\wordtwo}%
    \global\setbox\gline=\hbox{%
        \unhbox\gline\hskip\glossglue\vtop{%
            \box\wordone\nointerlineskip\box\wordtwo
        }%
    }%
    \testdone
    \ifnotdone
    \repeat
    {\hskip -\glossglue}\unhbox\gline
}

\ExplSyntaxOn

\NewDocumentCommand{\coolgl}{O{}mm}
{
    \group_begin:
    \keys_set:nn { coolgl } { #1 }
    \coolgl:VVnn
    \coolgl_space
    \coolgl_style
    { #2 }
    { #3 }
    \group_end:
}
\keys_define:nn { coolgl }
{
    space.tl_set:N = \coolgl_space,
    style.tl_set:N = \coolgl_style,
    space.initial:n = 10,
    style.initial:n = \small,
}

\cs_set_eq:NN \coolgl:nnnn \coolglossaux

\cs_generate_variant:Nn \coolgl:nnnn { VV }

\ExplSyntaxOff


\begin{document}
        
\coolgl[space=30,style=\itshape\bfseries\footnotesize]{%
i try something very long in order to get two lines this is so wonderful and now you see the vertical space optional argument is perfectly working}{%
i try something very long in order to get two lines this is so wonderful and now you see the vertical space optional argument is perfectly working}
    
\end{document}

enter image description here

3
  • 1
    That's indeed much better than multiple optional arguments.
    – egreg
    Commented Jan 18, 2023 at 17:38
  • It's interesting to know how to do both and compare them (even maybe in some cases to have two versions of the same auxiliary, like here \coolgloss and \coolgl). For example, I'm happy to know about the \exp_not:n{x} command. The expl3 syntax seems to be powerful. Commented Jan 18, 2023 at 17:58
  • 1
    @VincentKrebs expl3 is indeed very powerful as it provides a very well thought out set of powerful functions and conventions. With it TeX coding feels more like programming in a high level programming language than in a strange eccentric macro expansion language (though I definitely love the quirks of that strange eccentric macro expansion language).
    – Skillmon
    Commented Jan 18, 2023 at 19:31

You must log in to answer this question.

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