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}

  • 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. – Max Chernoff Jan 18 '23 at 05:03
  • I understand, \small is not expandable because in its definition, \selectfont has already expanded \fontsize{}{}. – Vincent Krebs Jan 22 '23 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. – Max Chernoff Jan 23 '23 at 05:54

3 Answers3

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}

Skillmon
  • 60,462
  • I see, so this was an expandability issue after all. I did not know of exp_not:n, many thanks. – Vincent Krebs Jan 18 '23 at 06:11
  • 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 Jan 18 '23 at 06:45
  • Really? Didn't know. I guess it makes sense since the -DocumentCommand part means it is intended for the body, not for low-level commands. I am following the model given to me through egreg's kind answers here https://tex.stackexchange.com/a/647169/262813 and here https://tex.stackexchange.com/a/669975/262813. – Vincent Krebs Jan 18 '23 at 08:14
  • 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 Jan 18 '23 at 08: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 Jan 18 '23 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 Jan 18 '23 at 10:17
  • 1
    @VincentKrebs \DeclareRobustCommand doesn't define \protected but using LaTeX2e's older \protect mechanism. – Skillmon Jan 18 '23 at 11:35
  • If "LaTeX2e has no good interface to \protected", why not use LaTeX3? I mean expl3 (but something other than \NewDocumentCommand, which you said was not appropriate for internals). – Vincent Krebs Jan 18 '23 at 16:44
  • 1
    @VincentKrebs because LaTeX3's only sensible interface to \protected for internals is \cs_new_protected:Npn or \cs_new_protected:Nn in the expl3 language, and then we mix expl3 code with LaTeX2e code which is also not-so-good style, but I have no definitive answer here, you could use \cs_new_protected:Npn instead of the \@ifdefinable and \protected\def. – Skillmon Jan 18 '23 at 19:22
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

Skillmon
  • 60,462
  • @VincentKrebs \^^ is not a macro, \^^@ is (^^ is special, it changes the next character as a rot64, making ^^@ the character code 0, but that only works if ^ has category code math-superscript, which is changed by gb4e). – Skillmon Jan 19 '23 at 05:33
  • 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 Jan 19 '23 at 05:36
  • @VincentKrebs don't bother, I'll publish them in a few days or so... Btw. installing it manually is pretty easy: Clone the repository, then run l3build install on the command line inside the main directory. – Skillmon Jan 19 '23 at 17:24
  • 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 . The l3build program should be part of your LaTeX-distribution. – Skillmon Jan 19 '23 at 17:54
  • 1
    @VincentKrebs if you want to later remove the package run l3build uninstall, afterwards you can delete the folder. – Skillmon Jan 19 '23 at 17:56
  • I see you released expkv-bundle yesterday on ctan ;) cool ;)) (I had not managed to install it manually because I got an error l3build-unpack.lua:87: attempt to index a nil value. By the way I downloaded the ctan file but build.lua is missing from the .zip. (and expkv-bundle still does not appear in the MikTeX database) – Vincent Krebs Jan 24 '23 at 21:49
  • 1
    @VincentKrebs give the poor MikTeX maintainer a few days :) And yes, CTAN-uploads don't include the build scripts... – Skillmon Jan 25 '23 at 05:01
  • Thought that was auto! – Vincent Krebs Jan 25 '23 at 10:26
  • 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 Jan 25 '23 at 10:45
  • Anyway congratulations, this is an amazing package, now unified. – Vincent Krebs Jan 25 '23 at 14:51
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

  • 1
    That's indeed much better than multiple optional arguments. – egreg Jan 18 '23 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. – Vincent Krebs Jan 18 '23 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 Jan 18 '23 at 19:31