summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/l3kernel/l3clist.dtx
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/latex/contrib/l3kernel/l3clist.dtx
Initial commit
Diffstat (limited to 'macros/latex/contrib/l3kernel/l3clist.dtx')
-rw-r--r--macros/latex/contrib/l3kernel/l3clist.dtx2031
1 files changed, 2031 insertions, 0 deletions
diff --git a/macros/latex/contrib/l3kernel/l3clist.dtx b/macros/latex/contrib/l3kernel/l3clist.dtx
new file mode 100644
index 0000000000..fa743396b7
--- /dev/null
+++ b/macros/latex/contrib/l3kernel/l3clist.dtx
@@ -0,0 +1,2031 @@
+% \iffalse meta-comment
+%
+%% File: l3clist.dtx
+%
+% Copyright (C) 2004-2011 Frank Mittelbach, The LaTeX3 Project
+% (C) 2012-2019 The LaTeX3 Project
+%
+% It may be distributed and/or modified under the conditions of the
+% LaTeX Project Public License (LPPL), either version 1.3c of this
+% license or (at your option) any later version. The latest version
+% of this license is in the file
+%
+% https://www.latex-project.org/lppl.txt
+%
+% This file is part of the "l3kernel bundle" (The Work in LPPL)
+% and all files in that bundle must be distributed together.
+%
+% -----------------------------------------------------------------------
+%
+% The development version of the bundle can be found at
+%
+% https://github.com/latex3/latex3
+%
+% for those people who are interested.
+%
+%<*driver>
+\documentclass[full,kernel]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3clist} package\\ Comma separated lists^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% Comma lists contain ordered data where items can be added to the left
+% or right end of the list. This data type allows basic list
+% manipulations such as adding/removing items, applying a function to
+% every item, removing duplicate items, extracting a given item, using
+% the comma list with specified separators, and so on. Sequences
+% (defined in \pkg{l3seq}) are safer, faster, and provide more features,
+% so they should often be preferred to comma lists. Comma lists are
+% mostly useful when interfacing with \LaTeXe{} or other code that
+% expects or provides comma list data.
+%
+% Several items can be added at once. To ease input of comma lists from
+% data provided by a user outside an \cs{ExplSyntaxOn} \ldots{}
+% \cs{ExplSyntaxOff} block, spaces are removed from both sides of each
+% comma-delimited argument upon input. Blank arguments are ignored, to
+% allow for trailing commas or repeated commas (which may otherwise
+% arise when concatenating comma lists \enquote{by hand}). In addition,
+% a set of braces is removed if the result of space-trimming is braced:
+% this allows the storage of any item in a comma list. For instance,
+% \begin{verbatim}
+% \clist_new:N \l_my_clist
+% \clist_put_left:Nn \l_my_clist { ~a~ , ~{b}~ , c~\d }
+% \clist_put_right:Nn \l_my_clist { ~{e~} , , {{f}} , }
+% \end{verbatim}
+% results in |\l_my_clist| containing |a,b,c~\d,{e~},{{f}}| namely the
+% five items |a|, |b|, |c~\d|, |e~| and~|{f}|. Comma lists normally do
+% not contain empty items so the following gives an empty comma list:
+% \begin{verbatim}
+% \clist_clear_new:N \l_my_clist
+% \clist_put_right:Nn \l_my_clist { , ~ , , }
+% \clist_if_empty:NTF \l_my_clist { true } { false }
+% \end{verbatim}
+% and it leaves \texttt{true} in the input stream. To include an
+% \enquote{unsafe} item (empty, or one that contains a comma, or starts
+% or ends with a space, or is a single brace group), surround it with
+% braces.
+%
+% Almost all operations on comma lists are
+% noticeably slower than those on sequences so converting the data to
+% sequences using \cs{seq_set_from_clist:Nn} (see \pkg{l3seq}) may be
+% advisable if speed is important. The exception is that
+% \cs{clist_if_in:NnTF} and \cs{clist_remove_duplicates:N} may be faster
+% than their sequence analogues for large lists. However, these
+% functions work slowly for \enquote{unsafe} items that must be braced,
+% and may produce errors when their argument contains |{|, |}| or |#|
+% (assuming the usual \TeX{} category codes apply). In addition, comma
+% lists cannot store quarks \cs{q_mark} or \cs{q_stop}. The sequence
+% data type should thus certainly be preferred to comma lists to store
+% such items.
+%
+% \section{Creating and initialising comma lists}
+%
+% \begin{function}{\clist_new:N, \clist_new:c}
+% \begin{syntax}
+% \cs{clist_new:N} \meta{comma list}
+% \end{syntax}
+% Creates a new \meta{comma list} or raises an error if the name is
+% already taken. The declaration is global. The \meta{comma list}
+% initially contains no items.
+% \end{function}
+%
+% \begin{function}[added = 2014-07-05]
+% {
+% \clist_const:Nn, \clist_const:Nx,
+% \clist_const:cn, \clist_const:cx
+% }
+% \begin{syntax}
+% \cs{clist_const:Nn} \meta{clist~var} \Arg{comma list}
+% \end{syntax}
+% Creates a new constant \meta{clist~var} or raises an error
+% if the name is already taken. The value of the
+% \meta{clist~var} is set globally to the
+% \meta{comma list}.
+% \end{function}
+%
+% \begin{function}
+% {\clist_clear:N, \clist_clear:c, \clist_gclear:N, \clist_gclear:c}
+% \begin{syntax}
+% \cs{clist_clear:N} \meta{comma list}
+% \end{syntax}
+% Clears all items from the \meta{comma list}.
+% \end{function}
+%
+% \begin{function}
+% {
+% \clist_clear_new:N, \clist_clear_new:c,
+% \clist_gclear_new:N, \clist_gclear_new:c
+% }
+% \begin{syntax}
+% \cs{clist_clear_new:N} \meta{comma list}
+% \end{syntax}
+% Ensures that the \meta{comma list} exists globally by applying
+% \cs{clist_new:N} if necessary, then applies
+% \cs[index=clist_clear:N]{clist_(g)clear:N} to leave
+% the list empty.
+% \end{function}
+%
+% \begin{function}
+% {
+% \clist_set_eq:NN, \clist_set_eq:cN,
+% \clist_set_eq:Nc, \clist_set_eq:cc,
+% \clist_gset_eq:NN, \clist_gset_eq:cN,
+% \clist_gset_eq:Nc, \clist_gset_eq:cc
+% }
+% \begin{syntax}
+% \cs{clist_set_eq:NN} \meta{comma list_1} \meta{comma list_2}
+% \end{syntax}
+% Sets the content of \meta{comma list_1} equal to that of
+% \meta{comma list_2}.
+% \end{function}
+%
+% \begin{function}[added = 2014-07-17]
+% {
+% \clist_set_from_seq:NN, \clist_set_from_seq:cN,
+% \clist_set_from_seq:Nc, \clist_set_from_seq:cc,
+% \clist_gset_from_seq:NN, \clist_gset_from_seq:cN,
+% \clist_gset_from_seq:Nc, \clist_gset_from_seq:cc
+% }
+% \begin{syntax}
+% \cs{clist_set_from_seq:NN} \meta{comma list} \meta{sequence}
+% \end{syntax}
+% Converts the data in the \meta{sequence} into a \meta{comma list}:
+% the original \meta{sequence} is unchanged.
+% Items which contain either spaces or commas are surrounded by braces.
+% \end{function}
+%
+% \begin{function}
+% {
+% \clist_concat:NNN, \clist_concat:ccc,
+% \clist_gconcat:NNN, \clist_gconcat:ccc
+% }
+% \begin{syntax}
+% \cs{clist_concat:NNN} \meta{comma list_1} \meta{comma list_2} \meta{comma list_3}
+% \end{syntax}
+% Concatenates the content of \meta{comma list_2} and \meta{comma list_3}
+% together and saves the result in \meta{comma list_1}. The items in
+% \meta{comma list_2} are placed at the left side of the new comma list.
+% \end{function}
+%
+% \begin{function}[EXP, pTF, added=2012-03-03]
+% {\clist_if_exist:N, \clist_if_exist:c}
+% \begin{syntax}
+% \cs{clist_if_exist_p:N} \meta{comma list}
+% \cs{clist_if_exist:NTF} \meta{comma list} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% Tests whether the \meta{comma list} is currently defined. This does
+% not check that the \meta{comma list} really is a comma list.
+% \end{function}
+%
+% \section{Adding data to comma lists}
+%
+% \begin{function}[added = 2011-09-06]
+% {
+% \clist_set:Nn, \clist_set:NV,
+% \clist_set:No, \clist_set:Nx,
+% \clist_set:cn, \clist_set:cV,
+% \clist_set:co, \clist_set:cx,
+% \clist_gset:Nn, \clist_gset:NV,
+% \clist_gset:No, \clist_gset:Nx,
+% \clist_gset:cn, \clist_gset:cV,
+% \clist_gset:co, \clist_gset:cx
+% }
+% \begin{syntax}
+% \cs{clist_set:Nn} \meta{comma list} |{|\meta{item_1},\ldots{},\meta{item_n}|}|
+% \end{syntax}
+% Sets \meta{comma list} to contain the \meta{items},
+% removing any previous content from the variable.
+% Blank items are omitted, spaces are removed from both sides of each
+% item, then a set of braces is removed if the resulting space-trimmed
+% item is braced.
+% To store some \meta{tokens} as a single \meta{item} even if the
+% \meta{tokens} contain commas or spaces, add a set of braces:
+% \cs{clist_set:Nn} \meta{comma list} |{| \Arg{tokens} |}|.
+% \end{function}
+%
+% \begin{function}[updated = 2011-09-05]
+% {
+% \clist_put_left:Nn, \clist_put_left:NV,
+% \clist_put_left:No, \clist_put_left:Nx,
+% \clist_put_left:cn, \clist_put_left:cV,
+% \clist_put_left:co, \clist_put_left:cx,
+% \clist_gput_left:Nn, \clist_gput_left:NV,
+% \clist_gput_left:No, \clist_gput_left:Nx,
+% \clist_gput_left:cn, \clist_gput_left:cV,
+% \clist_gput_left:co, \clist_gput_left:cx
+% }
+% \begin{syntax}
+% \cs{clist_put_left:Nn} \meta{comma list} |{|\meta{item_1},\ldots{},\meta{item_n}|}|
+% \end{syntax}
+% Appends the \meta{items} to the left of the \meta{comma list}.
+% Blank items are omitted, spaces are removed from both sides of each
+% item, then a set of braces is removed if the resulting space-trimmed
+% item is braced.
+% To append some \meta{tokens} as a single \meta{item} even if the
+% \meta{tokens} contain commas or spaces, add a set of braces:
+% \cs{clist_put_left:Nn} \meta{comma list} |{| \Arg{tokens} |}|.
+% \end{function}
+%
+% \begin{function}[updated = 2011-09-05]
+% {
+% \clist_put_right:Nn, \clist_put_right:NV,
+% \clist_put_right:No, \clist_put_right:Nx,
+% \clist_put_right:cn, \clist_put_right:cV,
+% \clist_put_right:co, \clist_put_right:cx,
+% \clist_gput_right:Nn, \clist_gput_right:NV,
+% \clist_gput_right:No, \clist_gput_right:Nx,
+% \clist_gput_right:cn, \clist_gput_right:cV,
+% \clist_gput_right:co, \clist_gput_right:cx
+% }
+% \begin{syntax}
+% \cs{clist_put_right:Nn} \meta{comma list} |{|\meta{item_1},\ldots{},\meta{item_n}|}|
+% \end{syntax}
+% Appends the \meta{items} to the right of the \meta{comma list}.
+% Blank items are omitted, spaces are removed from both sides of each
+% item, then a set of braces is removed if the resulting space-trimmed
+% item is braced.
+% To append some \meta{tokens} as a single \meta{item} even if the
+% \meta{tokens} contain commas or spaces, add a set of braces:
+% \cs{clist_put_right:Nn} \meta{comma list} |{| \Arg{tokens} |}|.
+% \end{function}
+%
+% \section{Modifying comma lists}
+%
+% While comma lists are normally used as ordered lists, it may be
+% necessary to modify the content. The functions here may be used
+% to update comma lists, while retaining the order of the unaffected
+% entries.
+%
+% \begin{function}
+% {
+% \clist_remove_duplicates:N, \clist_remove_duplicates:c,
+% \clist_gremove_duplicates:N, \clist_gremove_duplicates:c
+% }
+% \begin{syntax}
+% \cs{clist_remove_duplicates:N} \meta{comma list}
+% \end{syntax}
+% Removes duplicate items from the \meta{comma list}, leaving the
+% left most copy of each item in the \meta{comma list}. The \meta{item}
+% comparison takes place on a token basis, as for \cs{tl_if_eq:nn(TF)}.
+% \begin{texnote}
+% This function iterates through every item in the \meta{comma list} and
+% does a comparison with the \meta{items} already checked. It is therefore
+% relatively slow with large comma lists.
+% Furthermore, it may fail if any of the items in the
+% \meta{comma list} contains |{|, |}|, or |#|
+% (assuming the usual \TeX{} category codes apply).
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}[updated = 2011-09-06]
+% {
+% \clist_remove_all:Nn, \clist_remove_all:cn,
+% \clist_gremove_all:Nn, \clist_gremove_all:cn
+% }
+% \begin{syntax}
+% \cs{clist_remove_all:Nn} \meta{comma list} \Arg{item}
+% \end{syntax}
+% Removes every occurrence of \meta{item} from the \meta{comma list}.
+% The \meta{item} comparison takes place on a token basis, as for
+% \cs{tl_if_eq:nn(TF)}.
+% \begin{texnote}
+% The function may fail if the \meta{item} contains |{|, |}|, or |#|
+% (assuming the usual \TeX{} category codes apply).
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}[added = 2014-07-18]
+% {
+% \clist_reverse:N, \clist_reverse:c,
+% \clist_greverse:N, \clist_greverse:c
+% }
+% \begin{syntax}
+% \cs{clist_reverse:N} \meta{comma list}
+% \end{syntax}
+% Reverses the order of items stored in the \meta{comma list}.
+% \end{function}
+%
+% \begin{function}[added = 2014-07-18]{\clist_reverse:n}
+% \begin{syntax}
+% \cs{clist_reverse:n} \Arg{comma list}
+% \end{syntax}
+% Leaves the items in the \meta{comma list} in the input stream in
+% reverse order. Contrarily to other what is done for other
+% \texttt{n}-type \meta{comma list} arguments, braces and spaces are
+% preserved by this process.
+% \begin{texnote}
+% The result is returned within \tn{unexpanded}, which means that the
+% comma list does not expand further when appearing in an
+% \texttt{x}-type or \texttt{e}-type argument expansion.
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}[added = 2017-02-06]
+% {\clist_sort:Nn, \clist_sort:cn, \clist_gsort:Nn, \clist_gsort:cn}
+% \begin{syntax}
+% \cs{clist_sort:Nn} \meta{clist var} \Arg{comparison code}
+% \end{syntax}
+% Sorts the items in the \meta{clist var} according to the
+% \meta{comparison code}, and assigns the result to
+% \meta{clist var}. The details of sorting comparison are
+% described in Section~\ref{sec:l3sort:mech}.
+% \end{function}
+%
+% \section{Comma list conditionals}
+%
+% \begin{function}[EXP,pTF]{\clist_if_empty:N, \clist_if_empty:c}
+% \begin{syntax}
+% \cs{clist_if_empty_p:N} \meta{comma list}
+% \cs{clist_if_empty:NTF} \meta{comma list} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% Tests if the \meta{comma list} is empty (containing no items).
+% \end{function}
+%
+% \begin{function}[EXP, pTF, added = 2014-07-05]{\clist_if_empty:n}
+% \begin{syntax}
+% \cs{clist_if_empty_p:n} \Arg{comma list}
+% \cs{clist_if_empty:nTF} \Arg{comma list} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% Tests if the \meta{comma list} is empty (containing no items).
+% The rules for space trimming are as for other \texttt{n}-type
+% comma-list functions, hence the comma list |{~,~,,~}| (without
+% outer braces) is empty, while |{~,{},}| (without outer braces)
+% contains one element, which happens to be empty: the comma-list
+% is not empty.
+% \end{function}
+%
+% \begin{function}[updated = 2011-09-06, TF]
+% {
+% \clist_if_in:Nn, \clist_if_in:NV, \clist_if_in:No,
+% \clist_if_in:cn, \clist_if_in:cV, \clist_if_in:co,
+% \clist_if_in:nn, \clist_if_in:nV, \clist_if_in:no
+% }
+% \begin{syntax}
+% \cs{clist_if_in:NnTF} \meta{comma list} \Arg{item} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% Tests if the \meta{item} is present in the \meta{comma list}.
+% In the case of an \texttt{n}-type \meta{comma list}, the usual rules
+% of space trimming and brace stripping apply. Hence,
+% \begin{verbatim}
+% \clist_if_in:nnTF { a , {b}~ , {b} , c } { b } {true} {false}
+% \end{verbatim}
+% yields \texttt{true}.
+% \begin{texnote}
+% The function may fail if the \meta{item} contains |{|, |}|, or |#|
+% (assuming the usual \TeX{} category codes apply).
+% \end{texnote}
+% \end{function}
+%
+% \section{Mapping to comma lists}
+%
+% The functions described in this section apply a specified function
+% to each item of a comma list.
+% All mappings are done at the current group level, \emph{i.e.}~any
+% local assignments made by the \meta{function} or \meta{code} discussed
+% below remain in effect after the loop.
+%
+% When the comma list is given explicitly, as an \texttt{n}-type argument,
+% spaces are trimmed around each item.
+% If the result of trimming spaces is empty, the item is ignored.
+% Otherwise, if the item is surrounded by braces, one set is removed,
+% and the result is passed to the mapped function. Thus, if the
+% comma list that is being mapped is \verb*|{a , {{b} }, ,{}, {c},}|
+% then the arguments passed to the mapped function are
+% `\verb*|a|', `\verb*|{b} |', an empty argument, and `\verb*|c|'.
+%
+% When the comma list is given as an \texttt{N}-type argument, spaces
+% have already been trimmed on input, and items are simply stripped
+% of one set of braces if any. This case is more efficient than using
+% \texttt{n}-type comma lists.
+%
+% \begin{function}[rEXP, updated = 2012-06-29]
+% {\clist_map_function:NN, \clist_map_function:cN, \clist_map_function:nN}
+% \begin{syntax}
+% \cs{clist_map_function:NN} \meta{comma list} \meta{function}
+% \end{syntax}
+% Applies \meta{function} to every \meta{item} stored in the
+% \meta{comma list}. The \meta{function} receives one argument for
+% each iteration. The \meta{items} are returned from left to right.
+% The function \cs{clist_map_inline:Nn} is in general more efficient
+% than \cs{clist_map_function:NN}.
+% \end{function}
+%
+% \begin{function}[updated = 2012-06-29]
+% {\clist_map_inline:Nn, \clist_map_inline:cn, \clist_map_inline:nn}
+% \begin{syntax}
+% \cs{clist_map_inline:Nn} \meta{comma list} \Arg{inline function}
+% \end{syntax}
+% Applies \meta{inline function} to every \meta{item} stored
+% within the \meta{comma list}. The \meta{inline function} should
+% consist of code which receives the \meta{item} as |#1|.
+% The \meta{items} are returned from left to right.
+% \end{function}
+%
+% \begin{function}[updated = 2012-06-29]
+% {\clist_map_variable:NNn, \clist_map_variable:cNn, \clist_map_variable:nNn}
+% \begin{syntax}
+% \cs{clist_map_variable:NNn} \meta{comma list} \meta{variable} \Arg{code}
+% \end{syntax}
+% Stores each \meta{item} of the \meta{comma list} in turn in the
+% (token list) \meta{variable} and applies the \meta{code}. The
+% \meta{code} will usually make use of the \meta{variable}, but this
+% is not enforced. The assignments to the \meta{variable} are local.
+% Its value after the loop is the last \meta{item} in the \meta{comma
+% list}, or its original value if there were no \meta{item}. The
+% \meta{items} are returned from left to right.
+% \end{function}
+%
+% \begin{function}[rEXP, updated = 2012-06-29]{\clist_map_break:}
+% \begin{syntax}
+% \cs{clist_map_break:}
+% \end{syntax}
+% Used to terminate a \cs[no-index]{clist_map_\ldots{}} function before all
+% entries in the \meta{comma list} have been processed. This
+% normally takes place within a conditional statement, for example
+% \begin{verbatim}
+% \clist_map_inline:Nn \l_my_clist
+% {
+% \str_if_eq:nnTF { #1 } { bingo }
+% { \clist_map_break: }
+% {
+% % Do something useful
+% }
+% }
+% \end{verbatim}
+% Use outside of a \cs[no-index]{clist_map_\ldots{}} scenario leads to low
+% level \TeX{} errors.
+% \begin{texnote}
+% When the mapping is broken, additional tokens may be inserted
+% before further items are taken
+% from the input stream. This depends on the design of the mapping
+% function.
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}[updated = 2012-06-29, rEXP]{\clist_map_break:n}
+% \begin{syntax}
+% \cs{clist_map_break:n} \Arg{code}
+% \end{syntax}
+% Used to terminate a \cs[no-index]{clist_map_\ldots{}} function before all
+% entries in the \meta{comma list} have been processed, inserting
+% the \meta{code} after the mapping has ended. This
+% normally takes place within a conditional statement, for example
+% \begin{verbatim}
+% \clist_map_inline:Nn \l_my_clist
+% {
+% \str_if_eq:nnTF { #1 } { bingo }
+% { \clist_map_break:n { <code> } }
+% {
+% % Do something useful
+% }
+% }
+% \end{verbatim}
+% Use outside of a \cs[no-index]{clist_map_\ldots{}} scenario leads to low
+% level \TeX{} errors.
+% \begin{texnote}
+% When the mapping is broken, additional tokens may be inserted
+% before the \meta{code} is
+% inserted into the input stream.
+% This depends on the design of the mapping function.
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}[EXP, added = 2012-07-13]
+% {\clist_count:N, \clist_count:c, \clist_count:n}
+% \begin{syntax}
+% \cs{clist_count:N} \meta{comma list}
+% \end{syntax}
+% Leaves the number of items in the \meta{comma list} in the input
+% stream as an \meta{integer denotation}. The total number of items
+% in a \meta{comma list} includes those which are duplicates,
+% \emph{i.e.}~every item in a \meta{comma list} is counted.
+% \end{function}
+%
+% \section{Using the content of comma lists directly}
+%
+% \begin{function}[EXP, added = 2013-05-26]{\clist_use:Nnnn, \clist_use:cnnn}
+% \begin{syntax}
+% \cs{clist_use:Nnnn} \meta{clist~var} \Arg{separator~between~two} \Arg{separator~between~more~than~two} \Arg{separator~between~final~two}
+% \end{syntax}
+% Places the contents of the \meta{clist~var} in the input stream,
+% with the appropriate \meta{separator} between the items. Namely, if
+% the comma list has more than two items, the \meta{separator between
+% more than two} is placed between each pair of items except the
+% last, for which the \meta{separator between final two} is used. If
+% the comma list has exactly two items, then they are placed in the input
+% stream separated by the \meta{separator between two}. If the comma
+% list has a single item, it is placed in the input stream, and a comma
+% list with no items produces no output. An error is raised if
+% the variable does not exist or if it is invalid.
+%
+% For example,
+% \begin{verbatim}
+% \clist_set:Nn \l_tmpa_clist { a , b , , c , {de} , f }
+% \clist_use:Nnnn \l_tmpa_clist { ~and~ } { ,~ } { ,~and~ }
+% \end{verbatim}
+% inserts \enquote{\texttt{a, b, c, de, and f}} in the input
+% stream. The first separator argument is not used in this case
+% because the comma list has more than $2$ items.
+% \begin{texnote}
+% The result is returned within the \tn{unexpanded}
+% primitive (\cs{exp_not:n}), which means that the \meta{items}
+% do not expand further when appearing in an \texttt{x}-type
+% argument expansion.
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}[EXP, added = 2013-05-26]{\clist_use:Nn, \clist_use:cn}
+% \begin{syntax}
+% \cs{clist_use:Nn} \meta{clist~var} \Arg{separator}
+% \end{syntax}
+% Places the contents of the \meta{clist~var} in the input stream,
+% with the \meta{separator} between the items. If the comma
+% list has a single item, it is placed in the input stream, and a comma
+% list with no items produces no output. An error is raised if
+% the variable does not exist or if it is invalid.
+%
+% For example,
+% \begin{verbatim}
+% \clist_set:Nn \l_tmpa_clist { a , b , , c , {de} , f }
+% \clist_use:Nn \l_tmpa_clist { ~and~ }
+% \end{verbatim}
+% inserts \enquote{\texttt{a and b and c and de and f}} in the input
+% stream.
+% \begin{texnote}
+% The result is returned within the \tn{unexpanded}
+% primitive (\cs{exp_not:n}), which means that the \meta{items}
+% do not expand further when appearing in an \texttt{x}-type
+% argument expansion.
+% \end{texnote}
+% \end{function}
+%
+% \section{Comma lists as stacks}
+%
+% Comma lists can be used as stacks, where data is pushed to and popped
+% from the top of the comma list. (The left of a comma list is the top, for
+% performance reasons.) The stack functions for comma lists are not
+% intended to be mixed with the general ordered data functions detailed
+% in the previous section: a comma list should either be used as an
+% ordered data type or as a stack, but not in both ways.
+%
+% \begin{function}[noTF, added = 2012-05-14, updated = 2019-02-16]
+% {\clist_get:NN, \clist_get:cN}
+% \begin{syntax}
+% \cs{clist_get:NN} \meta{comma list} \meta{token list variable}
+% \end{syntax}
+% Stores the left-most item from a \meta{comma list} in the
+% \meta{token list variable} without removing it from the
+% \meta{comma list}. The \meta{token list variable} is assigned locally.
+% In the non-branching version, if the \meta{comma list} is empty the
+% \meta{token list variable} is set to the marker value \cs{q_no_value}.
+% \end{function}
+%
+% \begin{function}[updated = 2011-09-06]{\clist_pop:NN, \clist_pop:cN}
+% \begin{syntax}
+% \cs{clist_pop:NN} \meta{comma list} \meta{token list variable}
+% \end{syntax}
+% Pops the left-most item from a \meta{comma list} into the
+% \meta{token list variable}, \emph{i.e.}~removes the item from the
+% comma list and stores it in the \meta{token list variable}.
+% Both of the variables are assigned locally.
+% \end{function}
+%
+% \begin{function}{\clist_gpop:NN, \clist_gpop:cN}
+% \begin{syntax}
+% \cs{clist_gpop:NN} \meta{comma list} \meta{token list variable}
+% \end{syntax}
+% Pops the left-most item from a \meta{comma list} into the
+% \meta{token list variable}, \emph{i.e.}~removes the item from the
+% comma list and stores it in the \meta{token list variable}.
+% The \meta{comma list} is modified globally, while the assignment of
+% the \meta{token list variable} is local.
+% \end{function}
+%
+% \begin{function}[TF, added = 2012-05-14]{\clist_pop:NN, \clist_pop:cN}
+% \begin{syntax}
+% \cs{clist_pop:NNTF} \meta{comma list} \meta{token list variable} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% If the \meta{comma list} is empty, leaves the \meta{false code} in the
+% input stream. The value of the \meta{token list variable} is
+% not defined in this case and should not be relied upon. If the
+% \meta{comma list} is non-empty, pops the top item from the
+% \meta{comma list} in the \meta{token list variable}, \emph{i.e.}~removes
+% the item from the \meta{comma list}. Both the \meta{comma list} and the
+% \meta{token list variable} are assigned locally.
+% \end{function}
+%
+% \begin{function}[TF, added = 2012-05-14]{\clist_gpop:NN, \clist_gpop:cN}
+% \begin{syntax}
+% \cs{clist_gpop:NNTF} \meta{comma list} \meta{token list variable} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% If the \meta{comma list} is empty, leaves the \meta{false code} in the
+% input stream. The value of the \meta{token list variable} is
+% not defined in this case and should not be relied upon. If the
+% \meta{comma list} is non-empty, pops the top item from the
+% \meta{comma list} in the \meta{token list variable}, \emph{i.e.}~removes
+% the item from the \meta{comma list}. The \meta{comma list} is modified
+% globally, while the \meta{token list variable} is assigned locally.
+% \end{function}
+%
+% \begin{function}
+% {
+% \clist_push:Nn, \clist_push:NV, \clist_push:No, \clist_push:Nx,
+% \clist_push:cn, \clist_push:cV, \clist_push:co, \clist_push:cx,
+% \clist_gpush:Nn, \clist_gpush:NV, \clist_gpush:No, \clist_gpush:Nx,
+% \clist_gpush:cn, \clist_gpush:cV, \clist_gpush:co, \clist_gpush:cx
+% }
+% \begin{syntax}
+% \cs{clist_push:Nn} \meta{comma list} \Arg{items}
+% \end{syntax}
+% Adds the \Arg{items} to the top of the \meta{comma list}.
+% Spaces are removed from both sides of each item as for any
+% \texttt{n}-type comma list.
+% \end{function}
+%
+% \section{Using a single item}
+%
+% \begin{function}[added = 2014-07-17, EXP]
+% {\clist_item:Nn, \clist_item:cn, \clist_item:nn}
+% \begin{syntax}
+% \cs{clist_item:Nn} \meta{comma list} \Arg{integer expression}
+% \end{syntax}
+% Indexing items in the \meta{comma list} from~$1$ at the top (left), this
+% function evaluates the \meta{integer expression} and leaves the
+% appropriate item from the comma list in the input stream. If the
+% \meta{integer expression} is negative, indexing occurs from the
+% bottom (right) of the comma list. When the \meta{integer expression}
+% is larger than the number of items in the \meta{comma list} (as
+% calculated by \cs{clist_count:N}) then the function expands to
+% nothing.
+% \begin{texnote}
+% The result is returned within the \tn{unexpanded}
+% primitive (\cs{exp_not:n}), which means that the \meta{item}
+% does not expand further when appearing in an \texttt{x}-type
+% argument expansion.
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}[EXP, added = 2016-12-06]
+% {\clist_rand_item:N, \clist_rand_item:n, \clist_rand_item:c}
+% \begin{syntax}
+% \cs{clist_rand_item:N} \meta{clist~var}
+% \cs{clist_rand_item:n} \Arg{comma list}
+% \end{syntax}
+% Selects a pseudo-random item of the \meta{comma list}. If the
+% \meta{comma list} has no item, the result is empty.
+% \begin{texnote}
+% The result is returned within the \tn{unexpanded}
+% primitive (\cs{exp_not:n}), which means that the \meta{item}
+% does not expand further when appearing in an \texttt{x}-type
+% argument expansion.
+% \end{texnote}
+% \end{function}
+%
+% \section{Viewing comma lists}
+%
+% \begin{function}[updated = 2015-08-03]{\clist_show:N, \clist_show:c}
+% \begin{syntax}
+% \cs{clist_show:N} \meta{comma list}
+% \end{syntax}
+% Displays the entries in the \meta{comma list} in the terminal.
+% \end{function}
+%
+% \begin{function}[updated = 2013-08-03]{\clist_show:n}
+% \begin{syntax}
+% \cs{clist_show:n} \Arg{tokens}
+% \end{syntax}
+% Displays the entries in the comma list in the terminal.
+% \end{function}
+%
+% \begin{function}[added = 2014-08-22, updated = 2015-08-03]{\clist_log:N, \clist_log:c}
+% \begin{syntax}
+% \cs{clist_log:N} \meta{comma list}
+% \end{syntax}
+% Writes the entries in the \meta{comma list} in the log file. See
+% also \cs{clist_show:N} which displays the result in the terminal.
+% \end{function}
+%
+% \begin{function}[added = 2014-08-22]{\clist_log:n}
+% \begin{syntax}
+% \cs{clist_log:n} \Arg{tokens}
+% \end{syntax}
+% Writes the entries in the comma list in the log file. See also
+% \cs{clist_show:n} which displays the result in the terminal.
+% \end{function}
+%
+% \section{Constant and scratch comma lists}
+%
+% \begin{variable}[added = 2012-07-02]{\c_empty_clist}
+% Constant that is always empty.
+% \end{variable}
+%
+% \begin{variable}[added = 2011-09-06]{\l_tmpa_clist, \l_tmpb_clist}
+% Scratch comma lists for local assignment. These are never used by
+% the kernel code, and so are safe for use with any \LaTeX3-defined
+% function. However, they may be overwritten by other non-kernel
+% code and so should only be used for short-term storage.
+% \end{variable}
+%
+% \begin{variable}[added = 2011-09-06]{\g_tmpa_clist, \g_tmpb_clist}
+% Scratch comma lists for global assignment. These are never used by
+% the kernel code, and so are safe for use with any \LaTeX3-defined
+% function. However, they may be overwritten by other non-kernel
+% code and so should only be used for short-term storage.
+% \end{variable}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3clist} implementation}
+%
+% \TestFiles{m3clist002}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=clist>
+% \end{macrocode}
+%
+% \begin{variable}{\c_empty_clist}
+% An empty comma list is simply an empty token list.
+% \begin{macrocode}
+\cs_new_eq:NN \c_empty_clist \c_empty_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_internal_clist}
+% Scratch space for various internal uses. This comma list variable
+% cannot be declared as such because it comes before \cs{clist_new:N}
+% \begin{macrocode}
+\tl_new:N \l_@@_internal_clist
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_tmp:w}
+% A temporary function for various purposes.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_tmp:w { }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Removing spaces around items}
+%
+% \begin{macro}{\@@_trim_next:w}
+% Called as \cs{exp:w} \cs{@@_trim_next:w} \cs{prg_do_nothing:}
+% \meta{comma list} \ldots{} it expands to \Arg{trimmed item} where
+% the \meta{trimmed item} is the first non-empty result from removing
+% spaces from both ends of comma-delimited items in the \meta{comma
+% list}. The \cs{prg_do_nothing:} marker avoids losing braces. The
+% test for blank items is a somewhat optimized \cs{tl_if_empty:oTF}
+% construction; if blank, another item is sought, otherwise trim
+% spaces.
+% \begin{macrocode}
+\cs_new:Npn \@@_trim_next:w #1 ,
+ {
+ \tl_if_empty:oTF { \use_none:nn #1 ? }
+ { \@@_trim_next:w \prg_do_nothing: }
+ { \tl_trim_spaces_apply:oN {#1} \exp_end: }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[rEXP]{\@@_sanitize:n}
+% \begin{macro}{\@@_sanitize:Nn}
+% The auxiliary \cs{@@_sanitize:Nn} receives a delimiter
+% (\cs{c_empty_tl} the first time, afterwards a comma) and that item
+% as arguments. Unless we are done with the loop it calls
+% \cs{@@_wrap_item:w} to unbrace the item (using a comma delimiter is
+% safe since |#2| came from removing spaces from an argument delimited
+% by a comma) and possibly re-brace it if needed.
+% \begin{macrocode}
+\cs_new:Npn \@@_sanitize:n #1
+ {
+ \exp_after:wN \@@_sanitize:Nn \exp_after:wN \c_empty_tl
+ \exp:w \@@_trim_next:w \prg_do_nothing:
+ #1 , \q_recursion_tail , \q_recursion_stop
+ }
+\cs_new:Npn \@@_sanitize:Nn #1#2
+ {
+ \quark_if_recursion_tail_stop:n {#2}
+ #1 \@@_wrap_item:w #2 ,
+ \exp_after:wN \@@_sanitize:Nn \exp_after:wN ,
+ \exp:w \@@_trim_next:w \prg_do_nothing:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[TF]{\@@_if_wrap:n}
+% \begin{macro}{\@@_if_wrap:w}
+% True if the argument must be wrapped to avoid getting altered by some
+% clist operations. That is the case whenever the argument
+% \begin{itemize}
+% \item starts or end with a space or contains a comma,
+% \item is empty, or
+% \item consists of a single braced group.
+% \end{itemize}
+% All \pkg{l3clist} functions go through the same test when they need
+% to determine whether to brace an item, so it is not a problem that
+% this test has false positives such as \enquote{\cs{q_mark}
+% \texttt{?}}. If the argument starts or end with a space or contains
+% a comma then one of the three arguments of \cs{@@_if_wrap:w} will
+% have its end delimiter (partly) in one of the three copies of |#1|
+% in \cs{@@_if_wrap:nTF}; this has a knock-on effect meaning that the
+% result of the expansion is not empty; in that case, wrap.
+% Otherwise, the argument is safe unless it starts with a brace group
+% (or is empty) and it is empty or consists of a single
+% \texttt{n}-type argument.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \@@_if_wrap:n #1 { TF }
+ {
+ \tl_if_empty:oTF
+ {
+ \@@_if_wrap:w
+ \q_mark ? #1 ~ \q_mark ? ~ #1 \q_mark , ~ \q_mark #1 ,
+ }
+ {
+ \tl_if_head_is_group:nTF { #1 { } }
+ {
+ \tl_if_empty:nTF {#1}
+ { \prg_return_true: }
+ {
+ \tl_if_empty:oTF { \use_none:n #1}
+ { \prg_return_true: }
+ { \prg_return_false: }
+ }
+ }
+ { \prg_return_false: }
+ }
+ { \prg_return_true: }
+ }
+\cs_new:Npn \@@_if_wrap:w #1 \q_mark ? ~ #2 ~ \q_mark #3 , { }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_wrap_item:w}
+% Safe items are put in \cs{exp_not:n}, otherwise we put an extra set
+% of braces.
+% \begin{macrocode}
+\cs_new:Npn \@@_wrap_item:w #1 ,
+ { \@@_if_wrap:nTF {#1} { \exp_not:n { {#1} } } { \exp_not:n {#1} } }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Allocation and initialisation}
+%
+% \begin{macro}{\clist_new:N, \clist_new:c}
+% \UnitTested
+% Internally, comma lists are just token lists.
+% \begin{macrocode}
+\cs_new_eq:NN \clist_new:N \tl_new:N
+\cs_new_eq:NN \clist_new:c \tl_new:c
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}
+% {
+% \clist_const:Nn, \clist_const:cn,
+% \clist_const:Nx, \clist_const:cx
+% }
+% Creating and initializing a constant comma list is done by
+% sanitizing all items (stripping spaces and braces).
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_const:Nn #1#2
+ { \tl_const:Nx #1 { \@@_sanitize:n {#2} } }
+\cs_generate_variant:Nn \clist_const:Nn { c , Nx , cx }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clist_clear:N, \clist_clear:c}
+% \UnitTested
+% \begin{macro}{\clist_gclear:N, \clist_gclear:c}
+% \UnitTested
+% Clearing comma lists is just the same as clearing token lists.
+% \begin{macrocode}
+\cs_new_eq:NN \clist_clear:N \tl_clear:N
+\cs_new_eq:NN \clist_clear:c \tl_clear:c
+\cs_new_eq:NN \clist_gclear:N \tl_gclear:N
+\cs_new_eq:NN \clist_gclear:c \tl_gclear:c
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\clist_clear_new:N, \clist_clear_new:c}
+% \UnitTested
+% \begin{macro}{\clist_gclear_new:N, \clist_gclear_new:c}
+% \UnitTested
+% Once again a copy from the token list functions.
+% \begin{macrocode}
+\cs_new_eq:NN \clist_clear_new:N \tl_clear_new:N
+\cs_new_eq:NN \clist_clear_new:c \tl_clear_new:c
+\cs_new_eq:NN \clist_gclear_new:N \tl_gclear_new:N
+\cs_new_eq:NN \clist_gclear_new:c \tl_gclear_new:c
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}
+% {\clist_set_eq:NN, \clist_set_eq:cN, \clist_set_eq:Nc, \clist_set_eq:cc}
+% \UnitTested
+% \begin{macro}
+% {
+% \clist_gset_eq:NN, \clist_gset_eq:cN,
+% \clist_gset_eq:Nc, \clist_gset_eq:cc
+% }
+% \UnitTested
+% Once again, these are simple copies from the token list functions.
+% \begin{macrocode}
+\cs_new_eq:NN \clist_set_eq:NN \tl_set_eq:NN
+\cs_new_eq:NN \clist_set_eq:Nc \tl_set_eq:Nc
+\cs_new_eq:NN \clist_set_eq:cN \tl_set_eq:cN
+\cs_new_eq:NN \clist_set_eq:cc \tl_set_eq:cc
+\cs_new_eq:NN \clist_gset_eq:NN \tl_gset_eq:NN
+\cs_new_eq:NN \clist_gset_eq:Nc \tl_gset_eq:Nc
+\cs_new_eq:NN \clist_gset_eq:cN \tl_gset_eq:cN
+\cs_new_eq:NN \clist_gset_eq:cc \tl_gset_eq:cc
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}
+% {
+% \clist_set_from_seq:NN, \clist_set_from_seq:cN,
+% \clist_set_from_seq:Nc, \clist_set_from_seq:cc
+% }
+% \UnitTested
+% \begin{macro}
+% {
+% \clist_gset_from_seq:NN, \clist_gset_from_seq:cN,
+% \clist_gset_from_seq:Nc, \clist_gset_from_seq:cc
+% }
+% \UnitTested
+% \begin{macro}{\@@_set_from_seq:NNNN, \@@_set_from_seq:n}
+% Setting a comma list from a comma-separated list is done using a
+% simple mapping. Safe items are put in \cs{exp_not:n}, otherwise we
+% put an extra set of braces. The first comma must be removed, except
+% in the case of an empty comma-list.
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_set_from_seq:NN
+ { \@@_set_from_seq:NNNN \clist_clear:N \tl_set:Nx }
+\cs_new_protected:Npn \clist_gset_from_seq:NN
+ { \@@_set_from_seq:NNNN \clist_gclear:N \tl_gset:Nx }
+\cs_new_protected:Npn \@@_set_from_seq:NNNN #1#2#3#4
+ {
+ \seq_if_empty:NTF #4
+ { #1 #3 }
+ {
+ #2 #3
+ {
+ \exp_after:wN \use_none:n \exp:w \exp_end_continue_f:w
+ \seq_map_function:NN #4 \@@_set_from_seq:n
+ }
+ }
+ }
+\cs_new:Npn \@@_set_from_seq:n #1
+ {
+ ,
+ \@@_if_wrap:nTF {#1}
+ { \exp_not:n { {#1} } }
+ { \exp_not:n {#1} }
+ }
+\cs_generate_variant:Nn \clist_set_from_seq:NN { Nc }
+\cs_generate_variant:Nn \clist_set_from_seq:NN { c , cc }
+\cs_generate_variant:Nn \clist_gset_from_seq:NN { Nc }
+\cs_generate_variant:Nn \clist_gset_from_seq:NN { c , cc }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\clist_concat:NNN, \clist_concat:ccc}
+% \UnitTested
+% \begin{macro}{\clist_gconcat:NNN, \clist_gconcat:ccc}
+% \UnitTested
+% \begin{macro}{\@@_concat:NNNN}
+% Concatenating comma lists is not quite as easy as it seems, as
+% there needs to be the correct addition of a comma to the output. So
+% a little work to do.
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_concat:NNN
+ { \@@_concat:NNNN \tl_set:Nx }
+\cs_new_protected:Npn \clist_gconcat:NNN
+ { \@@_concat:NNNN \tl_gset:Nx }
+\cs_new_protected:Npn \@@_concat:NNNN #1#2#3#4
+ {
+ #1 #2
+ {
+ \exp_not:o #3
+ \clist_if_empty:NF #3 { \clist_if_empty:NF #4 { , } }
+ \exp_not:o #4
+ }
+ }
+\cs_generate_variant:Nn \clist_concat:NNN { ccc }
+\cs_generate_variant:Nn \clist_gconcat:NNN { ccc }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[pTF]{\clist_if_exist:N, \clist_if_exist:c}
+% Copies of the \texttt{cs} functions defined in \pkg{l3basics}.
+% \begin{macrocode}
+\prg_new_eq_conditional:NNn \clist_if_exist:N \cs_if_exist:N
+ { TF , T , F , p }
+\prg_new_eq_conditional:NNn \clist_if_exist:c \cs_if_exist:c
+ { TF , T , F , p }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Adding data to comma lists}
+%
+% \begin{macro}
+% {
+% \clist_set:Nn, \clist_set:NV,
+% \clist_set:No, \clist_set:Nx,
+% \clist_set:cn, \clist_set:cV,
+% \clist_set:co, \clist_set:cx
+% }
+% \begin{macro}
+% {
+% \clist_gset:Nn, \clist_gset:NV,
+% \clist_gset:No, \clist_gset:Nx,
+% \clist_gset:cn, \clist_gset:cV,
+% \clist_gset:co, \clist_gset:cx
+% }
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_set:Nn #1#2
+ { \tl_set:Nx #1 { \@@_sanitize:n {#2} } }
+\cs_new_protected:Npn \clist_gset:Nn #1#2
+ { \tl_gset:Nx #1 { \@@_sanitize:n {#2} } }
+\cs_generate_variant:Nn \clist_set:Nn { NV , No , Nx , c , cV , co , cx }
+\cs_generate_variant:Nn \clist_gset:Nn { NV , No , Nx , c , cV , co , cx }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}
+% {
+% \clist_put_left:Nn, \clist_put_left:NV,
+% \clist_put_left:No, \clist_put_left:Nx,
+% \clist_put_left:cn, \clist_put_left:cV,
+% \clist_put_left:co, \clist_put_left:cx
+% }
+% \UnitTested
+% \begin{macro}
+% {
+% \clist_gput_left:Nn, \clist_gput_left:NV,
+% \clist_gput_left:No, \clist_gput_left:Nx,
+% \clist_gput_left:cn, \clist_gput_left:cV,
+% \clist_gput_left:co, \clist_gput_left:cx
+% }
+% \UnitTested
+% \begin{macro}{\@@_put_left:NNNn}
+% Everything is based on concatenation after storing in
+% \cs{l_@@_internal_clist}. This avoids having to worry here about
+% space-trimming and so on.
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_put_left:Nn
+ { \@@_put_left:NNNn \clist_concat:NNN \clist_set:Nn }
+\cs_new_protected:Npn \clist_gput_left:Nn
+ { \@@_put_left:NNNn \clist_gconcat:NNN \clist_set:Nn }
+\cs_new_protected:Npn \@@_put_left:NNNn #1#2#3#4
+ {
+ #2 \l_@@_internal_clist {#4}
+ #1 #3 \l_@@_internal_clist #3
+ }
+\cs_generate_variant:Nn \clist_put_left:Nn { NV , No , Nx }
+\cs_generate_variant:Nn \clist_put_left:Nn { c , cV , co , cx }
+\cs_generate_variant:Nn \clist_gput_left:Nn { NV , No , Nx }
+\cs_generate_variant:Nn \clist_gput_left:Nn { c , cV , co , cx }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}
+% {
+% \clist_put_right:Nn, \clist_put_right:NV,
+% \clist_put_right:No, \clist_put_right:Nx,
+% \clist_put_right:cn, \clist_put_right:cV,
+% \clist_put_right:co, \clist_put_right:cx
+% }
+% \UnitTested
+% \begin{macro}
+% {
+% \clist_gput_right:Nn, \clist_gput_right:NV,
+% \clist_gput_right:No, \clist_gput_right:Nx,
+% \clist_gput_right:cn, \clist_gput_right:cV,
+% \clist_gput_right:co, \clist_gput_right:cx
+% }
+% \UnitTested
+% \begin{macro}{\@@_put_right:NNNn}
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_put_right:Nn
+ { \@@_put_right:NNNn \clist_concat:NNN \clist_set:Nn }
+\cs_new_protected:Npn \clist_gput_right:Nn
+ { \@@_put_right:NNNn \clist_gconcat:NNN \clist_set:Nn }
+\cs_new_protected:Npn \@@_put_right:NNNn #1#2#3#4
+ {
+ #2 \l_@@_internal_clist {#4}
+ #1 #3 #3 \l_@@_internal_clist
+ }
+\cs_generate_variant:Nn \clist_put_right:Nn { NV , No , Nx }
+\cs_generate_variant:Nn \clist_put_right:Nn { c , cV , co , cx }
+\cs_generate_variant:Nn \clist_gput_right:Nn { NV , No , Nx }
+\cs_generate_variant:Nn \clist_gput_right:Nn { c , cV , co , cx }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Comma lists as stacks}
+%
+% \begin{macro}{\clist_get:NN, \clist_get:cN}
+% \UnitTested
+% \begin{macro}{\@@_get:wN}
+% Getting an item from the left of a comma list is pretty easy: just
+% trim off the first item using the comma. No need to trim spaces as
+% comma-list \emph{variables} are assumed to have \enquote{cleaned-up}
+% items. (Note that grabbing a comma-delimited item removes an outer
+% pair of braces if present, exactly as needed to uncover the
+% underlying item.)
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_get:NN #1#2
+ {
+ \if_meaning:w #1 \c_empty_clist
+ \tl_set:Nn #2 { \q_no_value }
+ \else:
+ \exp_after:wN \@@_get:wN #1 , \q_stop #2
+ \fi:
+ }
+\cs_new_protected:Npn \@@_get:wN #1 , #2 \q_stop #3
+ { \tl_set:Nn #3 {#1} }
+\cs_generate_variant:Nn \clist_get:NN { c }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\clist_pop:NN, \clist_pop:cN}
+% \UnitTested
+% \begin{macro}{\clist_gpop:NN, \clist_gpop:cN}
+% \UnitTested
+% \begin{macro}
+% {\@@_pop:NNN, \@@_pop:wwNNN, \@@_pop:wN}
+% An empty clist leads to \cs{q_no_value}, otherwise grab until the
+% first comma and assign to the variable. The second argument of
+% \cs{@@_pop:wwNNN} is a comma list ending in a comma and
+% \cs{q_mark}, unless the original clist contained exactly one item:
+% then the argument is just \cs{q_mark}. The next auxiliary picks
+% either \cs{exp_not:n} or \cs{use_none:n} as |#2|, ensuring that the
+% result can safely be an empty comma list.
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_pop:NN
+ { \@@_pop:NNN \tl_set:Nx }
+\cs_new_protected:Npn \clist_gpop:NN
+ { \@@_pop:NNN \tl_gset:Nx }
+\cs_new_protected:Npn \@@_pop:NNN #1#2#3
+ {
+ \if_meaning:w #2 \c_empty_clist
+ \tl_set:Nn #3 { \q_no_value }
+ \else:
+ \exp_after:wN \@@_pop:wwNNN #2 , \q_mark \q_stop #1#2#3
+ \fi:
+ }
+\cs_new_protected:Npn \@@_pop:wwNNN #1 , #2 \q_stop #3#4#5
+ {
+ \tl_set:Nn #5 {#1}
+ #3 #4
+ {
+ \@@_pop:wN \prg_do_nothing:
+ #2 \exp_not:o
+ , \q_mark \use_none:n
+ \q_stop
+ }
+ }
+\cs_new:Npn \@@_pop:wN #1 , \q_mark #2 #3 \q_stop { #2 {#1} }
+\cs_generate_variant:Nn \clist_pop:NN { c }
+\cs_generate_variant:Nn \clist_gpop:NN { c }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[TF]{\clist_get:NN, \clist_get:cN}
+% \begin{macro}[TF]{\clist_pop:NN, \clist_pop:cN}
+% \begin{macro}[TF]{\clist_gpop:NN, \clist_gpop:cN}
+% \begin{macro}{\@@_pop_TF:NNN}
+% The same, as branching code: very similar to the above.
+% \begin{macrocode}
+\prg_new_protected_conditional:Npnn \clist_get:NN #1#2 { T , F , TF }
+ {
+ \if_meaning:w #1 \c_empty_clist
+ \prg_return_false:
+ \else:
+ \exp_after:wN \@@_get:wN #1 , \q_stop #2
+ \prg_return_true:
+ \fi:
+ }
+\prg_generate_conditional_variant:Nnn \clist_get:NN { c } { T , F , TF }
+\prg_new_protected_conditional:Npnn \clist_pop:NN #1#2 { T , F , TF }
+ { \@@_pop_TF:NNN \tl_set:Nx #1 #2 }
+\prg_new_protected_conditional:Npnn \clist_gpop:NN #1#2 { T , F , TF }
+ { \@@_pop_TF:NNN \tl_gset:Nx #1 #2 }
+\cs_new_protected:Npn \@@_pop_TF:NNN #1#2#3
+ {
+ \if_meaning:w #2 \c_empty_clist
+ \prg_return_false:
+ \else:
+ \exp_after:wN \@@_pop:wwNNN #2 , \q_mark \q_stop #1#2#3
+ \prg_return_true:
+ \fi:
+ }
+\prg_generate_conditional_variant:Nnn \clist_pop:NN { c } { T , F , TF }
+\prg_generate_conditional_variant:Nnn \clist_gpop:NN { c } { T , F , TF }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{
+% \clist_push:Nn, \clist_push:NV, \clist_push:No, \clist_push:Nx,
+% \clist_push:cn, \clist_push:cV, \clist_push:co, \clist_push:cx
+% }
+% \UnitTested
+% \begin{macro}{
+% \clist_gpush:Nn, \clist_gpush:NV, \clist_gpush:No, \clist_gpush:Nx,
+% \clist_gpush:cn, \clist_gpush:cV, \clist_gpush:co, \clist_gpush:cx
+% }
+% \UnitTested
+% Pushing to a comma list is the same as adding on the left.
+% \begin{macrocode}
+\cs_new_eq:NN \clist_push:Nn \clist_put_left:Nn
+\cs_new_eq:NN \clist_push:NV \clist_put_left:NV
+\cs_new_eq:NN \clist_push:No \clist_put_left:No
+\cs_new_eq:NN \clist_push:Nx \clist_put_left:Nx
+\cs_new_eq:NN \clist_push:cn \clist_put_left:cn
+\cs_new_eq:NN \clist_push:cV \clist_put_left:cV
+\cs_new_eq:NN \clist_push:co \clist_put_left:co
+\cs_new_eq:NN \clist_push:cx \clist_put_left:cx
+\cs_new_eq:NN \clist_gpush:Nn \clist_gput_left:Nn
+\cs_new_eq:NN \clist_gpush:NV \clist_gput_left:NV
+\cs_new_eq:NN \clist_gpush:No \clist_gput_left:No
+\cs_new_eq:NN \clist_gpush:Nx \clist_gput_left:Nx
+\cs_new_eq:NN \clist_gpush:cn \clist_gput_left:cn
+\cs_new_eq:NN \clist_gpush:cV \clist_gput_left:cV
+\cs_new_eq:NN \clist_gpush:co \clist_gput_left:co
+\cs_new_eq:NN \clist_gpush:cx \clist_gput_left:cx
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Modifying comma lists}
+%
+% \begin{variable}{\l_@@_internal_remove_clist, \l_@@_internal_remove_seq}
+% An internal comma list and a sequence for the removal routines.
+% \begin{macrocode}
+\clist_new:N \l_@@_internal_remove_clist
+\seq_new:N \l_@@_internal_remove_seq
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\clist_remove_duplicates:N, \clist_remove_duplicates:c}
+% \UnitTested
+% \begin{macro}{\clist_gremove_duplicates:N, \clist_gremove_duplicates:c}
+% \UnitTested
+% \begin{macro}{\@@_remove_duplicates:NN}
+% Removing duplicates means making a new list then copying it.
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_remove_duplicates:N
+ { \@@_remove_duplicates:NN \clist_set_eq:NN }
+\cs_new_protected:Npn \clist_gremove_duplicates:N
+ { \@@_remove_duplicates:NN \clist_gset_eq:NN }
+\cs_new_protected:Npn \@@_remove_duplicates:NN #1#2
+ {
+ \clist_clear:N \l_@@_internal_remove_clist
+ \clist_map_inline:Nn #2
+ {
+ \clist_if_in:NnF \l_@@_internal_remove_clist {##1}
+ { \clist_put_right:Nn \l_@@_internal_remove_clist {##1} }
+ }
+ #1 #2 \l_@@_internal_remove_clist
+ }
+\cs_generate_variant:Nn \clist_remove_duplicates:N { c }
+\cs_generate_variant:Nn \clist_gremove_duplicates:N { c }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\clist_remove_all:Nn, \clist_remove_all:cn}
+% \UnitTested
+% \begin{macro}{\clist_gremove_all:Nn, \clist_gremove_all:cn}
+% \UnitTested
+% \begin{macro}{\@@_remove_all:NNNn}
+% \begin{macro}{\@@_remove_all:w}
+% \begin{macro}{\@@_remove_all:}
+% The method used here for safe items is very similar to
+% \cs{tl_replace_all:Nnn}. However, if the item contains commas or
+% leading/trailing spaces, or is empty, or consists of a single brace
+% group, we know that it can only appear within braces so the code
+% would fail; instead just convert to a sequence and do the removal
+% with \pkg{l3seq} code (it involves somewhat elaborate code to do
+% most of the work expandably but the final token list comparisons
+% non-expandably).
+%
+% For \enquote{safe} items, build a function delimited by the
+% \meta{item} that should be removed,
+% surrounded with commas, and call that function followed by
+% the expanded comma list, and another copy of the \meta{item}.
+% The loop is controlled by the argument grabbed by
+% \cs{@@_remove_all:w}: when the item was found,
+% the \cs{q_mark} delimiter used is the one inserted by
+% \cs{@@_tmp:w}, and \cs{use_none_delimit_by_q_stop:w}
+% is deleted. At the end, the final \meta{item} is
+% grabbed, and the argument of \cs{@@_tmp:w} contains
+% \cs{q_mark}: in that case, \cs{@@_remove_all:w}
+% removes the second \cs{q_mark} (inserted by \cs{@@_tmp:w}),
+% and lets \cs{use_none_delimit_by_q_stop:w} act.
+%
+% No brace is lost because items are always grabbed with a leading comma.
+% The result of the first assignment has an extra leading comma,
+% which we remove in a second assignment.
+% Two exceptions: if the clist lost all of its elements, the result
+% is empty, and we shouldn't remove anything; if the clist started up
+% empty, the first step happens to turn it into a single comma, and
+% the second step removes it.
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_remove_all:Nn
+ { \@@_remove_all:NNNn \clist_set_from_seq:NN \tl_set:Nx }
+\cs_new_protected:Npn \clist_gremove_all:Nn
+ { \@@_remove_all:NNNn \clist_gset_from_seq:NN \tl_gset:Nx }
+\cs_new_protected:Npn \@@_remove_all:NNNn #1#2#3#4
+ {
+ \@@_if_wrap:nTF {#4}
+ {
+ \seq_set_from_clist:NN \l_@@_internal_remove_seq #3
+ \seq_remove_all:Nn \l_@@_internal_remove_seq {#4}
+ #1 #3 \l_@@_internal_remove_seq
+ }
+ {
+ \cs_set:Npn \@@_tmp:w ##1 , #4 ,
+ {
+ ##1
+ , \q_mark , \use_none_delimit_by_q_stop:w ,
+ \@@_remove_all:
+ }
+ #2 #3
+ {
+ \exp_after:wN \@@_remove_all:
+ #3 , \q_mark , #4 , \q_stop
+ }
+ \clist_if_empty:NF #3
+ {
+ #2 #3
+ {
+ \exp_args:No \exp_not:o
+ { \exp_after:wN \use_none:n #3 }
+ }
+ }
+ }
+ }
+\cs_new:Npn \@@_remove_all:
+ { \exp_after:wN \@@_remove_all:w \@@_tmp:w , }
+\cs_new:Npn \@@_remove_all:w #1 , \q_mark , #2 , { \exp_not:n {#1} }
+\cs_generate_variant:Nn \clist_remove_all:Nn { c }
+\cs_generate_variant:Nn \clist_gremove_all:Nn { c }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}
+% {
+% \clist_reverse:N, \clist_reverse:c,
+% \clist_greverse:N, \clist_greverse:c
+% }
+% Use \cs{clist_reverse:n} in an \texttt{x}-expanding assignment. The
+% extra work that \cs{clist_reverse:n} does to preserve braces and
+% spaces would not be needed for the well-controlled case of
+% \texttt{N}-type comma lists, but the slow-down is not too bad.
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_reverse:N #1
+ { \tl_set:Nx #1 { \exp_args:No \clist_reverse:n {#1} } }
+\cs_new_protected:Npn \clist_greverse:N #1
+ { \tl_gset:Nx #1 { \exp_args:No \clist_reverse:n {#1} } }
+\cs_generate_variant:Nn \clist_reverse:N { c }
+\cs_generate_variant:Nn \clist_greverse:N { c }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\clist_reverse:n}
+% \begin{macro}[EXP]{\@@_reverse:wwNww, \@@_reverse_end:ww}
+% The reversed token list is built one item at a time, and stored
+% between \cs{q_stop} and \cs{q_mark}, in the form of |?| followed by
+% zero or more instances of \enquote{\meta{item}\texttt{,}}. We start from a comma
+% list \enquote{\meta{item_1}\texttt{,\ldots,}\meta{item_n}}. During the loop,
+% the auxiliary \cs{@@_reverse:wwNww} receives \enquote{\texttt{?}\meta{item_i}} as
+% |#1|, \enquote{\meta{item_{i+1}}\texttt{,\ldots,}\meta{item_n}} as |#2|,
+% \cs{@@_reverse:wwNww} as |#3|, what remains until \cs{q_stop} as
+% |#4|, and \enquote{\meta{item_{i-1}}\texttt{,\ldots,}\meta{item_1}\texttt{,}} as |#5|.
+% The auxiliary moves |#1| just before |#5|, with a comma, and calls
+% itself (|#3|). After the last item is moved, \cs{@@_reverse:wwNww}
+% receives \enquote{\cs{q_mark} \cs{@@_reverse:wwNww} \texttt{!}} as its argument
+% |#1|, thus \cs{@@_reverse_end:ww} as its argument |#3|. This second
+% auxiliary cleans up until the marker~|!|, removes the trailing comma
+% (introduced when the first item was moved after \cs{q_stop}), and
+% leaves its argument~|#1| within \cs{exp_not:n}. There is also a
+% need to remove a leading comma, hence \cs{exp_not:o} and
+% \cs{use_none:n}.
+% \begin{macrocode}
+\cs_new:Npn \clist_reverse:n #1
+ {
+ \@@_reverse:wwNww ? #1 ,
+ \q_mark \@@_reverse:wwNww ! ,
+ \q_mark \@@_reverse_end:ww
+ \q_stop ? \q_mark
+ }
+\cs_new:Npn \@@_reverse:wwNww
+ #1 , #2 \q_mark #3 #4 \q_stop ? #5 \q_mark
+ { #3 ? #2 \q_mark #3 #4 \q_stop #1 , #5 \q_mark }
+\cs_new:Npn \@@_reverse_end:ww #1 ! #2 , \q_mark
+ { \exp_not:o { \use_none:n #2 } }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}
+% {\clist_sort:Nn, \clist_sort:cn, \clist_gsort:Nn, \clist_gsort:cn}
+% Implemented in \pkg{l3sort}.
+% \end{macro}
+%
+% \subsection{Comma list conditionals}
+%
+% \begin{macro}[pTF]{\clist_if_empty:N, \clist_if_empty:c}
+% \UnitTested
+% Simple copies from the token list variable material.
+% \begin{macrocode}
+\prg_new_eq_conditional:NNn \clist_if_empty:N \tl_if_empty:N
+ { p , T , F , TF }
+\prg_new_eq_conditional:NNn \clist_if_empty:c \tl_if_empty:c
+ { p , T , F , TF }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP, pTF]{\clist_if_empty:n}
+% \begin{macro}[EXP]{\@@_if_empty_n:w}
+% \begin{macro}[EXP]{\@@_if_empty_n:wNw}
+% As usual, we insert a token (here |?|) before grabbing
+% any argument: this avoids losing braces. The argument
+% of \cs{tl_if_empty:oTF} is empty if |#1| is |?| followed
+% by blank spaces (besides, this particular variant of
+% the emptiness test is optimized). If the item of the
+% comma list is blank, grab the next one. As soon as one
+% item is non-blank, exit: the second auxiliary grabs
+% \cs{prg_return_false:} as |#2|, unless every item in
+% the comma list was blank and the loop actually got broken
+% by the trailing |\q_mark \prg_return_false:| item.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \clist_if_empty:n #1 { p , T , F , TF }
+ {
+ \@@_if_empty_n:w ? #1
+ , \q_mark \prg_return_false:
+ , \q_mark \prg_return_true:
+ \q_stop
+ }
+\cs_new:Npn \@@_if_empty_n:w #1 ,
+ {
+ \tl_if_empty:oTF { \use_none:nn #1 ? }
+ { \@@_if_empty_n:w ? }
+ { \@@_if_empty_n:wNw }
+ }
+\cs_new:Npn \@@_if_empty_n:wNw #1 \q_mark #2#3 \q_stop {#2}
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[TF]
+% {
+% \clist_if_in:Nn, \clist_if_in:NV, \clist_if_in:No,
+% \clist_if_in:cn, \clist_if_in:cV, \clist_if_in:co,
+% \clist_if_in:nn, \clist_if_in:nV, \clist_if_in:no
+% }
+% \begin{macro}{\@@_if_in_return:nnN}
+% \UnitTested
+% For \enquote{safe} items, we simply surround the comma list, and the
+% item, with commas, then use the same code as for \cs{tl_if_in:Nn}.
+% For \enquote{unsafe} items we follow the same route as
+% \cs{seq_if_in:Nn}, mapping through the list a comparison function.
+% If found, return \texttt{true} and remove \cs{prg_return_false:}.
+% \begin{macrocode}
+\prg_new_protected_conditional:Npnn \clist_if_in:Nn #1#2 { T , F , TF }
+ {
+ \exp_args:No \@@_if_in_return:nnN #1 {#2} #1
+ }
+\prg_new_protected_conditional:Npnn \clist_if_in:nn #1#2 { T , F , TF }
+ {
+ \clist_set:Nn \l_@@_internal_clist {#1}
+ \exp_args:No \@@_if_in_return:nnN \l_@@_internal_clist {#2}
+ \l_@@_internal_clist
+ }
+\cs_new_protected:Npn \@@_if_in_return:nnN #1#2#3
+ {
+ \@@_if_wrap:nTF {#2}
+ {
+ \cs_set:Npx \@@_tmp:w ##1
+ {
+ \exp_not:N \tl_if_eq:nnT {##1}
+ \exp_not:n
+ {
+ {#2}
+ { \clist_map_break:n { \prg_return_true: \use_none:n } }
+ }
+ }
+ \clist_map_function:NN #3 \@@_tmp:w
+ \prg_return_false:
+ }
+ {
+ \cs_set:Npn \@@_tmp:w ##1 ,#2, { }
+ \tl_if_empty:oTF
+ { \@@_tmp:w ,#1, {} {} ,#2, }
+ { \prg_return_false: } { \prg_return_true: }
+ }
+ }
+\prg_generate_conditional_variant:Nnn \clist_if_in:Nn
+ { NV , No , c , cV , co } { T , F , TF }
+\prg_generate_conditional_variant:Nnn \clist_if_in:nn
+ { nV , no } { T , F , TF }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Mapping to comma lists}
+%
+% \begin{macro}{\clist_map_function:NN, \clist_map_function:cN}
+% \UnitTested
+% \begin{macro}{\@@_map_function:Nw}
+% If the variable is empty, the mapping is skipped (otherwise,
+% that comma-list would be seen as consisting of one empty item).
+% Then loop over the comma-list, grabbing one comma-delimited
+% item at a time. The end is marked by \cs{q_recursion_tail}.
+% The auxiliary function \cs{@@_map_function:Nw} is also used
+% in \cs{clist_map_inline:Nn}.
+% \begin{macrocode}
+\cs_new:Npn \clist_map_function:NN #1#2
+ {
+ \clist_if_empty:NF #1
+ {
+ \exp_last_unbraced:NNo \@@_map_function:Nw #2 #1
+ , \q_recursion_tail ,
+ \prg_break_point:Nn \clist_map_break: { }
+ }
+ }
+\cs_new:Npn \@@_map_function:Nw #1#2 ,
+ {
+ \quark_if_recursion_tail_break:nN {#2} \clist_map_break:
+ #1 {#2}
+ \@@_map_function:Nw #1
+ }
+\cs_generate_variant:Nn \clist_map_function:NN { c }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\clist_map_function:nN}
+% \UnitTested
+% \begin{macro}{\@@_map_function_n:Nn}
+% \begin{macro}{\@@_map_unbrace:Nw}
+% The \texttt{n}-type mapping function is a bit more awkward,
+% since spaces must be trimmed from each item.
+% Space trimming is again based on \cs{@@_trim_next:w}.
+% The auxiliary \cs{@@_map_function_n:Nn} receives as arguments the
+% function, and the next non-empty item (after space trimming but
+% before brace removal). One level of braces is removed by
+% \cs{@@_map_unbrace:Nw}.
+% \begin{macrocode}
+\cs_new:Npn \clist_map_function:nN #1#2
+ {
+ \exp_after:wN \@@_map_function_n:Nn \exp_after:wN #2
+ \exp:w \@@_trim_next:w \prg_do_nothing: #1 , \q_recursion_tail ,
+ \prg_break_point:Nn \clist_map_break: { }
+ }
+\cs_new:Npn \@@_map_function_n:Nn #1 #2
+ {
+ \quark_if_recursion_tail_break:nN {#2} \clist_map_break:
+ \@@_map_unbrace:Nw #1 #2,
+ \exp_after:wN \@@_map_function_n:Nn \exp_after:wN #1
+ \exp:w \@@_trim_next:w \prg_do_nothing:
+ }
+\cs_new:Npn \@@_map_unbrace:Nw #1 #2, { #1 {#2} }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\clist_map_inline:Nn, \clist_map_inline:cn}
+% \UnitTested
+% \begin{macro}{\clist_map_inline:nn}
+% \UnitTested
+% Inline mapping is done by creating a suitable function
+% \enquote{on the fly}: this is done globally to avoid
+% any issues with \TeX{}'s groups. We use a different
+% function for each level of nesting.
+%
+% Since the mapping is non-expandable, we can perform
+% the space-trimming needed by the \texttt{n} version
+% simply by storing the comma-list in a variable. We
+% don't need a different comma-list for each nesting
+% level: the comma-list is expanded before the mapping
+% starts.
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_map_inline:Nn #1#2
+ {
+ \clist_if_empty:NF #1
+ {
+ \int_gincr:N \g__kernel_prg_map_int
+ \cs_gset_protected:cpn
+ { @@_map_ \int_use:N \g__kernel_prg_map_int :w } ##1 {#2}
+ \exp_last_unbraced:Nco \@@_map_function:Nw
+ { @@_map_ \int_use:N \g__kernel_prg_map_int :w }
+ #1 , \q_recursion_tail ,
+ \prg_break_point:Nn \clist_map_break:
+ { \int_gdecr:N \g__kernel_prg_map_int }
+ }
+ }
+\cs_new_protected:Npn \clist_map_inline:nn #1
+ {
+ \clist_set:Nn \l_@@_internal_clist {#1}
+ \clist_map_inline:Nn \l_@@_internal_clist
+ }
+\cs_generate_variant:Nn \clist_map_inline:Nn { c }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\clist_map_variable:NNn, \clist_map_variable:cNn}
+% \UnitTested
+% \begin{macro}{\clist_map_variable:nNn}
+% \begin{macro}{\@@_map_variable:Nnw}
+% As for other comma-list mappings, filter out the case of
+% an empty list. Same approach as \cs{clist_map_function:Nn},
+% additionally we store each item in the given variable.
+% As for inline mappings, space trimming for the \texttt{n}
+% variant is done by storing the comma list in a variable.
+% The quark test is done before assigning the item to the variable:
+% this avoids storing a quark which the user wouldn't expect.
+% The strange \cs{use:n} avoids unlikely problems when |#2| would
+% contain \cs{q_recursion_stop}.
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_map_variable:NNn #1#2#3
+ {
+ \clist_if_empty:NF #1
+ {
+ \exp_args:Nno \use:nn
+ { \@@_map_variable:Nnw #2 {#3} }
+ #1
+ , \q_recursion_tail , \q_recursion_stop
+ \prg_break_point:Nn \clist_map_break: { }
+ }
+ }
+\cs_new_protected:Npn \clist_map_variable:nNn #1
+ {
+ \clist_set:Nn \l_@@_internal_clist {#1}
+ \clist_map_variable:NNn \l_@@_internal_clist
+ }
+\cs_new_protected:Npn \@@_map_variable:Nnw #1#2#3,
+ {
+ \quark_if_recursion_tail_stop:n {#3}
+ \tl_set:Nn #1 {#3}
+ \use:n {#2}
+ \@@_map_variable:Nnw #1 {#2}
+ }
+\cs_generate_variant:Nn \clist_map_variable:NNn { c }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\clist_map_break:, \clist_map_break:n}
+% The break statements use the general \cs{prg_map_break:Nn} mechanism.
+% \begin{macrocode}
+\cs_new:Npn \clist_map_break:
+ { \prg_map_break:Nn \clist_map_break: { } }
+\cs_new:Npn \clist_map_break:n
+ { \prg_map_break:Nn \clist_map_break: }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clist_count:N, \clist_count:c, \clist_count:n}
+% \begin{macro}{\@@_count:n}
+% \begin{macro}{\@@_count:w}
+% Counting the items in a comma list is done using the same approach as for
+% other token count functions: turn each entry into a \texttt{+1} then use
+% integer evaluation to actually do the mathematics.
+% In the case of an \texttt{n}-type comma-list, we could of course use
+% \cs{clist_map_function:nN}, but that is very slow, because it carefully
+% removes spaces. Instead, we loop manually, and skip blank items
+% (but not |{}|, hence the extra spaces).
+% \begin{macrocode}
+\cs_new:Npn \clist_count:N #1
+ {
+ \int_eval:n
+ {
+ 0
+ \clist_map_function:NN #1 \@@_count:n
+ }
+ }
+\cs_generate_variant:Nn \clist_count:N { c }
+\cs_new:Npx \clist_count:n #1
+ {
+ \exp_not:N \int_eval:n
+ {
+ 0
+ \exp_not:N \@@_count:w \c_space_tl
+ #1 \exp_not:n { , \q_recursion_tail , \q_recursion_stop }
+ }
+ }
+\cs_new:Npn \@@_count:n #1 { + 1 }
+\cs_new:Npx \@@_count:w #1 ,
+ {
+ \exp_not:n { \exp_args:Nf \quark_if_recursion_tail_stop:n } {#1}
+ \exp_not:N \tl_if_blank:nF {#1} { + 1 }
+ \exp_not:N \@@_count:w \c_space_tl
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Using comma lists}
+%
+% \begin{macro}[EXP]{\clist_use:Nnnn, \clist_use:cnnn}
+% \begin{macro}[EXP]
+% {\@@_use:wwn, \@@_use:nwwwwnwn, \@@_use:nwwn}
+% \begin{macro}[EXP]{\clist_use:Nn, \clist_use:cn}
+% First check that the variable exists. Then count the items in the
+% comma list. If it has none, output nothing. If it has one item,
+% output that item, brace stripped (note that space-trimming has
+% already been done when the comma list was assigned). If it has two,
+% place the \meta{separator~between~two} in the middle.
+%
+% Otherwise, \cs{@@_use:nwwwwnwn} takes the following arguments; 1:
+% a \meta{separator}, 2, 3, 4: three items from the comma list (or
+% quarks), 5: the rest of the comma list, 6: a \meta{continuation}
+% function (\texttt{use_ii} or \texttt{use_iii} with its
+% \meta{separator} argument), 7: junk, and 8: the temporary result,
+% which is built in a brace group following \cs{q_stop}. The
+% \meta{separator} and the first of the three items are placed in the
+% result, then we use the \meta{continuation}, placing the remaining
+% two items after it. When we begin this loop, the three items really
+% belong to the comma list, the first \cs{q_mark} is taken as a
+% delimiter to the \texttt{use_ii} function, and the continuation is
+% \texttt{use_ii} itself. When we reach the last two items of the
+% original token list, \cs{q_mark} is taken as a third item, and now
+% the second \cs{q_mark} serves as a delimiter to \texttt{use_ii},
+% switching to the other \meta{continuation}, \texttt{use_iii}, which
+% uses the \meta{separator between final two}.
+% \begin{macrocode}
+\cs_new:Npn \clist_use:Nnnn #1#2#3#4
+ {
+ \clist_if_exist:NTF #1
+ {
+ \int_case:nnF { \clist_count:N #1 }
+ {
+ { 0 } { }
+ { 1 } { \exp_after:wN \@@_use:wwn #1 , , { } }
+ { 2 } { \exp_after:wN \@@_use:wwn #1 , {#2} }
+ }
+ {
+ \exp_after:wN \@@_use:nwwwwnwn
+ \exp_after:wN { \exp_after:wN } #1 ,
+ \q_mark , { \@@_use:nwwwwnwn {#3} }
+ \q_mark , { \@@_use:nwwn {#4} }
+ \q_stop { }
+ }
+ }
+ {
+ \__kernel_msg_expandable_error:nnn
+ { kernel } { bad-variable } {#1}
+ }
+ }
+\cs_generate_variant:Nn \clist_use:Nnnn { c }
+\cs_new:Npn \@@_use:wwn #1 , #2 , #3 { \exp_not:n { #1 #3 #2 } }
+\cs_new:Npn \@@_use:nwwwwnwn
+ #1#2 , #3 , #4 , #5 \q_mark , #6#7 \q_stop #8
+ { #6 {#3} , {#4} , #5 \q_mark , {#6} #7 \q_stop { #8 #1 #2 } }
+\cs_new:Npn \@@_use:nwwn #1#2 , #3 \q_stop #4
+ { \exp_not:n { #4 #1 #2 } }
+\cs_new:Npn \clist_use:Nn #1#2
+ { \clist_use:Nnnn #1 {#2} {#2} {#2} }
+\cs_generate_variant:Nn \clist_use:Nn { c }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Using a single item}
+%
+% \begin{macro}{\clist_item:Nn, \clist_item:cn}
+% \begin{macro}{\@@_item:nnnN, \@@_item:ffoN, \@@_item:ffnN}
+% \begin{macro}{\@@_item_N_loop:nw}
+% To avoid needing to test the end of the list at each step,
+% we first compute the \meta{length} of the list. If the item number
+% is~$0$, less than $-\meta{length}$, or more than $\meta{length}$,
+% the result is empty. If it is negative, but not less than $-\meta{length}$,
+% add $\meta{length}+1$ to the item number before performing the loop.
+% The loop itself is very simple, return the item if the counter
+% reached~$1$, otherwise, decrease the counter and repeat.
+% \begin{macrocode}
+\cs_new:Npn \clist_item:Nn #1#2
+ {
+ \@@_item:ffoN
+ { \clist_count:N #1 }
+ { \int_eval:n {#2} }
+ #1
+ \@@_item_N_loop:nw
+ }
+\cs_new:Npn \@@_item:nnnN #1#2#3#4
+ {
+ \int_compare:nNnTF {#2} < 0
+ {
+ \int_compare:nNnTF {#2} < { - #1 }
+ { \use_none_delimit_by_q_stop:w }
+ { \exp_args:Nf #4 { \int_eval:n { #2 + 1 + #1 } } }
+ }
+ {
+ \int_compare:nNnTF {#2} > {#1}
+ { \use_none_delimit_by_q_stop:w }
+ { #4 {#2} }
+ }
+ { } , #3 , \q_stop
+ }
+\cs_generate_variant:Nn \@@_item:nnnN { ffo, ff }
+\cs_new:Npn \@@_item_N_loop:nw #1 #2,
+ {
+ \int_compare:nNnTF {#1} = 0
+ { \use_i_delimit_by_q_stop:nw { \exp_not:n {#2} } }
+ { \exp_args:Nf \@@_item_N_loop:nw { \int_eval:n { #1 - 1 } } }
+ }
+\cs_generate_variant:Nn \clist_item:Nn { c }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\clist_item:nn}
+% \begin{macro}{
+% \@@_item_n:nw,
+% \@@_item_n_loop:nw,
+% \@@_item_n_end:n,
+% \@@_item_n_strip:n,
+% \@@_item_n_strip:w}
+% This starts in the same way as \cs{clist_item:Nn} by counting the items
+% of the comma list. The final item should be space-trimmed before being
+% brace-stripped, hence we insert a couple of odd-looking
+% \cs{prg_do_nothing:} to avoid losing braces. Blank items are ignored.
+% \begin{macrocode}
+\cs_new:Npn \clist_item:nn #1#2
+ {
+ \@@_item:ffnN
+ { \clist_count:n {#1} }
+ { \int_eval:n {#2} }
+ {#1}
+ \@@_item_n:nw
+ }
+\cs_new:Npn \@@_item_n:nw #1
+ { \@@_item_n_loop:nw {#1} \prg_do_nothing: }
+\cs_new:Npn \@@_item_n_loop:nw #1 #2,
+ {
+ \exp_args:No \tl_if_blank:nTF {#2}
+ { \@@_item_n_loop:nw {#1} \prg_do_nothing: }
+ {
+ \int_compare:nNnTF {#1} = 0
+ { \exp_args:No \@@_item_n_end:n {#2} }
+ {
+ \exp_args:Nf \@@_item_n_loop:nw
+ { \int_eval:n { #1 - 1 } }
+ \prg_do_nothing:
+ }
+ }
+ }
+\cs_new:Npn \@@_item_n_end:n #1 #2 \q_stop
+ { \tl_trim_spaces_apply:nN {#1} \@@_item_n_strip:n }
+\cs_new:Npn \@@_item_n_strip:n #1 { \@@_item_n_strip:w #1 , }
+\cs_new:Npn \@@_item_n_strip:w #1 , { \exp_not:n {#1} }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\clist_rand_item:n, \clist_rand_item:N, \clist_rand_item:c}
+% \begin{macro}{\@@_rand_item:nn}
+% The |N|-type function is not implemented through the |n|-type
+% function for efficiency: for instance comma-list variables do not
+% require space-trimming of their items. Even testing for emptyness
+% of an |n|-type comma-list is slow, so we count items first and use
+% that both for the emptyness test and the pseudo-random integer.
+% Importantly, \cs{clist_item:Nn} and \cs{clist_item:nn} only evaluate
+% their argument once.
+% \begin{macrocode}
+\cs_new:Npn \clist_rand_item:n #1
+ { \exp_args:Nf \@@_rand_item:nn { \clist_count:n {#1} } {#1} }
+\cs_new:Npn \@@_rand_item:nn #1#2
+ {
+ \int_compare:nNnF {#1} = 0
+ { \clist_item:nn {#2} { \int_rand:nn { 1 } {#1} } }
+ }
+\cs_new:Npn \clist_rand_item:N #1
+ {
+ \clist_if_empty:NF #1
+ { \clist_item:Nn #1 { \int_rand:nn { 1 } { \clist_count:N #1 } } }
+ }
+\cs_generate_variant:Nn \clist_rand_item:N { c }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Viewing comma lists}
+%
+% \begin{macro}{\clist_show:N, \clist_show:c, \clist_log:N, \clist_log:c, \@@_show:NN}
+% Apply the general \cs{__kernel_chk_defined:NT} and
+% \cs{msg_show:nnnnnn}.
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_show:N { \@@_show:NN \msg_show:nnxxxx }
+\cs_generate_variant:Nn \clist_show:N { c }
+\cs_new_protected:Npn \clist_log:N { \@@_show:NN \msg_log:nnxxxx }
+\cs_generate_variant:Nn \clist_log:N { c }
+\cs_new_protected:Npn \@@_show:NN #1#2
+ {
+ \__kernel_chk_defined:NT #2
+ {
+ #1 { LaTeX/kernel } { show-clist }
+ { \token_to_str:N #2 }
+ { \clist_map_function:NN #2 \msg_show_item:n }
+ { } { }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clist_show:n, \clist_log:n, \@@_show:Nn}
+% A variant of the above: no existence check, empty first argument for
+% the message.
+% \begin{macrocode}
+\cs_new_protected:Npn \clist_show:n { \@@_show:Nn \msg_show:nnxxxx }
+\cs_new_protected:Npn \clist_log:n { \@@_show:Nn \msg_log:nnxxxx }
+\cs_new_protected:Npn \@@_show:Nn #1#2
+ {
+ #1 { LaTeX/kernel } { show-clist }
+ { } { \clist_map_function:nN {#2} \msg_show_item:n } { } { }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Scratch comma lists}
+%
+% \begin{variable}{\l_tmpa_clist, \l_tmpb_clist}
+% \begin{variable}{\g_tmpa_clist, \g_tmpb_clist}
+% Temporary comma list variables.
+% \begin{macrocode}
+\clist_new:N \l_tmpa_clist
+\clist_new:N \l_tmpb_clist
+\clist_new:N \g_tmpa_clist
+\clist_new:N \g_tmpb_clist
+% \end{macrocode}
+% \end{variable}
+% \end{variable}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex