# l3keys and recursive commands

I am trying to create an l3keys list called mykeys which is used to control the behaviour of the command mycommand:n. Initially, it is just the identity. Setting command=\textit should change it into \textit (composed with identity). In general, setting command=\foo should redefine mycommand:n to become #1\foo{mycommand:n{#1}}. However, as it is now, it fails quite miserably. Is it possible to make this work?

\documentclass{article}

\usepackage{expl3}

\ExplSyntaxOn

\cs_new:Npn \identity:n #1 { #1 }

\keys_define:nn { mykeys }
{
command .multichoice:,
command / none .code:n = {
\cs_set_eq:NN \mycommand:n \identity:n %this resets \mycommand:n
%to become the identity
},
command / unknown .code:n = {
\cs_set:Npx \mycommand:n ##1
{
#1 { \mycommand:n { ##1 } }
}
}
}

\keys_set:nn { mykeys } { command=none }

\ExplSyntaxOff

\begin{document}

\ExplSyntaxOn

\mycommand:n{test1} % should produce test1

\keys_set:nn { mykeys } { command=\textit }

\mycommand:n{test2} % should produce \textit{test2}

\keys_set:nn { mykeys } { command=\textbf }

\mycommand:n{test3} % should produce \textbf{\textit{test3}}

\ExplSyntaxOff

\end{document}

• you can't x-expand a latex2e font command as it will blow up and you can't x-expand an expl3 defined command as it's typically \protected, you will want to do the equivalent of \let\savedmycommand\mycommand \def\mycommand##1{{#1{\savedmycommand{##1}} – David Carlisle Jul 10 '17 at 16:16
• I experimented with that a lot, but was unable to make it work. Eventually I decided to make my MWE as minimal as possible and see what people would suggest. – Gaussler Jul 10 '17 at 16:17
• @DavidCarlisle Even writing what you just wrote (that is, plain TeX commands inside expl3) does not work, unfortunately. – Gaussler Jul 10 '17 at 16:52
• Oh no it wouldn't work as is for a start you'd have to generate a new name each time to save the previous state, but I may post a version using expansion later if egreg not done it by then (I was driving home, I thought he'd have done it by now:-) – David Carlisle Jul 10 '17 at 17:30
• I'm obviously missing something: why not command .code:n = \cs_set_eq:NN \mycommand:n #1? One could extend that to have a single-token test, etc. (though my experience is that you want to avoid 'magic value' keys). – Joseph Wright Jul 10 '17 at 21:32

I'm not particularly keen on the interface, but here I'd store the commands for application in sequence

\documentclass{article}
\usepackage{expl3,xparse}
\ExplSyntaxOn

\seq_new:N \l__mycommand_cmds_seq
\tl_new:N \l__mycommand_tmp_tl

\keys_define:nn { mykeys }
{
command .code:n =
{
\str_if_eq:nnTF {#1} { none }
{ \seq_clear:N \l__mycommand_cmds_seq }
{ \seq_put_right:Nn \l__mycommand_cmds_seq {#1} }
}
}
\keys_set:nn { mykeys } { command = none }

\cs_new_protected:Npn \mycommand_main:n #1
{
\group_begin:
\tl_set:Nn \l__mycommand_tmp_tl {#1}
\seq_map_inline:Nn \l__mycommand_cmds_seq
{
\tl_set:Nx \l__mycommand_tmp_tl
{ \exp_not:n {##1} { \exp_not:V \l__mycommand_tmp_tl } }
}
\exp_args:NV \group_end:
\l__mycommand_tmp_tl
}

\NewDocumentCommand \mysetup { +m } { \keys_set:nn { mykeys } {#1} }
\NewDocumentCommand \mycommand { m }
{ \mycommand_main:n {#1} }
\ExplSyntaxOff

\begin{document}

\mycommand{test1} % should produce test1

\mysetup{command = \textit}

\mycommand{test2} % should produce \textit{test2}

\mysetup{command = \textbf}

\mycommand{test3} % should produce \textbf{\textit{test3}}

\end{document}


One might do a single-token/macro test as part of the keyval setting, but that is likely overkill.

One might build up the 'functional' token list during the keyval setting, but I suspect that having the sequence available for inspection might be useful in debugging.

• Note that multichoice is intended for the case foo = {bar,baz} not really foo = bar, foo = baz, i.e. conceptually each time you set a key the expectation is that the new value entire 'overwrites' the older one. (I have considered though not yet implemented storing all of the 'raw' key values so one can do lookup, etc.). – Joseph Wright Jul 11 '17 at 8:25
• So I might as well use choice? It is meant for a general purpose interface where the command key may be used to add accents or change the font of a symbol. – Gaussler Jul 11 '17 at 8:33

so long as the commands involved expose their implementation after a single expansion, you can do something like the following

\documentclass{article}

\usepackage{expl3}

\ExplSyntaxOn

\cs_new:Npn \identity:n #1 { #1 }

\cs_new:Npn\wrap_def:NN#1#2{
\exp_after:wN\cs_set:Npn
\exp_after:wN#2\exp_after:wN##\exp_after:wN1\exp_after:wN{
\exp_after:wN#1\exp_after:wN{#2{##1}}}
}

\keys_define:nn { mykeys }
{
command .multichoice:,
command / none .code:n = {
\cs_set_eq:NN \mycommand:n \identity:n %this resets \mycommand:n
%to become the identity
},
command / unknown .code:n = {
\wrap_def:NN #1 \mycommand:n
}
}

\keys_set:nn { mykeys } { command=none }

\ExplSyntaxOff

\begin{document}

\ExplSyntaxOn

\mycommand:n{test1} % should produce test1

\keys_set:nn { mykeys } { command=\textit }

\mycommand:n{test2} % should produce \textit{test2}

\keys_set:nn { mykeys } { command=\textbf }

\mycommand:n{test3} % should produce \textbf{\textit{test3}}

\ExplSyntaxOff

\end{document}

• This is closest I think to the original implementation suggestion, but I a different strategy, more robust for commands defined via xparse or latex2e \protect etc where a first level expansion isn't so useful, would be just to construct a seq of all the commands specified so far and then generate the nested call on the fly when needed by mapping over the sequence. – David Carlisle Jul 10 '17 at 18:40

Implementing David's suggestion:

\documentclass{article}

\usepackage{expl3}

\ExplSyntaxOn

\tl_new:N \l_gaussler_command_start_tl
\tl_new:N \l_gaussler_command_end_tl

\keys_define:nn { mykeys }
{
command .multichoice:,
command / none .code:n =
{
\tl_clear:N \l_gaussler_command_start_tl
\tl_clear:N \l_gaussler_command_end_tl
\cs_set_protected:Nn \gaussler_thecommand:n { ##1 }
},
command / unknown .code:n =
{
\tl_put_left:Nn \l_gaussler_command_start_tl { \exp_not:N #1 \gaussler_command_open: }
\tl_put_right:Nn \l_gaussler_command_end_tl { \gaussler_command_close: }
\cs_set_protected:Nx \gaussler_thecommand:n
{
\tl_use:N \l_gaussler_command_start_tl
##1
\tl_use:N \l_gaussler_command_end_tl
}
}
}
\cs_new:Nn \gaussler_command_open: { \if_true: { \else: } \fi: }
\cs_new:Nn \gaussler_command_close: { \if_false: { \else: } \fi: }

\keys_set:nn { mykeys } { command=none }

\ExplSyntaxOff

\begin{document}

\ExplSyntaxOn

\gaussler_thecommand:n{test1} % should produce test1

\keys_set:nn { mykeys } { command=\textit }

\gaussler_thecommand:n{test2} % should produce \textit{test2}

\keys_set:nn { mykeys } { command=\textbf }

\gaussler_thecommand:n{test3} % should produce \textbf{\textit{test3}}

\keys_set:nn { mykeys } { command=none }

\gaussler_thecommand:n{test4}

\ExplSyntaxOff

\end{document}


• Nice trick with the conditionals! :-) I was experimenting with using \bgroup and \egroup, which obviously collapsed everything. – Gaussler Jul 11 '17 at 6:28
• @Gaussler \bgroup and \egroup cannot be used for delimiting arguments. – egreg Jul 11 '17 at 6:47