2

I have a document command which accepts several optional token arguments, and I'd like to implement another document command which accepts the same optional token arguments and passes them properly to the first command. I've searched this site for the answer and, while there are many questions about passing optional arguments from one command to another, none of them (that I found) address my use case of xparse's token (i.e. t type) optional arguments (e.g., they are for \newcommand rather than \NewDocumentCommand).

Here is a MWE:

\documentclass{article}
\usepackage[normalem]{ulem}
\usepackage{xparse}

\NewDocumentCommand\decorate{t^ t+ t! m}{%
    \IfBooleanTF{#1}{%
        \IfBooleanTF{#2}%
            {\emph{\uline{#4}}}%
            {\IfBooleanTF{#3}{\emph\sout{#4}}}{\emph{#4}}%
    }%
    {\IfBooleanTF{#2}%
        {\uline{#4}}%
        {\IfBooleanTF{#3}{\sout{#4}}{#4}}%
    }%
}

% works but re-implements \decorate
% \NewDocumentCommand\dsection{t^ t+ t! m}{%
%   \IfBooleanTF{#1}{%
%       \IfBooleanTF{#2}%
%           {\section{\emph{\uline{#4}}}}%
%           {\IfBooleanTF{#3}{\section{\emph\sout{#4}}}}{\section{\emph{#4}}}%
%   }%
%   {\IfBooleanTF{#2}%
%       {\section{\uline{#4}}}%
%       {\IfBooleanTF{#3}{\section{\sout{#4}}}{\section{#4}}}%
%   }%
%   % additional commands not related to \decorate
% }

% % works but requires the same if/else branching as in \decorate
% \NewDocumentCommand\dsection{t^ t+ t! m}{%
%   \IfBooleanTF{#1}{%
%       \IfBooleanTF{#2}%
%           {\section{\decorate^+{#4}}}%
%           {\IfBooleanTF{#3}{\section{\decorate^!{#4}}}}{\section{\decorate^{#4}}}%
%   }%
%   {\IfBooleanTF{#2}%
%       {\section{\decorate+{#4}}}%
%       {\IfBooleanTF{#3}{\section{\decorate!{#4}}}{\section{#4}}}%
%   }%
%   % additional commands not related to \decorate
% }

% does not work, but would have the desired property of passing the token arguments to \decorate
\NewDocumentCommand\dsection{t^ t+ t! m}{%
    \section{\decorate \IfBooleanT{#1}{#1}\IfBooleanT{#2}{#2} \IfBooleanT{#3}{#3}{#4}}
    %\section{\decorate#1#2#3{#4}} % also does not work
    %\section{\decorate \IfBooleanT{#1}{^}\IfBooleanT{#2}{+} \IfBooleanT{#3}{!}{#4}} % causes "missing $ inserted" error
    % additional commands not related to \decorate
}

\begin{document}
\dsection^{Caret section}
\decorate^{Caret text}

\dsection+{Plus section}
\decorate+{plus text}

\dsection!{Bang section}
\decorate!{bang text}
\end{document}

I would like to call \decorate within \dsection and properly pass the token arguments to it, but none of my attempts work. In the uncommented attempt above the "decorations" are not applied (i.e. \decorate is effectively called with none of the token arguments).

As shown in one of the commented out definitions of \dsection I can call \decorate in its definition by hard-coding one token argument at a time, but this requires me to re-implement the IfBooleanTF logic in \decorate. How do I implement \dsection using a single call to \decorate while correctly passing the optional token arguments to it?

2 Answers 2

4

user202729 already provided a piece of code where you can see what a solution could look like.

So let's focus on the question why your code doesn't work:

Some of xparse's argument specifiers indicate that pre-processing takes place with the argument.

Thus you need to distinguish the set of tokens that at the time of grabbing it from the token stream forms the argument from the set of tokens that forms the result of pre-processing the argument.

With an argument that is to be pre-processed right after grabbing it from the token-stream #n (#n being either #1 or #2 or #3 or #4 or #5 or #6 or #7 or #8 or #9) of the corresponding \NewDocumentCommand-definition's replacement-text does not refer to the set of tokens which forms that argument but does refer to the set of tokens which forms the result of pre-processing that argument.

The manual of the xparse package says about the t-argument-specifier:

t An optional ⟨token⟩, which will result in a value \BooleanTrue if ⟨token⟩ is present and \BooleanFalse otherwise. Given as t⟨token⟩.

This means that the token forming a t-type-argument is pre-processed after grabbing it from the token stream so that the corresponding #n used in a \NewDocumentCommand-definition's replacement-text (#1 or #2 or #3 or #4 or #5 or #6 or #7 or #8 or #9) either is the token \BooleanTrue or is the token \BooleanFalse.

Thus t-type-arguments can only be passed on as arguments to such other macros which "expect" the corresponding arguments to consist either of the token \BooleanTrue or of the token \BooleanFalse.

This is what user202729 did.


Your not-working attempt is:

\NewDocumentCommand\dsection{t^ t+ t! m}{%
    \section{\decorate \IfBooleanT{#1}{#1}\IfBooleanT{#2}{#2} \IfBooleanT{#3}{#3}{#4}}
    %\section{\decorate#1#2#3{#4}} % also does not work
    %\section{\decorate \IfBooleanT{#1}{^}\IfBooleanT{#2}{+} \IfBooleanT{#3}{!}{#4}} % causes "missing $ inserted" error
    % additional commands not related to \decorate
}

The attempt

\section{\decorate \IfBooleanT{#1}{#1}\IfBooleanT{#2}{#2} \IfBooleanT{#3}{#3}{#4}}

does not work out because neither the token ^ nor the token + nor the token ! is passed to \decorate so that optional arguments for \decorate are not provided and the single token \IfBooleanT following the token \decorate is taken for \decorate's m-type-argument.

The attempt

\section{\decorate#1#2#3{#4}}

does not work out because instead of tokens ^, +, ! a combination of three tokens \BooleanTrue/\BooleanFalse coming from pre-processing \dsection's three optional t-type-arguments and denoted via #1#2#3 is placed behind the token \decorate so that with \decorate TeX does not find tokens ^, +, ! which might be taken for optional arguments but does find one of the tokens \BooleanTrue/\BooleanFalse which is taken for \decorate's non-optional m-type-argument.

The attempt

\section{\decorate \IfBooleanT{#1}{^}\IfBooleanT{#2}{+} \IfBooleanT{#3} {!}{#4}}

does not work out because \decorate is not trailed by tokens ^, +, ! which might be taken for optional arguments but is trailed by the token \IfBooleanT which is taken for \decorate's non-optional m-type-argument.

But this attempt seems very close to what you might need:

Instead of appending the tokens \IfBooleanT{#1}{^}\IfBooleanT{#2}{+}\IfBooleanT{#3}{!} right behind the token \decorate the result of fully expanding all these tokens needs to be appended right behind the token \decorate.

How this can be done using expl3/LaTeX3's e-expansion is shown at the end of this answer.


The gist of all this is:

With an xparse-argument, which according to the corresponding \NewDocumentCommand-argument-specifier is pre-processed, #n (#n being either #1 or #2 or #3 or #4 or #5 or #6 or #7 or #8 or #9) within the\NewDocumentCommand-definition's replacement-text refers to that set of tokens which forms the result of pre-processing that argument.

Information about possible results of pre-processing xparse-arguments can be found in the manual of the package xparse.


If you like it complicated you can use expl3's e-expansion for creating a sequence of ^/+/! and appending that right behind the token \decorate:

\documentclass{article}
\usepackage[normalem]{ulem}
\usepackage{xparse}

\newcommand\foo[1]{#1}% foo is abbreviation for "first of one".

\NewDocumentCommand\decorate{t^ t+ t! m}{%
  \IfBooleanTF{#1}{\emph}{\foo}%
  {%
    \IfBooleanTF{#2}{\uline}{\foo}%
    {%
      \IfBooleanTF{#3}{\sout}{\foo}%
      {#4}%
    }%
  }%
}

\ExplSyntaxOn
\NewDocumentCommand\dsection{t^ t+ t! m}{%
    \section{\exp_last_unbraced:Ne \decorate {\IfBooleanT{#1}{^}\IfBooleanT{#2}{+}\IfBooleanT{#3}{!}} {#4}}%
    % additional commands not related to \decorate and where #1, #2 and #3 each denote one of the tokens
    % \BooleanTrue or \BooleanFalse and #4 denotes the m-type argument.
}
\ExplSyntaxOff

%% With a recent LaTeX release you don't need expl3-syntax but can use \ExpandArgs :
%\newcommand\tot[2]{#1#2}% tot is abbreviation for "two of two".
%\NewDocumentCommand\dsection{t^ t+ t! m}{%
%    \section{\ExpandArgs{ne}\tot{\decorate}{\IfBooleanT{#1}{^}\IfBooleanT{#2}{+}\IfBooleanT{#3}{!}}{#4}}%
%    % additional commands not related to \decorate and where #1, #2 and #3 each denote one of the tokens
%    % \BooleanTrue or \BooleanFalse and #4 denotes the m-type argument.
%}



\begin{document}

\dsection{Section}
\decorate{Text}

\dsection^{Caret section}
\decorate^{Caret text}

\dsection+{Plus section}
\decorate+{plus text}

\dsection!{Bang section}
\decorate!{bang text}

\dsection^+!{Caret Plus Bang section}
\decorate^+!{caret plus bang text}

\end{document}

enter image description here


In case you wonder what \exp_last_unbraced:Ne is about:

\exp_last_unbraced:Ne takes two arguments:

With the first argument pre-processing is done according to the rules denoted by the N-type-argument-specifier.
With the second argument pre-processing is done according to the rules denoted by the e-type-argument-specifier.
The tokens which form the result of pre-processing the first argument (the N-type-argument) are inserted into the token-stream, trailed by the tokens which form the result of pre-processing the second argument (the e-type-argument).
The N-type-specifier denotes that the argument is to consist of a single token which is to be inserted into the token stream without whatsoever further pre-processing.
The e-type-specifier denotes that the argument may consist of several tokens and that \expanded{...} is to be applied to the tokens forming the argument which in turn yields full expansion of the tokens forming the argument.

There are many commands like \exp_args:... or \exp_last_unbraced:... with very similar names where only argument-specifiers differ.

With all these commands with arguments (like N-type) whose pre-processing-result is to consist of a single (control-sequence-)token the single token forming the pre-processing-result is appended to the token-stream without being nested between curly braces {...}.

With arguments (like e-type) whose pre-processing-result may consist of several tokens, the tokens forming the pre-processing-result usually are nested between a pair of matching curly braces {...} at the time of being appended to the token stream.

last_unbraced denotes that despite the convention of usually wrapping pre-processing-results which may consist of several tokens between a pair of matching curly braces {...}, the result of pre-processing the last argument, i.e., the resul of pre-processing the e-type-argument, shall be appended to the token-stream without being nested between curly braces.

4
  • Thanks for the explanation, and I like your clever definition of \decorate in the final code block! I need to do some more research on expl3 to better understand your definitions of \dsection, but I understand better why my attempts failed.
    – Null
    Nov 9, 2022 at 15:09
  • @Null I just added some explanation about \exp_last_unbraced:Ne. ;-) Nov 9, 2022 at 15:52
  • @Null: Ad "research": In case you are interested, have a look at the documentation of the l3kernel, especially The LaTeX3 Programming Language and The LaTeX3 interfaces. Nov 9, 2022 at 16:02
  • Many thanks for the added explanation and links!
    – Null
    Nov 9, 2022 at 16:54
4

"Separate the interface from the internal implementation". As is commonly advised.

\documentclass{article}
\usepackage[normalem]{ulem}
\usepackage{xparse}

\ExplSyntaxOn

\cs_new_protected:Nn \__null_decorate:nnnn {
    \IfBooleanTF{#1}{
        \IfBooleanTF{#2}
            {\emph{\uline{#4}}}
            {\IfBooleanTF{#3}{\emph\sout{#4}}}{\emph{#4}}
    }
    {\IfBooleanTF{#2}
        {\uline{#4}}
        {\IfBooleanTF{#3}{\sout{#4}}{#4}}
    }
}

\NewDocumentCommand\decorate{t^ t+ t! m}{
    \__null_decorate:nnnn {#1}{#2}{#3}{#4}
}

\NewDocumentCommand\dsection{t^ t+ t! m}{%
    \section{ \__null_decorate:nnnn {#1}{#2}{#3}{#4} }
}

\ExplSyntaxOff


\begin{document}
\dsection^{Caret section}
\decorate^{Caret text}

\dsection+{Plus section}
\decorate+{plus text}

\dsection!{Bang section}
\decorate!{bang text}
\end{document}

This solution should be obvious in retrospect. (the __null is named after the OP's username)

1
  • Thanks, this works although I don't entirely understand it. I also don't understand why my code doesn't work, but I'm new to LaTeX3 programming so I need to do some research. Before I asked this question I didn't even know what I needed to research, so this at least shows me I need to better understand \cs_new(_protected), etc.
    – Null
    Nov 8, 2022 at 18:40

You must log in to answer this question.

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