6

Suppose given two comma lists of the same length:

\clist_set:Nn \l__mymodule_keys_clist { key1, key2, key3 }
\clist_set:Nn \l__mymodule_acts_clist { act1, act2, act3 }

How can one convert them into a test function of the following form?

\NewDocumentCommand { \TestFunction } { m }
  {
    \str_case:nnF { #1 }
      {
        { key1 } { act1 }
        { key2 } { act2 }
        { key3 } { act3 }
      } { No ~ action! }
  }

Below is a MWE.

\documentclass{article}
\begin{document}
\ExplSyntaxOn

\clist_new:N \l__mymodule_keys_clist
\clist_new:N \l__mymodule_acts_clist

\clist_set:Nn \l__mymodule_keys_clist { key1, key2, key3 }
\clist_set:Nn \l__mymodule_acts_clist { act1, act2, act3 }

\NewDocumentCommand { \TestFunction } { m }
  {
    \str_case:nnF { #1 }
      {
        { key1 } { act1 }
        { key2 } { act2 }
        { key3 } { act3 }
      } { No ~ action! }
  }

\ExplSyntaxOff

\TestFunction{key2}

\TestFunction{key4}

\end{document}

The expected result would be

enter image description here

The MWE produces this result but the content of the \TestFunction is hard-coded and is not dynamically created from the two given comma lists.


Below is my current attempt:

\NewDocumentCommand { \TestFunction } { m }
  {
    \clist_set_eq:NN \l_tmpa_clist \l__mymodule_acts_clist
    \tl_clear:N \l_tmpa_tl
    \clist_map_inline:Nn \l__mymodule_keys_clist
      {
        \tl_put_right:Nn \l_tmpa_tl { { ##1 } }
        \clist_pop:NN \l_tmpa_clist \l_tmpb_tl
        \exp_args:NNe \tl_put_right:Nn \l_tmpa_tl { { \l_tmpb_tl } }
      }
    \exp_args:Nno \str_case:nnF { #1 } { \l_tmpa_tl } { No ~ action! }
  }

Though it works in this particular case, the problem is that it uses the e-type expansion which would require the content of the second comma list to be fully expandable, which is not the case if it contains things like \textbf. The thing is that when popping the first item from the second clist, it seems that I can only save it to a macro, say \l_tmpb_tl; then later on, when I wish to append { \l_tmpb_tl } to a temporary macro (here \l_tmpa_tl), the o-type expansion that expand it once does not seem to be enough. Is there some way to improve this code or is there an alternative and better approach?

3
  • Please tell us what the results of \TestFunction{key2} and \TestFunction{key4} should be.
    – Mico
    Jul 20, 2023 at 4:53
  • @Mico Thank you for the comment. I have added the expected result along with some explanation.
    – Jinwen
    Jul 20, 2023 at 5:34
  • What would be the purpose of all this?
    – Gaussler
    Jul 24, 2023 at 9:28

4 Answers 4

4

You seem to want to turn the two comma lists into a property list (I'm rather keen to believe this is an XY-question).

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\makepropfromclists}{O{default}mm}
 {% #1 = name of the prop, #2 = name of the keys clist, #3 = name of the actions clist
  \prop_clear_new:c { l_mymodule_keysacts_#1_prop }
  \int_step_inline:nn { \clist_count:c { l__mymodule_#2_clist } }
   {
    \prop_put:cxx { l_mymodule_keysacts_#1_prop }
     { \clist_item:cn { l__mymodule_#2_clist } { ##1 } }
     { \clist_item:cn { l__mymodule_#3_clist } { ##1 } }
   }
 }

\NewDocumentCommand{\TestFunction}{O{default}m}
 {
  \prop_if_in:cnTF { l_mymodule_keysacts_#1_prop } { #2 }
   {
    \prop_get:cnN { l_mymodule_keysacts_#1_prop } { #2 } \l_tmpa_tl
    \tl_use:N \l_tmpa_tl
   }
   { No~action! }
 }

\clist_new:N \l__mymodule_keys_clist
\clist_new:N \l__mymodule_acts_clist
\clist_set:Nn \l__mymodule_keys_clist { key1, key2, key3 }
\clist_set:Nn \l__mymodule_acts_clist { act1, act2, act3 }

\makepropfromclists{keys}{acts}

\ExplSyntaxOff

\begin{document}

\TestFunction{key2}

\TestFunction{key4}

\end{document}

enter image description here

Don't use clists for what they aren't thought for.

3
  • Actually, inspired by your answer here, I was thinking of adding German support for my package crefthe. Currently I stored, in order, the name of declensions, definite articles and names in singular and plural forms into different clists (for now I am not really sure what would be the difference with using seq), and were expected to use \str_case to show the corresponding string for the given declension type. That was the motivation for this question.
    – Jinwen
    Jul 20, 2023 at 8:34
  • By the way, please allow me to ask a follow up question. I noticed that you used \prop_put:cxx with x-type expansion, but it seems OK for those to clists to contain things like \textbf. However, in my attempt given at the end of my question, even if I write \exp_args:NNx, the appearance of \textbf still causes errors. What is going on here?
    – Jinwen
    Jul 20, 2023 at 8:42
  • @Jinwen There's no problem, because \clist_item:Nn (or variants thereof) returns the item protected by \exp_not:n.
    – egreg
    Jul 20, 2023 at 11:09
5

You can implement your task directly using TeX primitives, no expl3 is needed:

\def\keys {key1,key2,key3}
\def\acts {act1,act2,act3}

\def\actsX #1,#2\end#3{\expandafter\def\csname k:#3\endcsname{#1}\def\acts{#2}} 
\def\setkeys #1,{\ifx\end#1\else
   \expandafter\actsX \acts,\end {#1}%
   \expandafter\setkeys \fi
}
\expandafter\setkeys\keys,\end,

\def\TestFunction#1{%
   \ifcsname k:#1\endcsname 
      \csname k:#1\endcsname
   \else 
      No action%
   \fi
}

\TestFunction{key2}  % prints: act2

\TestFunction{key4}  % prints: No action

The \setkeys macro with the parameter key1,key2,key3,\end, defines macros \k:key1 with value act1, \k:key2 with value act2 etc. If there is more keys than actions then the macros with trailing keys are empty. Finally, the macro \TestFunction prints the value of given key or prints No action if the macro isn't defined.

3

Using \seq_map_pairwise_function:NNN gets us most of the way there:

\documentclass{article}

\ExplSyntaxOn
% Starting clists
\clist_set:Nn \l__mymodule_keys_clist { key1, key2, key3 }
\clist_set:Nn \l__mymodule_acts_clist { act1, act2, act3 }

% Convert to sequences
\seq_set_from_clist:NN \l__mymodule_keys_seq \l__mymodule_keys_clist
\seq_set_from_clist:NN \l__mymodule_acts_seq \l__mymodule_acts_clist

% The value that we're searching for
\tl_new:N \l__mymodule_search_tl

% The mapping function
\cs_new_protected:Nn \__mymodule_map:nn {
    \tl_if_eq:NnT \l__mymodule_search_tl { #1 } {
        % \seq_map_break:n { #2 \use_none:n } % Doesn't work for some reason
        \prg_break:n { #2 \use_none:n }
    }
}

% The fallthrough action
\tl_const:Nn \c__mymodule_fallthrough_tl { No ~ action! }

% The user-accessible function
\NewDocumentCommand { \TestFunction } { m } {
    \tl_set:Nn \l__mymodule_search_tl { #1 }
    \seq_map_pairwise_function:NNN
        \l__mymodule_keys_seq
        \l__mymodule_acts_seq
        \__mymodule_map:nn
    \c__mymodule_fallthrough_tl
}
\ExplSyntaxOff

\begin{document}
    \TestFunction{key2}

    \TestFunction{key4}
\end{document}

This is much cleaner if you're able to use a single property list/key–val expression instead of a pair of clists:

\documentclass{article}

\ExplSyntaxOn
% Starting property list
\prop_set_from_keyval:Nn \l__mymodule_keys_prop {
    key1 = act1,
    key2 = act2,
    key3 = act3
}

% The user-accessible function
\NewDocumentCommand { \TestFunction } { m } {
    \prop_get:NnNTF \l__mymodule_keys_prop { #1 } \l_tmpa_tl {
        \tl_use:N \l_tmpa_tl
    } {
        No ~ action!
    }
}
\ExplSyntaxOff

\begin{document}
    \TestFunction{key2}

    \TestFunction{key4}
\end{document}

The output is identical with both methods:

enter image description here

2

Although expl3's mapping-functions seem not to be designed for doing 'in place'-manipulation on lists of tokens, in the following I assume that the LaTeX-distribution in use is recent enough for \clist_map_function:NN to be available. (interface3.pdf of my TeX-distribution says about \clist_map_function:NN: Updated: 2012-06-29. This is about 11 years ago, so assuming availability of \clist_map_function:NN might be reasonable.)


The gist of all following code is applying \clist_map_function:NN for mapping comma-list-items to a function/macro which uses an argument-delimiter for placing items nested in braces behind that delimiter. Before the delimiter you find the comma-list item in braces and the tokens forming the next iteration of the mapping-function.

So after each mapping-iteration you have s. th. like

\function{⟨item in this mapping-iteration⟩}⟨stuff for subsequent mapping-iterations⟩\DELIMITER{⟨item from last mapping-iteration⟩}{⟨item from last but one mapping-iteration⟩}..{⟨item from first mapping-iteration⟩}.

And \function uses arguments #1 and #2\DELIMITER and does:

⟨stuff for subsequent mapping-iterations⟩\DELIMITER{⟨item in this mapping-iteration⟩}{⟨item from last mapping-iteration⟩}{⟨item from last but one mapping-iteration⟩}..{⟨item from first mapping-iteration⟩}.

So by and by from items,of,list,a and ITEMS,OF,LIST,B, s.th. like
\DELIMITER{⟨result gathered so far, initially empty⟩}{B}{LIST}{OF}{ITEMS}{},{a}{list}{of}{items}{},
is formed.

This in turn can be merged by using a loop where \DELIMITER is defined with an argument-pattern #1#2#3,#4#5,\DELIMITER{{#2}{#4}#1}#3,#5, and which terminates when {#3#5} is an empty argument so that {#2}{#4}#1 forms the result of merging.


Caveat 1:

⟨stuff for subsequent mapping-iterations⟩ must not contain \DELIMITER unless nested in curly braces.
I didn't bother checking the implementation of \clist_map_function:NN for finding out if the case of ⟨stuff for subsequent mapping-iterations⟩ containing \DELIMITER outside a pair of curly braces is possible.

Caveat 2:

Applying the function which is mapped to the single comma-list-items leads to accumulating things in the input stack. Therefore with (not that) large comma-lists you might get error-messages like
! TeX capacity exceeded, sorry [input stack size=...].


In the code below \mymodule_mergecommalists:nnwnw is used in place of \DELIMITER and \mymodule_moveargbehindmergecommalistsbraced:nw is used in place of \function/is mapped to the comma-list's items.


If you wish the content of \l__mymodule_keys_clist and \l__mymodule_acts_clist to be hardcoded into the definition of \TestFunction so that the behavior of \TestFunction is not affected by subsequent changes to \l__mymodule_keys_clist and/or \l__mymodule_acts_clist, you can probably do s. th. like this:

\documentclass{article}

\ExplSyntaxOn
% \DefineTestFunction{<control word token denoting test function to define>}%
%                    {<additional xparse-argument-specifiers, denoting #2, #3 etc>
%                      % #1 is available in <action if #1 is not a key of the keys comma list> 
%                      % in any case and denotes the argument to test
%                    }%
%                    {<keys comma list>}%
%                    {<actions comma list>}%
%                    {<action if #1 is not a key of the keys comma list>}%
\cs_new:Npn \mymodule_DefineTestFunction:NnNNn #1#2#3#4#5 { 
  \exp_args:Nno \use:n
  { \NewDocumentCommand { #1 } { m#2 } } {
    \exp:w \exp_args:Nno \use:n {\exp_end: \str_case:nnF {##1} }
    {%
      \exp:w
      \mymodule_moveargbehindmergecommalistsunbraced:nw{{},}
      \clist_map_function:NN #4 \mymodule_moveargbehindmergecommalistsbraced:nw
      \mymodule_moveargbehindmergecommalistsunbraced:nw{{},}
      \clist_map_function:NN #3 \mymodule_moveargbehindmergecommalistsbraced:nw
      \mymodule_moveargbehindmergecommalistsbraced:nw{}
      \mymodule_mergecommalists:nnwnw
    }{#5}
  }
}
\cs_new:Npn \mymodule_moveargbehindmergecommalistsunbraced:nw #1#2\mymodule_mergecommalists:nnwnw 
  {#2 \mymodule_mergecommalists:nnwnw #1}
\cs_new:Npn \mymodule_moveargbehindmergecommalistsbraced:nw #1#2\mymodule_mergecommalists:nnwnw 
  {#2 \mymodule_mergecommalists:nnwnw {#1}}
\cs_new:Npn \mymodule_mergecommalists:nnwnw #1#2#3,#4#5,
  {\tl_if_empty:nTF{#3#5}{\exp_end:{#2}{#4}#1}{\mymodule_mergecommalists:nnwnw {{#2}{#4}#1}#3,#5,}}
%-----------------------------------------------------------------------------------

\clist_set:Nn \l__mymodule_keys_clist { key1, key2, key3 }
\clist_set:Nn \l__mymodule_acts_clist { act1, act2, act3 }

\mymodule_DefineTestFunction:NnNNn{\TestFunction}{}
                                  {\l__mymodule_keys_clist}
                                  {\l__mymodule_acts_clist}
                                  {The~key~#1~is~not~in~the~list,~thus~default~action!}

\mymodule_DefineTestFunction:NnNNn{\OtherTestFunction}{m}
                                  {\l__mymodule_keys_clist}
                                  {\l__mymodule_acts_clist}
                                  {The~key~#1~is~not~in~the~list,~thus:~#2}

\cs_show:c{TestFunction~code}
\cs_show:c{OtherTestFunction~code}

\ExplSyntaxOff

\begin{document}

\TestFunction{key1}

\TestFunction{key2}

\TestFunction{key3}

\TestFunction{key4}

\OtherTestFunction{key1}{This time's default}

\OtherTestFunction{key2}{This time's default}

\OtherTestFunction{key3}{This time's default}

\OtherTestFunction{key4}{This time's default}

\end{document}

enter image description here


If you wish the lists \l__mymodule_keys_clist and \l__mymodule_acts_clist to be evaluated at the time when \TestFunction is carried out, you can probably do s. th. like this:

\documentclass{article}

\ExplSyntaxOn
% \DefineTestFunction{<control word token denoting test function to define>}%
%                    {<additional xparse-argument-specifiers, denoting #2, #3 etc>
%                      % #1 is available in <action if #1 is not a key of the keys comma list> 
%                      % in any case and denotes the argument to test
%                    }%
%                    {<keys comma list>}%
%                    {<actions comma list>}%
%                    {<action if #1 is not a key of the keys comma list>}%
\cs_new:Npn \mymodule_DefineTestFunction:NnNNn #1#2#3#4#5 { 
  \NewDocumentCommand { #1 } { m#2 } {
    \exp_args:Nno \str_case:nnF {##1} 
    {%
      \exp:w
      \mymodule_moveargbehindmergecommalistsunbraced:nw{{},}
      \clist_map_function:NN #4 \mymodule_moveargbehindmergecommalistsbraced:nw
      \mymodule_moveargbehindmergecommalistsunbraced:nw{{},}
      \clist_map_function:NN #3 \mymodule_moveargbehindmergecommalistsbraced:nw
      \mymodule_moveargbehindmergecommalistsbraced:nw{}
      \mymodule_mergecommalists:nnwnw
    }{#5}
  }
}
\cs_new:Npn \mymodule_moveargbehindmergecommalistsunbraced:nw #1#2\mymodule_mergecommalists:nnwnw 
  {#2 \mymodule_mergecommalists:nnwnw #1}
\cs_new:Npn \mymodule_moveargbehindmergecommalistsbraced:nw #1#2\mymodule_mergecommalists:nnwnw 
  {#2 \mymodule_mergecommalists:nnwnw {#1}}
\cs_new:Npn \mymodule_mergecommalists:nnwnw #1#2#3,#4#5,
  {\tl_if_empty:nTF{#3#5}{\exp_end:{#2}{#4}#1}{\mymodule_mergecommalists:nnwnw {{#2}{#4}#1}#3,#5,}}
%-----------------------------------------------------------------------------------

\mymodule_DefineTestFunction:NnNNn{\TestFunction}{}
                                  {\l__mymodule_keys_clist}
                                  {\l__mymodule_acts_clist}
                                  {The~key~#1~is~not~in~the~list,~thus~default~action!}

\mymodule_DefineTestFunction:NnNNn{\OtherTestFunction}{m}
                                  {\l__mymodule_keys_clist}
                                  {\l__mymodule_acts_clist}
                                  {The~key~#1~is~not~in~the~list,~thus:~#2}

\clist_set:Nn \l__mymodule_keys_clist { key1, key2, key3 }
\clist_set:Nn \l__mymodule_acts_clist { act1, act2, act3 }

\cs_show:c{TestFunction~code}
\cs_show:c{OtherTestFunction~code}

\ExplSyntaxOff

\begin{document}

\TestFunction{key1}

\TestFunction{key2}

\TestFunction{key3}

\TestFunction{key4}

\OtherTestFunction{key1}{This time's default}

\OtherTestFunction{key2}{This time's default}

\OtherTestFunction{key3}{This time's default}

\OtherTestFunction{key4}{This time's default}

\end{document}

enter image description here


If you wish to specify at the time when \TestFunction is carried out, which comma-list-variables to evaluate, you can probably do s. th. like this:

\documentclass{article}

\ExplSyntaxOn
% \DefineTestFunction{<control word token denoting test function to define>}%
%                    {<additional xparse-argument-specifiers, denoting #4, #5 etc>
%                      % #1 is available in <action if #1 is not a key of the keys comma list> 
%                      % in any case and denotes the argument to test.
%                      % #2 is available in <action if #1 is not a key of the keys comma list> 
%                      % in any case and denotes the <keys comma list>.
%                      % #3 is available in <action if #1 is not a key of the keys comma list> 
%                      % in any case and denotes the <actions comma list>.
%                    }%
%                    {<action if #1 is not a key of the keys comma list>}%
\cs_new:Npn \mymodule_DefineTestFunction:NnNNn #1#2#3 { 
  \NewDocumentCommand { #1 } { mmm#2 } {
    \exp_args:Nno \str_case:nnF {##1} 
    {%
      \exp:w
      \mymodule_moveargbehindmergecommalistsunbraced:nw{{},}
      \clist_map_function:NN ##3 \mymodule_moveargbehindmergecommalistsbraced:nw
      \mymodule_moveargbehindmergecommalistsunbraced:nw{{},}
      \clist_map_function:NN ##2 \mymodule_moveargbehindmergecommalistsbraced:nw
      \mymodule_moveargbehindmergecommalistsbraced:nw{}
      \mymodule_mergecommalists:nnwnw
    }{#3}
  }
}
\cs_new:Npn \mymodule_moveargbehindmergecommalistsunbraced:nw #1#2\mymodule_mergecommalists:nnwnw 
  {#2 \mymodule_mergecommalists:nnwnw #1}
\cs_new:Npn \mymodule_moveargbehindmergecommalistsbraced:nw #1#2\mymodule_mergecommalists:nnwnw 
  {#2 \mymodule_mergecommalists:nnwnw {#1}}
\cs_new:Npn \mymodule_mergecommalists:nnwnw #1#2#3,#4#5,
  {\tl_if_empty:nTF{#3#5}{\exp_end:{#2}{#4}#1}{\mymodule_mergecommalists:nnwnw {{#2}{#4}#1}#3,#5,}}
%-----------------------------------------------------------------------------------

\mymodule_DefineTestFunction:NnNNn{\TestFunction}{}
                                  {The~key~#1~is~not~in~the~list,~thus~default~action!}

\mymodule_DefineTestFunction:NnNNn{\OtherTestFunction}{m}
                                  {The~key~#1~is~not~in~the~list,~thus:~#4}

\clist_set:Nn \l__mymodule_keys_clist { key1, key2, key3 }
\clist_set:Nn \l__mymodule_acts_clist { act1, act2, act3 }

\cs_show:c{TestFunction~code}
\cs_show:c{OtherTestFunction~code}

\ExplSyntaxOff

\begin{document}

\ExplSyntaxOn

\TestFunction{key1}{\l__mymodule_keys_clist}{\l__mymodule_acts_clist}\par

\TestFunction{key2}{\l__mymodule_keys_clist}{\l__mymodule_acts_clist}\par

\TestFunction{key3}{\l__mymodule_keys_clist}{\l__mymodule_acts_clist}\par

\TestFunction{key4}{\l__mymodule_keys_clist}{\l__mymodule_acts_clist}\par

\OtherTestFunction{key1}{\l__mymodule_keys_clist}{\l__mymodule_acts_clist}{This~time's~default}\par

\OtherTestFunction{key2}{\l__mymodule_keys_clist}{\l__mymodule_acts_clist}{This~time's~default}\par

\OtherTestFunction{key3}{\l__mymodule_keys_clist}{\l__mymodule_acts_clist}{This~time's~default}\par

\OtherTestFunction{key4}{\l__mymodule_keys_clist}{\l__mymodule_acts_clist}{This~time's~default}

\end{document}

enter image description here

1

You must log in to answer this question.

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