From e0c6872cf40896c7be36b11dcc744620f10adf1d Mon Sep 17 00:00:00 2001
From: Norbert Preining <norbert@preining.info>
Date: Mon, 2 Sep 2019 13:46:59 +0900
Subject: Initial commit

---
 macros/latex/contrib/l3kernel/l3keys.dtx | 2831 ++++++++++++++++++++++++++++++
 1 file changed, 2831 insertions(+)
 create mode 100644 macros/latex/contrib/l3kernel/l3keys.dtx

(limited to 'macros/latex/contrib/l3kernel/l3keys.dtx')

diff --git a/macros/latex/contrib/l3kernel/l3keys.dtx b/macros/latex/contrib/l3kernel/l3keys.dtx
new file mode 100644
index 0000000000..074ca85b6e
--- /dev/null
+++ b/macros/latex/contrib/l3kernel/l3keys.dtx
@@ -0,0 +1,2831 @@
+% \iffalse meta-comment
+%
+%% File: l3keys.dtx
+%
+% Copyright (C) 2006-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{l3keys} package\\ Key--value interfaces^^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}
+%
+% The key--value method is a popular system for creating large numbers
+% of settings for controlling function or package behaviour.  The
+% system normally results in input of the form
+% \begin{verbatim}
+%   \MyModuleSetup{
+%     key-one = value one,
+%     key-two = value two
+%   }
+% \end{verbatim}
+% or
+% \begin{verbatim}
+%   \MyModuleMacro[
+%     key-one = value one,
+%     key-two = value two
+%   ]{argument}
+% \end{verbatim}
+% for the user.
+%
+% The high level functions here are intended as a method to create
+% key--value controls. Keys are themselves created using a key--value
+% interface, minimising the number of functions and arguments
+% required. Each key is created by setting one or more \emph{properties}
+% of the key:
+% \begin{verbatim}
+%   \keys_define:nn { mymodule }
+%     {
+%       key-one .code:n   = code including parameter #1,
+%       key-two .tl_set:N = \l_mymodule_store_tl
+%     }
+% \end{verbatim}
+% These values can then be set as with other key--value approaches:
+% \begin{verbatim}
+%   \keys_set:nn { mymodule }
+%     {
+%       key-one = value one,
+%       key-two = value two
+%     }
+% \end{verbatim}
+%
+% At a document level, \cs{keys_set:nn} is used within a
+% document function, for example
+% \begin{verbatim}
+%   \DeclareDocumentCommand \MyModuleSetup { m }
+%     { \keys_set:nn { mymodule } { #1 }  }
+%   \DeclareDocumentCommand \MyModuleMacro { o m }
+%     {
+%       \group_begin:
+%         \keys_set:nn { mymodule } { #1 }
+%         % Main code for \MyModuleMacro
+%       \group_end:
+%     }
+% \end{verbatim}
+%
+% Key names may contain any tokens, as they are handled internally
+% using \cs{tl_to_str:n}. As discussed in
+% section~\ref{sec:l3keys:subdivision}, it is suggested that the character
+% |/| is reserved for sub-division of keys into logical
+% groups. Functions and variables are \emph{not} expanded when creating
+% key names, and so
+% \begin{verbatim}
+%   \tl_set:Nn \l_mymodule_tmp_tl { key }
+%   \keys_define:nn { mymodule }
+%     {
+%       \l_mymodule_tmp_tl .code:n = code
+%     }
+% \end{verbatim}
+% creates a key called |\l_mymodule_tmp_tl|, and not one called
+% \texttt{key}.
+%
+% \section{Creating keys}
+%
+% \begin{function}[updated = 2017-11-14]{\keys_define:nn}
+%   \begin{syntax}
+%     \cs{keys_define:nn} \Arg{module} \Arg{keyval list}
+%   \end{syntax}
+%   Parses the \meta{keyval list} and defines the keys listed there for
+%   \meta{module}. The \meta{module} name is treated as a string.
+%   In practice the
+%   \meta{module} should be chosen to be unique to the module in question
+%   (unless deliberately adding keys to an existing module).
+%
+%   The \meta{keyval list} should consist of one or more key names along
+%   with an associated key \emph{property}. The properties of a key
+%   determine how it acts. The individual properties are described
+%   in the following text; a typical use of \cs{keys_define:nn} might
+%   read
+%   \begin{verbatim}
+%     \keys_define:nn { mymodule }
+%       {
+%         keyname .code:n = Some~code~using~#1,
+%         keyname .value_required:n = true
+%       }
+%   \end{verbatim}
+%   where the properties of the key begin from the |.| after the key
+%   name.
+% \end{function}
+%
+% The various properties available take either no arguments at
+% all, or require one or more arguments. This is indicated in the
+% name of the property using an argument specification. In the following
+% discussion, each property is illustrated attached to an
+% arbitrary \meta{key}, which when used may be supplied with a
+% \meta{value}. All key \emph{definitions} are local.
+%
+% Key properties are applied in the reading order and so the ordering
+% is significant. Key properties which define \enquote{actions}, such
+% as |.code:n|, |.tl_set:N|, \emph{etc.}, override one another.
+% Some other properties are mutually exclusive, notably |.value_required:n|
+% and |.value_forbidden:n|, and so they replace one another. However,
+% properties covering non-exclusive behaviours may be given in any order. Thus
+% for example the following definitions are equivalent.
+% \begin{verbatim}
+%   \keys_define:nn { mymodule }
+%     {
+%       keyname .code:n           = Some~code~using~#1,
+%       keyname .value_required:n = true
+%     }
+%   \keys_define:nn { mymodule }
+%     {
+%       keyname .value_required:n = true,
+%       keyname .code:n           = Some~code~using~#1
+%     }
+% \end{verbatim}
+% Note that with the exception of the special |.undefine:| property, all
+% key properties define the key within the current \TeX{} scope.
+%
+% \begin{function}[updated = 2013-07-08]
+%   {.bool_set:N, .bool_set:c, .bool_gset:N, .bool_gset:c}
+%   \begin{syntax}
+%     \meta{key} .bool_set:N = \meta{boolean}
+%   \end{syntax}
+%   Defines \meta{key} to set \meta{boolean} to \meta{value} (which
+%   must be either \texttt{true} or \texttt{false}).  If the variable
+%   does not exist, it will be created globally at the point that
+%   the key is set up.
+% \end{function}
+%
+% \begin{function}[added = 2011-08-28, updated = 2013-07-08]
+%   {
+%     .bool_set_inverse:N, .bool_set_inverse:c,
+%     .bool_gset_inverse:N, .bool_gset_inverse:c
+%   }
+%   \begin{syntax}
+%     \meta{key} .bool_set_inverse:N = \meta{boolean}
+%   \end{syntax}
+%   Defines \meta{key} to set \meta{boolean} to the logical
+%   inverse of \meta{value} (which  must be either \texttt{true} or
+%   \texttt{false}).
+%   If the \meta{boolean} does not exist, it will be created globally
+%   at the point that the key is set up.
+% \end{function}
+%
+% \begin{function}{.choice:}
+%   \begin{syntax}
+%     \meta{key} .choice:
+%   \end{syntax}
+%   Sets \meta{key} to act as a choice key. Each valid choice
+%   for \meta{key} must then be created, as discussed in
+%   section~\ref{sec:l3keys:choice}.
+% \end{function}
+%
+% \begin{function}[added = 2011-08-21, updated = 2013-07-10]
+%   {.choices:nn, .choices:Vn, .choices:on, .choices:xn}
+%   \begin{syntax}
+%     \meta{key} .choices:nn = \Arg{choices} \Arg{code}
+%   \end{syntax}
+%   Sets \meta{key} to act as a choice key, and defines a series \meta{choices}
+%   which are implemented using the \meta{code}. Inside \meta{code},
+%   \cs{l_keys_choice_tl} will be the name of the choice made, and
+%   \cs{l_keys_choice_int} will be the position of the choice in the list
+%   of \meta{choices} (indexed from~$1$).
+%   Choices are discussed in detail in section~\ref{sec:l3keys:choice}.
+% \end{function}
+%
+% \begin{function}[added = 2011-09-11]
+%   {.clist_set:N, .clist_set:c, .clist_gset:N, .clist_gset:c}
+%   \begin{syntax}
+%     \meta{key} .clist_set:N = \meta{comma list variable}
+%   \end{syntax}
+%   Defines \meta{key} to set \meta{comma list variable} to \meta{value}.
+%   Spaces around commas and empty items will be stripped.
+%   If the variable does not exist, it
+%   is created globally at the point that the key is set up.
+% \end{function}
+%
+% \begin{function}[updated = 2013-07-10]{.code:n}
+%   \begin{syntax}
+%     \meta{key} .code:n = \Arg{code}
+%   \end{syntax}
+%   Stores the \meta{code} for execution when \meta{key} is used.
+%   The \meta{code} can include one parameter (|#1|), which will be the
+%   \meta{value} given for the \meta{key}.
+% \end{function}
+%
+% \begin{function}[updated = 2013-07-09]
+%   {.default:n, .default:V, .default:o, .default:x}
+%   \begin{syntax}
+%     \meta{key} .default:n = \Arg{default}
+%   \end{syntax}
+%   Creates a \meta{default} value for \meta{key}, which is used if no
+%   value is given. This will be used if only the key name is given,
+%   but not if a blank \meta{value} is given:
+%   \begin{verbatim}
+%     \keys_define:nn { mymodule }
+%       {
+%         key .code:n    = Hello~#1,
+%         key .default:n = World
+%       }
+%     \keys_set:nn { mymodule }
+%       {
+%         key = Fred, % Prints 'Hello Fred'
+%         key,        % Prints 'Hello World'
+%         key = ,     % Prints 'Hello '
+%       }
+%   \end{verbatim}
+%   The default does not affect keys where values are required or
+%   forbidden. Thus a required value cannot be supplied by a default
+%   value, and giving a default value for a key which cannot take a value
+%   does not trigger an error.
+% \end{function}
+%
+% \begin{function}{.dim_set:N, .dim_set:c, .dim_gset:N, .dim_gset:c}
+%   \begin{syntax}
+%     \meta{key} .dim_set:N = \meta{dimension}
+%   \end{syntax}
+%   Defines \meta{key} to set \meta{dimension} to \meta{value} (which
+%   must a dimension expression).  If the variable does not exist, it
+%   is created globally at the point that the key is set up.
+% \end{function}
+%
+% \begin{function}{.fp_set:N, .fp_set:c, .fp_gset:N, .fp_gset:c}
+%   \begin{syntax}
+%     \meta{key} .fp_set:N = \meta{floating point}
+%   \end{syntax}
+%   Defines \meta{key} to set \meta{floating point} to \meta{value}
+%   (which must a floating point expression).  If the variable does not exist,
+%   it is created globally at the point that the key is set up.
+% \end{function}
+%
+% \begin{function}[added = 2013-07-14]
+%   {.groups:n}
+%   \begin{syntax}
+%     \meta{key} .groups:n = \Arg{groups}
+%   \end{syntax}
+%   Defines \meta{key} as belonging to the \meta{groups} declared. Groups
+%   provide a \enquote{secondary axis} for selectively setting keys, and are
+%   described in Section~\ref{sec:l3keys:selective}.
+% \end{function}
+%
+% \begin{function}[added = 2016-11-22]{.inherit:n}
+%   \begin{syntax}
+%     \meta{key} .inherit:n = \Arg{parents}
+%   \end{syntax}
+%   Specifies that the \meta{key} path should inherit the keys listed
+%   as \meta{parents}. For example, after setting
+%   \begin{verbatim}
+%     \keys_define:nn { foo } { test .code:n = \tl_show:n {#1} }
+%     \keys_define:nn { } { bar .inherit:n = foo }
+%   \end{verbatim}
+%   setting
+%   \begin{verbatim}
+%     \keys_set:nn { bar } { test = a }
+%   \end{verbatim}
+%   will be equivalent to
+%   \begin{verbatim}
+%     \keys_set:nn { foo } { test = a }
+%   \end{verbatim}
+% \end{function}
+%
+% \begin{function}[updated = 2013-07-09]
+%   {.initial:n, .initial:V, .initial:o, .initial:x}
+%   \begin{syntax}
+%     \meta{key} .initial:n = \Arg{value}
+%   \end{syntax}
+%   Initialises the \meta{key} with the \meta{value}, equivalent to
+%   \begin{quote}
+%     \cs{keys_set:nn} \Arg{module} \{ \meta{key} = \meta{value} \}
+%   \end{quote}
+% \end{function}
+%
+% \begin{function}{.int_set:N, .int_set:c, .int_gset:N, .int_gset:c}
+%   \begin{syntax}
+%     \meta{key} .int_set:N = \meta{integer}
+%   \end{syntax}
+%   Defines \meta{key} to set \meta{integer} to \meta{value} (which
+%   must be an integer expression).  If the variable does not exist, it
+%   is created globally at the point that the key is set up.
+% \end{function}
+%
+% \begin{function}[updated = 2013-07-10]{.meta:n}
+%   \begin{syntax}
+%     \meta{key} .meta:n = \Arg{keyval list}
+%   \end{syntax}
+%   Makes \meta{key} a meta-key, which will set \meta{keyval list} in
+%   one go.  The \meta{keyval list} can refer as |#1| to the value given
+%   at the time the \meta{key} is used (or, if no value is given, the
+%   \meta{key}'s default value).
+% \end{function}
+%
+% \begin{function}[added = 2013-07-10]{.meta:nn}
+%   \begin{syntax}
+%     \meta{key} .meta:nn = \Arg{path} \Arg{keyval list}
+%   \end{syntax}
+%   Makes \meta{key} a meta-key, which will set \meta{keyval list} in
+%   one go using the \meta{path} in place of the current one.  The
+%   \meta{keyval list} can refer as |#1| to the value given at the time
+%   the \meta{key} is used (or, if no value is given, the \meta{key}'s
+%   default value).
+% \end{function}
+%
+% \begin{function}[added = 2011-08-21]{.multichoice:}
+%   \begin{syntax}
+%     \meta{key} .multichoice:
+%   \end{syntax}
+%   Sets \meta{key} to act as a multiple choice key. Each valid choice
+%   for \meta{key} must then be created, as discussed in
+%   section~\ref{sec:l3keys:choice}.
+% \end{function}
+%
+% \begin{function}[added = 2011-08-21, updated = 2013-07-10]
+%   {.multichoices:nn, .multichoices:Vn, .multichoices:on, .multichoices:xn}
+%   \begin{syntax}
+%     \meta{key} .multichoices:nn \Arg{choices} \Arg{code}
+%   \end{syntax}
+%   Sets \meta{key} to act as a multiple choice key, and defines a series
+%   \meta{choices}
+%   which are implemented using the \meta{code}. Inside \meta{code},
+%   \cs{l_keys_choice_tl} will be the name of the choice made, and
+%   \cs{l_keys_choice_int} will be the position of the choice in the list
+%   of \meta{choices} (indexed from~$1$).
+%   Choices are discussed in detail in section~\ref{sec:l3keys:choice}.
+% \end{function}
+%
+% \begin{function}[added = 2019-05-05]
+%   {.muskip_set:N, .muskip_set:c, .muskip_gset:N, .muskip_gset:c}
+%   \begin{syntax}
+%     \meta{key} .muskip_set:N = \meta{muskip}
+%   \end{syntax}
+%   Defines \meta{key} to set \meta{muskip} to \meta{value} (which
+%   must be a muskip expression). If the variable does not exist, it
+%   is created globally at the point that the key is set up.
+% \end{function}
+%
+% \begin{function}[added = 2019-01-31]
+%   {.prop_put:N, .prop_put:c, .prop_gput:N, .prop_gput:c}
+%   \begin{syntax}
+%     \meta{key} .prop_put:N = \meta{property list}
+%   \end{syntax}
+%   Defines \meta{key} to put the \meta{value} onto the \meta{property list}
+%   stored under the \meta{key}.
+%   If the variable does not exist, it
+%   is created globally at the point that the key is set up.
+% \end{function}
+%
+% \begin{function}{.skip_set:N, .skip_set:c, .skip_gset:N, .skip_gset:c}
+%   \begin{syntax}
+%     \meta{key} .skip_set:N = \meta{skip}
+%   \end{syntax}
+%   Defines \meta{key} to set \meta{skip} to \meta{value} (which
+%   must be a skip expression). If the variable does not exist, it
+%   is created globally at the point that the key is set up.
+% \end{function}
+%
+% \begin{function}{.tl_set:N, .tl_set:c, .tl_gset:N, .tl_gset:c}
+%   \begin{syntax}
+%     \meta{key} .tl_set:N = \meta{token list variable}
+%   \end{syntax}
+%   Defines \meta{key} to set \meta{token list variable} to \meta{value}.
+%   If the variable does not exist, it is created globally
+%   at the point that the key is set up.
+% \end{function}
+%
+% \begin{function}{.tl_set_x:N, .tl_set_x:c, .tl_gset_x:N, .tl_gset_x:c}
+%   \begin{syntax}
+%     \meta{key} .tl_set_x:N = \meta{token list variable}
+%   \end{syntax}
+%   Defines \meta{key} to set \meta{token list variable} to \meta{value},
+%   which will be subjected to an \texttt{x}-type expansion
+%   (\emph{i.e.}~using \cs{tl_set:Nx}). If the variable does not exist,
+%   it is created globally at the point that the key is set up.
+% \end{function}
+%
+% \begin{function}[added = 2015-07-14]{.undefine:}
+%   \begin{syntax}
+%     \meta{key} .undefine:
+%   \end{syntax}
+%   Removes the definition of the \meta{key} within the current scope.
+% \end{function}
+%
+% \begin{function}[added = 2015-07-14]{.value_forbidden:n}
+%   \begin{syntax}
+%     \meta{key} .value_forbidden:n = \texttt{true\string|false}
+%   \end{syntax}
+%   Specifies that \meta{key} cannot receive a \meta{value} when used.
+%   If a \meta{value} is given then an error will be issued. Setting
+%   the property \texttt{false} cancels the restriction.
+% \end{function}
+%
+% \begin{function}[added = 2015-07-14]{.value_required:n}
+%   \begin{syntax}
+%      \meta{key} .value_required:n = \texttt{true\string|false}
+%   \end{syntax}
+%   Specifies that \meta{key} must receive a \meta{value} when used.
+%   If a \meta{value} is not given then an error will be issued. Setting
+%   the property \texttt{false} cancels the restriction.
+% \end{function}
+%
+% \section{Sub-dividing keys}
+% \label{sec:l3keys:subdivision}
+%
+% When creating large numbers of keys, it may be desirable to divide
+% them into several sub-groups for a given module. This can be achieved
+% either by adding a sub-division to the module name:
+% \begin{verbatim}
+%   \keys_define:nn { mymodule / subgroup }
+%     { key .code:n = code }
+% \end{verbatim}
+% or to the key name:
+% \begin{verbatim}
+%   \keys_define:nn { mymodule }
+%     { subgroup / key .code:n = code }
+% \end{verbatim}
+% As illustrated, the best choice of token for sub-dividing keys in
+% this way is |/|. This is because of the method that is
+% used to represent keys internally. Both of the above code fragments
+% set the same key, which has full name \texttt{mymodule/subgroup/key}.
+%
+% As illustrated in the next section, this subdivision is
+% particularly relevant to making multiple choices.
+%
+% \section{Choice and multiple choice keys}
+% \label{sec:l3keys:choice}
+%
+% The \pkg{l3keys} system supports two types of choice key, in which a series
+% of pre-defined input values are linked to varying implementations. Choice
+% keys are usually created so that the various values are mutually-exclusive:
+% only one can apply at any one time. \enquote{Multiple} choice keys are also
+% supported: these allow a selection of values to be chosen at the same time.
+%
+% Mutually-exclusive choices are created by setting the \texttt{.choice:}
+% property:
+% \begin{verbatim}
+%   \keys_define:nn { mymodule }
+%     { key .choice: }
+% \end{verbatim}
+% For keys which are set up as choices, the valid choices are generated
+% by creating sub-keys of the choice key. This can be carried out in
+% two ways.
+%
+% In many cases, choices execute similar code which is dependant only
+% on the name of the choice or the position of the choice in the
+% list of all possibilities. Here, the keys can share the same code, and can
+% be rapidly created using the  \texttt{.choices:nn} property.
+% \begin{verbatim}
+%   \keys_define:nn { mymodule }
+%     {
+%       key .choices:nn =
+%         { choice-a, choice-b, choice-c }
+%         {
+%           You~gave~choice~'\tl_use:N \l_keys_choice_tl',~
+%           which~is~in~position~\int_use:N \l_keys_choice_int \c_space_tl
+%           in~the~list.
+%         }
+%     }
+% \end{verbatim}
+% The index \cs{l_keys_choice_int} in the list of choices starts at~$1$.
+%
+% \begin{variable}{\l_keys_choice_int, \l_keys_choice_tl}
+%   Inside the code block for a choice generated using \texttt{.choices:nn},
+%   the variables \cs{l_keys_choice_tl} and \cs{l_keys_choice_int} are
+%   available to indicate the name of the current choice, and its position in
+%   the comma list.  The position is indexed from~$1$. Note that, as with
+%   standard key code generated using \texttt{.code:n}, the value passed to
+%   the key (i.e.~the choice name) is also available as |#1|.
+% \end{variable}
+%
+% On the other hand, it is sometimes useful to create choices which
+% use entirely different code from one another. This can be achieved
+% by setting the \texttt{.choice:} property of a key, then manually
+% defining sub-keys.
+% \begin{verbatim}
+%   \keys_define:nn { mymodule }
+%     {
+%       key .choice:,
+%       key / choice-a .code:n = code-a,
+%       key / choice-b .code:n = code-b,
+%       key / choice-c .code:n = code-c,
+%     }
+% \end{verbatim}
+%
+% It is possible to mix the two methods, but manually-created choices
+% should \emph{not} use \cs{l_keys_choice_tl} or \cs{l_keys_choice_int}.
+% These variables do not have defined behaviour when used outside of
+% code created using \texttt{.choices:nn}
+% (\emph{i.e.}~anything might happen).
+%
+% It is possible to allow choice keys to take values which have not previously
+% been defined by adding code for the special \texttt{unknown} choice. The
+% general behavior of the \texttt{unknown} key is described in
+% Section~\ref{sec:l3keys:unknown}. A typical example in the case of a choice
+% would be to issue a custom error message:
+% \begin{verbatim}
+%   \keys_define:nn { mymodule }
+%     {
+%       key .choice:,
+%       key / choice-a .code:n = code-a,
+%       key / choice-b .code:n = code-b,
+%       key / choice-c .code:n = code-c,
+%       key / unknown  .code:n =
+%         \msg_error:nnxxx { mymodule } { unknown-choice }
+%           { key }                              % Name of choice key
+%           { choice-a , choice-b ,  choice-c }  % Valid choices
+%           { \exp_not:n {#1} }                  % Invalid choice given
+%       %
+%       %
+%     }
+% \end{verbatim}
+%
+% Multiple choices are created in a very similar manner to mutually-exclusive
+% choices, using the properties \texttt{.multichoice:} and
+% \texttt{.multichoices:nn}. As with mutually exclusive choices, multiple
+% choices are define as sub-keys. Thus both
+% \begin{verbatim}
+%   \keys_define:nn { mymodule }
+%     {
+%       key .multichoices:nn =
+%         { choice-a, choice-b, choice-c }
+%         {
+%           You~gave~choice~'\tl_use:N \l_keys_choice_tl',~
+%           which~is~in~position~
+%           \int_use:N \l_keys_choice_int \c_space_tl
+%           in~the~list.
+%         }
+%     }
+% \end{verbatim}
+% and
+% \begin{verbatim}
+%   \keys_define:nn { mymodule }
+%     {
+%       key .multichoice:,
+%       key / choice-a .code:n = code-a,
+%       key / choice-b .code:n = code-b,
+%       key / choice-c .code:n = code-c,
+%     }
+% \end{verbatim}
+% are valid.
+%
+% When a multiple choice key is set
+% \begin{verbatim}
+%   \keys_set:nn { mymodule }
+%     {
+%       key = { a , b , c } % 'key' defined as a multiple choice
+%     }
+% \end{verbatim}
+% each choice is applied in turn, equivalent to a \texttt{clist} mapping or
+% to applying each value individually:
+% \begin{verbatim}
+%   \keys_set:nn { mymodule }
+%     {
+%       key = a ,
+%       key = b ,
+%       key = c ,
+%     }
+% \end{verbatim}
+% Thus each separate choice will have passed to it the
+% \cs{l_keys_choice_tl} and \cs{l_keys_choice_int} in exactly
+% the same way as described for \texttt{.choices:nn}.
+%
+% \section{Setting keys}
+%
+% \begin{function}[updated = 2017-11-14]
+%   {\keys_set:nn, \keys_set:nV, \keys_set:nv, \keys_set:no}
+%   \begin{syntax}
+%     \cs{keys_set:nn} \Arg{module} \Arg{keyval list}
+%   \end{syntax}
+%   Parses the \meta{keyval list}, and sets those keys which are defined
+%   for \meta{module}. The behaviour on finding an unknown key can be set
+%   by defining a special \texttt{unknown} key: this is illustrated
+%   later.
+% \end{function}
+%
+% \begin{variable}[updated = 2015-07-14]
+%   {\l_keys_key_tl, \l_keys_path_tl, \l_keys_value_tl}
+%   For each key processed, information of the full \emph{path} of the
+%   key, the \emph{name} of the key and the \emph{value} of the key is
+%   available within three token list variables. These may be used within
+%   the code of the key.
+%
+%   The \emph{value} is everything after the \texttt{=}, which may be
+%   empty if no value was given. This is stored in \cs{l_keys_value_tl}, and
+%   is not processed in any way by \cs{keys_set:nn}.
+%
+%   The \emph{path} of the key is a \enquote{full} description of the key,
+%   and is unique for each key. It consists of the module and full key name,
+%   thus for example
+%   \begin{verbatim}
+%     \keys_set:nn { mymodule } { key-a = some-value }
+%   \end{verbatim}
+%   has path \texttt{mymodule/key-a} while
+%   \begin{verbatim}
+%     \keys_set:nn { mymodule } { subset  / key-a = some-value }
+%   \end{verbatim}
+%   has path \texttt{mymodule/subset/key-a}. This information is stored in
+%   \cs{l_keys_path_tl}, and will have been processed by \cs{tl_to_str:n}.
+%
+%   The \emph{name} of the key is the part of the path after the last
+%   \texttt{/}, and thus is not unique. In the preceding examples, both keys
+%   have name \texttt{key-a} despite having different paths.  This information
+%   is stored in \cs{l_keys_key_tl}, and will have been processed by
+%   \cs{tl_to_str:n}.
+% \end{variable}
+%
+% \section{Handling of unknown keys}
+% \label{sec:l3keys:unknown}
+%
+% If a key has not previously been defined (is unknown), \cs{keys_set:nn}
+% looks for a special \texttt{unknown} key for the same module, and if this is
+% not defined raises an error indicating that the key name was unknown. This
+% mechanism can be used for example to issue custom error texts.
+% \begin{verbatim}
+%   \keys_define:nn { mymodule }
+%     {
+%       unknown .code:n =
+%         You~tried~to~set~key~'\l_keys_key_tl'~to~'#1'.
+%     }
+% \end{verbatim}
+%
+% \begin{function}[added = 2011-08-23, updated = 2019-01-29]
+%   {
+%     \keys_set_known:nn, \keys_set_known:nV,
+%     \keys_set_known:nv, \keys_set_known:no,
+%     \keys_set_known:nnN, \keys_set_known:nVN,
+%     \keys_set_known:nvN, \keys_set_known:noN,
+%     \keys_set_known:nnnN, \keys_set_known:nVnN,
+%     \keys_set_known:nvnN, \keys_set_known:nonN
+%   }
+%   \begin{syntax}
+%     \cs{keys_set_known:nn} \Arg{module} \Arg{keyval list}
+%     \cs{keys_set_known:nnN} \Arg{module} \Arg{keyval list} \meta{tl}
+%     \cs{keys_set_known:nnnN} \Arg{module} \Arg{keyval list} \Arg{root} \meta{tl}
+%   \end{syntax}
+%   These functions set keys which are known for the \meta{module}, and
+%   simply ignore other keys. The \cs{keys_set_known:nn} function parses the
+%   \meta{keyval list}, and sets those keys which are defined for
+%   \meta{module}. Any keys which are unknown are not processed further by
+%   the parser. In addition, \cs{keys_set_known:nnN} stores the key--value
+%   pairs in the \meta{tl} in comma-separated form (\emph{i.e.}~an edited
+%   version of the \meta{keyval list}). When a \meta{root} is given
+%   (\cs{keys_set_known:nnnN}), the key--value entries are returned
+%   relative to this point in the key tree. When it is absent, only the
+%   key name and value are provided. The correct list is returned by
+%   nested calls. 
+% \end{function}
+%
+% \section{Selective key setting}
+% \label{sec:l3keys:selective}
+%
+% In some cases it may be useful to be able to select only some keys for
+% setting, even though these keys have the same path. For example, with
+% a set of keys defined using
+% \begin{verbatim}
+%   \keys define:nn { mymodule }
+%     {
+%       key-one   .code:n   = { \my_func:n {#1} } ,
+%       key-two   .tl_set:N = \l_my_a_tl          ,
+%       key-three .tl_set:N = \l_my_b_tl          ,
+%       key-four  .fp_set:N = \l_my_a_fp          ,
+%     }
+% \end{verbatim}
+% the use of \cs{keys_set:nn} attempts to set all four keys. However, in
+% some contexts it may only be sensible to set some keys, or to control the
+% order of setting. To do this, keys may be assigned to \emph{groups}:
+% arbitrary sets which are independent of the key tree. Thus modifying the
+% example to read
+% \begin{verbatim}
+%   \keys define:nn { mymodule }
+%     {
+%       key-one   .code:n   = { \my_func:n {#1} } ,
+%       key-one   .groups:n = { first }           ,
+%       key-two   .tl_set:N = \l_my_a_tl          ,
+%       key-two   .groups:n = { first }           ,
+%       key-three .tl_set:N = \l_my_b_tl          ,
+%       key-three .groups:n = { second }          ,
+%       key-four  .fp_set:N = \l_my_a_fp          ,
+%     }
+% \end{verbatim}
+% assigns \texttt{key-one} and \texttt{key-two} to group \texttt{first},
+% \texttt{key-three} to group \texttt{second}, while \texttt{key-four} is
+% not assigned to a group.
+%
+% Selective key setting may be achieved either by selecting one or more
+% groups to be made \enquote{active}, or by marking one or more groups to
+% be ignored in key setting.
+%
+% \begin{function}[added = 2013-07-14, updated = 2019-01-29]
+%   {
+%     \keys_set_filter:nnn, \keys_set_filter:nnV,
+%     \keys_set_filter:nnv, \keys_set_filter:nno,
+%     \keys_set_filter:nnnN, \keys_set_filter:nnVN,
+%     \keys_set_filter:nnvN, \keys_set_filter:nnoN,
+%     \keys_set_filter:nnnnN, \keys_set_filter:nnVnN,
+%     \keys_set_filter:nnvnN, \keys_set_filter:nnonN,
+%   }
+%   \begin{syntax}
+%     \cs{keys_set_filter:nnn} \Arg{module} \Arg{groups} \Arg{keyval list}
+%     \cs{keys_set_filter:nnnN} \Arg{module} \Arg{groups} \Arg{keyval list} \meta{tl}
+%     \cs{keys_set_filter:nnnnN} \Arg{module} \Arg{groups} \Arg{keyval list} \meta{root} \meta{tl}
+%   \end{syntax}
+%   Activates key filtering in an \enquote{opt-out} sense: keys assigned to any
+%   of the \meta{groups} specified are ignored. The \meta{groups} are
+%   given as a comma-separated list. Unknown keys are not assigned to any
+%   group and are thus always set. The key--value pairs for each
+%   key which is filtered out are stored in the \meta{tl} in a
+%   comma-separated form (\emph{i.e.}~an edited version of the \meta{keyval
+%   list}). The \cs{keys_set_filter:nnn} version skips this stage.
+%
+%   Use of \cs{keys_set_filter:nnnN} can be nested, with the correct residual
+%   \meta{keyval list} returned at each stage. In the version which takes
+%   a \meta{root} argument, the key list is returned relative to that point
+%   in the key tree. In the cases without a \meta{root} argument, only
+%   the key names and values are returned.
+% \end{function}
+%
+% \begin{function}[added = 2013-07-14, updated = 2017-05-27]
+%   {
+%     \keys_set_groups:nnn, \keys_set_groups:nnV,
+%     \keys_set_groups:nnv, \keys_set_groups:nno
+%   }
+%   \begin{syntax}
+%     \cs{keys_set_groups:nnn} \Arg{module} \Arg{groups} \Arg{keyval list}
+%   \end{syntax}
+%   Activates key filtering in an \enquote{opt-in} sense: only keys assigned to
+%   one or more of the \meta{groups} specified are set. The \meta{groups} are
+%   given as a comma-separated list. Unknown keys are not assigned to any
+%   group and are thus never set.
+% \end{function}
+%
+% \section{Utility functions for keys}
+%
+% \begin{function}[EXP, pTF, updated = 2017-11-14]{\keys_if_exist:nn}
+%   \begin{syntax}
+%     \cs{keys_if_exist_p:nn} \Arg{module} \Arg{key} \\
+%     \cs{keys_if_exist:nnTF} \Arg{module} \Arg{key} \Arg{true code} \Arg{false code}
+%   \end{syntax}
+%   Tests if the \meta{key} exists for \meta{module}, \emph{i.e.}~if any code
+%   has been defined for \meta{key}.
+% \end{function}
+%
+% \begin{function}[added = 2011-08-21,EXP,pTF, updated = 2017-11-14]
+%   {\keys_if_choice_exist:nnn}
+%   \begin{syntax}
+%     \cs{keys_if_choice_exist_p:nnn} \Arg{module} \Arg{key} \Arg{choice} \\
+%     \cs{keys_if_choice_exist:nnnTF} \Arg{module} \Arg{key} \Arg{choice} \Arg{true code} \Arg{false code}
+%   \end{syntax}
+%   Tests if the \meta{choice} is defined for the \meta{key} within the
+%   \meta{module}, \emph{i.e.}~if any code has been defined for
+%   \meta{key}/\meta{choice}. The test is \texttt{false} if the \meta{key}
+%   itself is not defined.
+% \end{function}
+%
+% \begin{function}[updated = 2015-08-09]{\keys_show:nn}
+%   \begin{syntax}
+%     \cs{keys_show:nn} \Arg{module} \Arg{key}
+%   \end{syntax}
+%   Displays in the terminal
+%   the information associated to the \meta{key} for a \meta{module},
+%   including the function which is used to actually implement it.
+% \end{function}
+%
+% \begin{function}[added = 2014-08-22, updated = 2015-08-09]{\keys_log:nn}
+%   \begin{syntax}
+%     \cs{keys_log:nn} \Arg{module} \Arg{key}
+%   \end{syntax}
+%   Writes in the log file the information associated to the \meta{key}
+%   for a \meta{module}.  See also \cs{keys_show:nn} which displays the
+%   result in the terminal.
+% \end{function}
+%
+% \section{Low-level interface for parsing key--val lists}
+%
+% To re-cap from earlier, a key--value list is input of the form
+% \begin{verbatim}
+%   KeyOne = ValueOne ,
+%   KeyTwo = ValueTwo ,
+%   KeyThree
+% \end{verbatim}
+% where each key--value pair is separated by a comma from the rest of
+% the list, and each key--value pair does not necessarily contain an
+% equals sign or a value! Processing this type of input correctly
+% requires a number of careful steps, to correctly account for
+% braces, spaces and the category codes of separators.
+%
+% While the functions described earlier are used as a high-level interface
+% for processing such input, in special circumstances you may wish to use
+% a lower-level approach.
+% The low-level parsing system converts a \meta{key--value list}
+% into \meta{keys} and associated \meta{values}. After the parsing phase
+% is completed, the resulting keys and values (or keys alone) are
+% available for further processing. This processing is not carried out by the
+% low-level parser itself, and so the parser requires the names of
+% two functions along with the key--value list. One function is
+% needed to process key--value pairs (it receives two arguments),
+% and a second function is required for keys given without any value
+% (it is called with a single argument).
+%
+% The parser does not double |#| tokens or expand any input. Active
+% tokens |=| and |,| appearing at the outer level of braces are converted
+% to category \enquote{other} (12) so that the parser does not \enquote{miss}
+% any due to category code changes. Spaces are removed from the ends
+% of the keys and values. Keys and values which are given in braces
+% have exactly one set removed (after space trimming), thus
+% \begin{verbatim}
+%    key = {value here},
+% \end{verbatim}
+% and
+% \begin{verbatim}
+%   key = value here,
+% \end{verbatim}
+% are treated identically.
+%
+% \begin{function}[updated = 2011-09-08]{\keyval_parse:NNn}
+%   \begin{syntax}
+%     \cs{keyval_parse:NNn} \meta{function_1} \meta{function_2} \Arg{key--value list}
+%   \end{syntax}
+%   Parses the \meta{key--value list} into a series of \meta{keys} and
+%   associated \meta{values}, or keys alone (if no \meta{value} was
+%   given).  \meta{function_1} should take one argument, while
+%   \meta{function_2} should absorb two arguments. After
+%   \cs{keyval_parse:NNn} has parsed the \meta{key--value list},
+%   \meta{function_1} is used to process keys given with no value
+%   and \meta{function_2} is used to process keys given with a
+%   value. The order of the \meta{keys} in the \meta{key--value list}
+%   is preserved. Thus
+%   \begin{verbatim}
+%     \keyval_parse:NNn \function:n \function:nn
+%       { key1 = value1 , key2 = value2, key3 = , key4 }
+%   \end{verbatim}
+%   is converted into an input stream
+%   \begin{verbatim}
+%     \function:nn { key1 } { value1 }
+%     \function:nn { key2 } { value2 }
+%     \function:nn { key3 } { }
+%     \function:n  { key4 }
+%   \end{verbatim}
+%   Note that there is a difference between an empty value (an equals
+%   sign followed by nothing) and a missing value (no equals sign at
+%   all). Spaces are trimmed from the ends of the \meta{key} and \meta{value},
+%   then one \emph{outer} set of braces is removed from the \meta{key}
+%   and \meta{value} as part of the processing.
+% \end{function}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3keys} Implementation}
+%
+%    \begin{macrocode}
+%<*initex|package>
+%    \end{macrocode}
+%
+% \subsection{Low-level interface}
+%
+% The low-level key parser is based heavily on \pkg{keyval}, but with a number
+% of additional \enquote{safety} requirements and with the idea that the
+% parsed list of key--value pairs can be processed in a variety of ways.
+% The net result is that this code needs around twice the amount of time
+% as \pkg{keyval} to parse the same list of keys. To optimise speed as far
+% as reasonably practical, a number of lower-level approaches are taken
+% rather than using the higher-level \pkg{expl3} interfaces.
+%
+%    \begin{macrocode}
+%<@@=keyval>
+%    \end{macrocode}
+%
+% \begin{variable}{\l_@@_key_tl, \l_@@_value_tl}
+%   The current key name and value.
+%    \begin{macrocode}
+\tl_new:N \l_@@_key_tl
+\tl_new:N \l_@@_value_tl
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_sanitise_tl}
+%   A token list variable for dealing with awkward category codes in the
+%   input.
+%    \begin{macrocode}
+\tl_new:N \l_@@_sanitise_tl
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\keyval_parse:NNn}
+%   The main function starts off by normalising category codes in package mode.
+%   That's relatively \enquote{expensive} so is skipped (hopefully) in format
+%   mode. We then hand off to the parser. The use of \cs{q_mark} here prevents
+%   loss of braces from the key argument. Notice that by passing the two
+%   processor commands along the input stack we avoid the need to track these
+%   at all.
+%    \begin{macrocode}
+\cs_new_protected:Npn \keyval_parse:NNn #1#2#3
+  {
+%<*initex>
+    \@@_loop:NNw #1#2 \q_mark #3 , \q_recursion_tail ,
+%</initex>
+%<*package>
+    \tl_set:Nn \l_@@_sanitise_tl {#3}
+    \@@_sanitise_equals:
+    \@@_sanitise_comma:
+    \exp_after:wN \@@_loop:NNw \exp_after:wN #1 \exp_after:wN #2
+      \exp_after:wN \q_mark \l_@@_sanitise_tl , \q_recursion_tail ,
+%</package>
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_sanitise_equals:, \@@_sanitise_comma:}
+% \begin{macro}
+%   {
+%     \@@_sanitise_equals_auxi:w, \@@_sanitise_equals_auxii:w,
+%     \@@_sanitise_comma_auxi:w, \@@_sanitise_comma_auxii:w,
+%     \@@_sanitise_aux:w
+%   }
+%   A reasonably fast search and replace set up specifically for the active
+%   tokens. The nature of the input is known so everything is hard-coded.
+%   With only two tokens to cover, the speed gain from using dedicated
+%   functions is worth it.
+%    \begin{macrocode}
+%<*package>
+\group_begin:
+  \char_set_catcode_active:n { `\= }
+  \char_set_catcode_active:n { `\, }
+  \cs_new_protected:Npn \@@_sanitise_equals:
+    {
+      \exp_after:wN \@@_sanitise_equals_auxi:w \l_@@_sanitise_tl
+        \q_mark = \q_nil =
+      \exp_after:wN \@@_sanitise_aux:w \l_@@_sanitise_tl
+    }
+    \cs_new_protected:Npn \@@_sanitise_equals_auxi:w #1 =
+      {
+        \tl_set:Nn \l_@@_sanitise_tl {#1}
+        \@@_sanitise_equals_auxii:w
+      }
+    \cs_new_protected:Npn \@@_sanitise_equals_auxii:w #1 =
+      {
+        \if_meaning:w \q_nil #1 \scan_stop:
+        \else:
+          \tl_set:Nx \l_@@_sanitise_tl
+            {
+              \exp_not:o \l_@@_sanitise_tl
+              \token_to_str:N =
+              \exp_not:n {#1}
+            }
+          \exp_after:wN \@@_sanitise_equals_auxii:w
+        \fi:
+      }
+  \cs_new_protected:Npn \@@_sanitise_comma:
+    {
+      \exp_after:wN \@@_sanitise_comma_auxi:w \l_@@_sanitise_tl
+        \q_mark , \q_nil ,
+      \exp_after:wN \@@_sanitise_aux:w \l_@@_sanitise_tl
+    }
+    \cs_new_protected:Npn \@@_sanitise_comma_auxi:w #1 ,
+      {
+        \tl_set:Nn \l_@@_sanitise_tl {#1}
+        \@@_sanitise_comma_auxii:w
+      }
+    \cs_new_protected:Npn \@@_sanitise_comma_auxii:w #1 ,
+      {
+        \if_meaning:w \q_nil #1 \scan_stop:
+        \else:
+          \tl_set:Nx \l_@@_sanitise_tl
+            {
+              \exp_not:o \l_@@_sanitise_tl
+              \token_to_str:N ,
+              \exp_not:n {#1}
+            }
+          \exp_after:wN \@@_sanitise_comma_auxii:w
+        \fi:
+      }
+\group_end:
+\cs_new_protected:Npn \@@_sanitise_aux:w #1 \q_mark
+  { \tl_set:Nn \l_@@_sanitise_tl {#1} }
+%</package>
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_loop:NNw}
+%   A fast test for the end of the loop, remembering to remove the leading
+%   quark first. Assuming that is not the case, look for a key and value then
+%   loop around, re-inserting a leading quark in front of the next position.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_loop:NNw #1#2#3 ,
+  {
+    \exp_after:wN \if_meaning:w \exp_after:wN \q_recursion_tail
+      \use_none:n #3 \prg_do_nothing:
+    \else:
+      \@@_split:NNw #1#2#3 == \q_stop
+      \exp_after:wN \@@_loop:NNw \exp_after:wN #1 \exp_after:wN #2
+        \exp_after:wN \q_mark
+    \fi:
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_split:NNw, \@@_split_value:NNw}
+% \begin{macro}{\@@_split_tidy:w}
+% \begin{macro}{\@@_action:}
+%   The value is picked up separately from the key so there can be another
+%   quark inserted at the front, keeping braces and allowing both parts to
+%   share the same code paths. The key is found first then there's a check
+%   that there is something there: this is biased to the common case of there
+%   actually being a key. For the value, we first need to see if there is
+%   anything to do: if there is, extract it. The appropriate action is then
+%   inserted in front of the key and value. Doing this using an assignment is
+%   marginally faster than an an expansion chain.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_split:NNw #1#2#3 =
+  {
+    \@@_def:Nn \l_@@_key_tl {#3}
+    \if_meaning:w \l_@@_key_tl \c_empty_tl
+      \exp_after:wN \@@_split_tidy:w
+    \else:
+      \exp_after:wN \@@_split_value:NNw
+        \exp_after:wN #1
+        \exp_after:wN #2
+        \exp_after:wN \q_mark
+    \fi:
+  }
+\cs_new_protected:Npn \@@_split_value:NNw #1#2#3 = #4 \q_stop
+  {
+    \if:w \scan_stop: \tl_to_str:n {#4} \scan_stop:
+      \cs_set:Npx \@@_action:
+        { \exp_not:N #1 { \exp_not:o \l_@@_key_tl } }
+    \else:
+      \if:w
+        \scan_stop:
+        \__kernel_tl_to_str:w \exp_after:wN { \use_none:n #4 }
+        \scan_stop:
+        \@@_def:Nn \l_@@_value_tl {#3}
+        \cs_set:Npx \@@_action:
+          {
+            \exp_not:N #2
+              { \exp_not:o \l_@@_key_tl }
+              { \exp_not:o \l_@@_value_tl }
+          }
+      \else:
+        \cs_set:Npn \@@_action:
+          {
+            \__kernel_msg_error:nn { kernel }
+              { misplaced-equals-sign }
+          }
+      \fi:
+    \fi:
+    \@@_action:
+  }
+\cs_new_protected:Npn \@@_split_tidy:w #1 \q_stop
+  {
+    \if:w
+      \scan_stop:
+      \__kernel_tl_to_str:w \exp_after:wN { \use_none:n #1 }
+      \scan_stop:
+    \else:
+      \exp_after:wN \@@_empty_key:
+    \fi:
+  }
+\cs_new:Npn \@@_action: { }
+\cs_new_protected:Npn \@@_empty_key:
+  { \__kernel_msg_error:nn { kernel } { misplaced-equals-sign } }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_def:Nn}
+% \begin{macro}[EXP]{\@@_def_aux:n}
+% \begin{macro}[EXP]{\@@_def_aux:w}
+%   First remove the leading quark, then trim spaces off, and finally remove
+%   a set of braces.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_def:Nn #1#2
+  {
+    \tl_set:Nx #1
+      { \tl_trim_spaces_apply:oN { \use_none:n #2 } \@@_def_aux:n }
+  }
+\cs_new:Npn \@@_def_aux:n #1
+  { \@@_def_aux:w #1 \q_stop }
+\cs_new:Npn \@@_def_aux:w #1 \q_stop { \exp_not:n {#1} }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% One message for the low level parsing system.
+%    \begin{macrocode}
+\__kernel_msg_new:nnnn { kernel } { misplaced-equals-sign }
+  { Misplaced~equals~sign~in~key-value~input~\msg_line_number: }
+  {
+    LaTeX~is~attempting~to~parse~some~key-value~input~but~found~
+    two~equals~signs~not~separated~by~a~comma.
+  }
+%    \end{macrocode}
+%
+% \subsection{Constants and variables}
+%
+%    \begin{macrocode}
+%<@@=keys>
+%    \end{macrocode}
+%
+% \begin{variable}
+%   {
+%     \c_@@_code_root_tl    ,
+%     \c_@@_default_root_tl ,
+%     \c_@@_groups_root_tl  ,
+%     \c_@@_inherit_root_tl  ,
+%     \c_@@_type_root_tl    ,
+%     \c_@@_validate_root_tl
+%   }
+%   Various storage areas for the different data which make up keys.
+%    \begin{macrocode}
+\tl_const:Nn \c_@@_code_root_tl     { key~code~>~ }
+\tl_const:Nn \c_@@_default_root_tl  { key~default~>~ }
+\tl_const:Nn \c_@@_groups_root_tl   { key~groups~>~ }
+\tl_const:Nn \c_@@_inherit_root_tl  { key~inherit~>~ }
+\tl_const:Nn \c_@@_type_root_tl     { key~type~>~ }
+\tl_const:Nn \c_@@_validate_root_tl { key~validate~>~ }
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\c_@@_props_root_tl}
+%   The prefix for storing properties.
+%    \begin{macrocode}
+\tl_const:Nn \c_@@_props_root_tl { key~prop~>~ }
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_keys_choice_int, \l_keys_choice_tl}
+%   Publicly accessible data on which choice is being used when several
+%   are generated as a set.
+%    \begin{macrocode}
+\int_new:N \l_keys_choice_int
+\tl_new:N \l_keys_choice_tl
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_groups_clist}
+%   Used for storing and recovering the list of groups which apply to a key:
+%   set as a comma list but at one point we have to use this for a token
+%   list recovery.
+%    \begin{macrocode}
+\clist_new:N \l_@@_groups_clist
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_keys_key_tl}
+%   The name of a key itself: needed when setting keys.
+%    \begin{macrocode}
+\tl_new:N \l_keys_key_tl
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_module_tl}
+%   The module for an entire set of keys.
+%    \begin{macrocode}
+\tl_new:N \l_@@_module_tl
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_no_value_bool}
+%   A marker is needed internally to show if only a key or a key plus a
+%   value was seen: this is recorded here.
+%    \begin{macrocode}
+\bool_new:N \l_@@_no_value_bool
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_only_known_bool}
+%   Used to track if only \enquote{known} keys are being set.
+%    \begin{macrocode}
+\bool_new:N \l_@@_only_known_bool
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_keys_path_tl}
+%   The \enquote{path} of the current key is stored here: this is
+%   available to the programmer and so is public.
+%    \begin{macrocode}
+\tl_new:N \l_keys_path_tl
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_inherit_tl}
+%    \begin{macrocode}
+\tl_new:N \l_@@_inherit_tl
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_relative_tl}
+%   The relative path for passing keys back to the user.
+%    \begin{macrocode}
+\tl_new:N \l_@@_relative_tl
+\tl_set:Nn \l_@@_relative_tl { \q_no_value }
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_property_tl}
+%   The \enquote{property} begin set for a key at definition time is
+%   stored here.
+%    \begin{macrocode}
+\tl_new:N \l_@@_property_tl
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_selective_bool, \l_@@_filtered_bool}
+%   Two flags for using key groups: one to indicate that \enquote{selective}
+%   setting is active, a second to specify which type (\enquote{opt-in}
+%   or \enquote{opt-out}).
+%    \begin{macrocode}
+\bool_new:N \l_@@_selective_bool
+\bool_new:N \l_@@_filtered_bool
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_selective_seq}
+%   The list of key groups being filtered in or out during selective setting.
+%    \begin{macrocode}
+\seq_new:N \l_@@_selective_seq
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_unused_clist}
+%   Used when setting only some keys to store those left over.
+%    \begin{macrocode}
+\tl_new:N \l_@@_unused_clist
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_keys_value_tl}
+%   The value given for a key: may be empty if no value was given.
+%    \begin{macrocode}
+\tl_new:N \l_keys_value_tl
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_tmp_bool, \l_@@_tmpa_tl, \l_@@_tmpb_tl}
+%   Scratch space.
+%    \begin{macrocode}
+\bool_new:N \l_@@_tmp_bool
+\tl_new:N \l_@@_tmpa_tl
+\tl_new:N \l_@@_tmpb_tl
+%    \end{macrocode}
+% \end{variable}
+%
+% \subsection{The key defining mechanism}
+%
+% \begin{macro}{\keys_define:nn}
+% \begin{macro}{\@@_define:nnn, \@@_define:onn}
+%   The public function for definitions is just a wrapper for the lower
+%   level mechanism, more or less. The outer function is designed to
+%   keep a track of the current module, to allow safe nesting. The module is set
+%   removing any leading |/| (which is not needed here).
+%    \begin{macrocode}
+\cs_new_protected:Npn \keys_define:nn
+  { \@@_define:onn \l_@@_module_tl }
+\cs_new_protected:Npn \@@_define:nnn #1#2#3
+  {
+    \tl_set:Nx \l_@@_module_tl { \@@_trim_spaces:n {#2} }
+    \keyval_parse:NNn \@@_define:n \@@_define:nn {#3}
+    \tl_set:Nn \l_@@_module_tl {#1}
+  }
+\cs_generate_variant:Nn \@@_define:nnn { o }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_define:n}
+% \begin{macro}{\@@_define:nn}
+% \begin{macro}{\@@_define_aux:nn}
+%   The outer functions here record whether a value was given and then
+%   converge on a common internal mechanism. There is first a search for
+%   a property in the current key name, then a check to make sure it is
+%   known before the code hands off to the next step.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_define:n #1
+  {
+    \bool_set_true:N \l_@@_no_value_bool
+    \@@_define_aux:nn {#1} { }
+  }
+\cs_new_protected:Npn \@@_define:nn #1#2
+  {
+    \bool_set_false:N \l_@@_no_value_bool
+    \@@_define_aux:nn {#1} {#2}
+  }
+\cs_new_protected:Npn \@@_define_aux:nn #1#2
+  {
+    \@@_property_find:n {#1}
+    \cs_if_exist:cTF { \c_@@_props_root_tl \l_@@_property_tl }
+      { \@@_define_code:n {#2}
+      }
+      {
+         \tl_if_empty:NF \l_@@_property_tl
+           {
+             \__kernel_msg_error:nnxx { kernel } { key-property-unknown }
+              { \l_@@_property_tl } { \l_keys_path_tl }
+           }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_property_find:n}
+% \begin{macro}{\@@_property_find:w}
+%   Searching for a property means finding the last |.| in the input,
+%   and storing the text before and after it. Everything is turned into
+%   strings, so there is no problem using an \texttt{x}-type expansion.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_property_find:n #1
+  {
+    \tl_set:Nx \l_@@_property_tl { \@@_trim_spaces:n {#1} }
+    \exp_after:wN \@@_property_find:w \l_@@_property_tl . .
+      \q_stop {#1}
+  }
+\cs_new_protected:Npn \@@_property_find:w #1 . #2 . #3 \q_stop #4
+  {
+    \tl_if_blank:nTF {#3}
+      {
+        \tl_clear:N \l_@@_property_tl
+        \__kernel_msg_error:nnn { kernel } { key-no-property } {#4}
+      }
+      {
+        \str_if_eq:nnTF {#3} { . }
+          {
+            \tl_set:Nx \l_keys_path_tl
+              {
+                \tl_if_empty:NF \l_@@_module_tl
+                  { \l_@@_module_tl  / }
+               \tl_trim_spaces:n {#1}
+              }
+            \tl_set:Nn \l_@@_property_tl { . #2 }
+          }
+          {
+            \tl_set:Nx \l_keys_path_tl { \l_@@_module_tl / #1 . #2 }
+            \@@_property_search:w #3 \q_stop
+          }
+      }
+  }
+\cs_new_protected:Npn \@@_property_search:w #1 . #2 \q_stop
+  {
+    \str_if_eq:nnTF {#2} { . }
+      {
+        \tl_set:Nx \l_keys_path_tl { \l_keys_path_tl }
+        \tl_set:Nn \l_@@_property_tl { . #1 }
+      }
+      {
+        \tl_set:Nx \l_keys_path_tl { \l_keys_path_tl . #1 }
+        \@@_property_search:w #2 \q_stop
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_define_code:n}
+% \begin{macro}[EXP]{\@@_define_code:w}
+%   Two possible cases. If there is a value for the key, then just use
+%   the function. If not, then a check to make sure there is no need for
+%   a value with the property. If there should be one then complain,
+%   otherwise execute it. There is no need to check for a |:| as if it
+%   was missing the earlier tests would have failed.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_define_code:n #1
+  {
+    \bool_if:NTF \l_@@_no_value_bool
+      {
+        \exp_after:wN \@@_define_code:w
+          \l_@@_property_tl \q_stop
+          { \use:c { \c_@@_props_root_tl \l_@@_property_tl } }
+          {
+            \__kernel_msg_error:nnxx { kernel }
+              { key-property-requires-value } { \l_@@_property_tl }
+              { \l_keys_path_tl }
+            }
+      }
+      { \use:c { \c_@@_props_root_tl \l_@@_property_tl } {#1} }
+  }
+\exp_last_unbraced:NNNNo
+  \cs_new:Npn \@@_define_code:w #1 \c_colon_str #2 \q_stop
+    { \tl_if_empty:nTF {#2} }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Turning properties into actions}
+%
+% \begin{macro}{\@@_bool_set:Nn, \@@_bool_set:cn}
+%   Boolean keys are really just choices, but all done by hand. The
+%   second argument here is the scope: either empty or \texttt{ g } for
+%   global.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_bool_set:Nn #1#2
+  {
+    \bool_if_exist:NF #1 { \bool_new:N #1 }
+    \@@_choice_make:
+    \@@_cmd_set:nx { \l_keys_path_tl / true }
+      { \exp_not:c { bool_ #2 set_true:N } \exp_not:N #1 }
+    \@@_cmd_set:nx { \l_keys_path_tl / false }
+      { \exp_not:c { bool_ #2 set_false:N } \exp_not:N #1 }
+    \@@_cmd_set:nn { \l_keys_path_tl / unknown }
+      {
+        \__kernel_msg_error:nnx { kernel } { boolean-values-only }
+          { \l_keys_key_tl }
+      }
+    \@@_default_set:n { true }
+  }
+\cs_generate_variant:Nn \@@_bool_set:Nn { c }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_bool_set_inverse:Nn, \@@_bool_set_inverse:cn}
+%   Inverse boolean setting is much the same.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_bool_set_inverse:Nn #1#2
+  {
+    \bool_if_exist:NF #1 { \bool_new:N #1 }
+    \@@_choice_make:
+    \@@_cmd_set:nx { \l_keys_path_tl / true }
+      { \exp_not:c { bool_ #2 set_false:N } \exp_not:N #1 }
+    \@@_cmd_set:nx { \l_keys_path_tl / false }
+      { \exp_not:c { bool_ #2 set_true:N } \exp_not:N #1 }
+    \@@_cmd_set:nn { \l_keys_path_tl / unknown }
+      {
+        \__kernel_msg_error:nnx { kernel } { boolean-values-only }
+          { \l_keys_key_tl }
+      }
+    \@@_default_set:n { true }
+  }
+\cs_generate_variant:Nn \@@_bool_set_inverse:Nn { c }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_choice_make:, \@@_multichoice_make:}
+% \begin{macro}{\@@_choice_make:N}
+% \begin{macro}{\@@_choice_make_aux:N}
+%   To make a choice from a key, two steps: set the code, and set the
+%   unknown key. As multichoices and choices are essentially the same bar one
+%   function, the code is given together.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_choice_make:
+  { \@@_choice_make:N \@@_choice_find:n }
+\cs_new_protected:Npn \@@_multichoice_make:
+  { \@@_choice_make:N \@@_multichoice_find:n }
+\cs_new_protected:Npn \@@_choice_make:N #1
+  {
+    \cs_if_exist:cTF
+      { \c_@@_type_root_tl \@@_parent:o \l_keys_path_tl }
+      {
+        \str_if_eq:vnTF
+          { \c_@@_type_root_tl \@@_parent:o \l_keys_path_tl }
+          { choice }
+          {
+            \__kernel_msg_error:nnxx { kernel } { nested-choice-key }
+              { \l_keys_path_tl } { \@@_parent:o \l_keys_path_tl }
+          }
+          { \@@_choice_make_aux:N #1 }
+      }
+      { \@@_choice_make_aux:N #1 }
+  }
+\cs_new_protected:Npn \@@_choice_make_aux:N #1
+  {
+    \cs_set_nopar:cpn { \c_@@_type_root_tl \l_keys_path_tl }
+      { choice }
+    \@@_cmd_set:nn { \l_keys_path_tl } { #1 {##1} }
+    \@@_cmd_set:nn { \l_keys_path_tl / unknown }
+      {
+        \__kernel_msg_error:nnxx { kernel } { key-choice-unknown }
+          { \l_keys_path_tl } {##1}
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_choices_make:nn, \@@_multichoices_make:nn}
+% \begin{macro}{\@@_choices_make:Nnn}
+%   Auto-generating choices means setting up the root key as a choice, then
+%   defining each choice in turn.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_choices_make:nn
+  { \@@_choices_make:Nnn \@@_choice_make: }
+\cs_new_protected:Npn \@@_multichoices_make:nn
+  { \@@_choices_make:Nnn \@@_multichoice_make: }
+\cs_new_protected:Npn \@@_choices_make:Nnn #1#2#3
+  {
+    #1
+    \int_zero:N \l_keys_choice_int
+    \clist_map_inline:nn {#2}
+      {
+        \int_incr:N \l_keys_choice_int
+        \@@_cmd_set:nx
+          { \l_keys_path_tl / \@@_trim_spaces:n {##1} }
+          {
+            \tl_set:Nn \exp_not:N \l_keys_choice_tl {##1}
+            \int_set:Nn \exp_not:N \l_keys_choice_int
+              { \int_use:N \l_keys_choice_int }
+            \exp_not:n {#3}
+          }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}
+%   {\@@_cmd_set:nn, \@@_cmd_set:nx, \@@_cmd_set:Vn, \@@_cmd_set:Vo}
+%   Setting the code for a key first logs if appropriate that we are
+%   defining a new key, then saves the code.
+%    \begin{macrocode}
+\__kernel_patch:nnNNpn
+  {
+    \cs_if_exist:cF { \c_@@_code_root_tl #1 }
+      { \__kernel_debug_log:x { Defining~key~#1~\msg_line_context: } }
+  }
+  { }
+\cs_new_protected:Npn \@@_cmd_set:nn #1#2
+  { \cs_set_protected:cpn { \c_@@_code_root_tl #1 } ##1 {#2} }
+\cs_generate_variant:Nn \@@_cmd_set:nn { nx , Vn , Vo }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_default_set:n}
+%   Setting a default value is easy. These are stored using \cs{cs_set:cpx} as this
+%   avoids any worries about whether a token list exists.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_default_set:n #1
+  {
+    \tl_if_empty:nTF {#1}
+      {
+        \cs_set_eq:cN
+          { \c_@@_default_root_tl \l_keys_path_tl }
+          \tex_undefined:D
+      }
+      {
+        \cs_set_nopar:cpx
+          { \c_@@_default_root_tl \l_keys_path_tl }
+          { \exp_not:n {#1} }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_groups_set:n}
+%   Assigning a key to one or more groups uses comma lists. As the list of
+%   groups only exists if there is anything to do, the setting is done using
+%   a scratch list. For the usual grouping reasons we use the low-level
+%   approach to undefining a list.  We also use the low-level approach for
+%   the other case to avoid tripping up the |check-declarations| code.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_groups_set:n #1
+  {
+    \clist_set:Nn \l_@@_groups_clist {#1}
+    \clist_if_empty:NTF \l_@@_groups_clist
+      {
+        \cs_set_eq:cN { \c_@@_groups_root_tl \l_keys_path_tl }
+          \tex_undefined:D
+      }
+      {
+        \cs_set_eq:cN { \c_@@_groups_root_tl \l_keys_path_tl }
+          \l_@@_groups_clist
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_inherit:n}
+%  Inheritance means ignoring anything already said about the key:
+%  zap the lot and set up.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_inherit:n #1
+  {
+    \@@_undefine:
+    \cs_set_nopar:cpn { \c_@@_inherit_root_tl \l_keys_path_tl } {#1}
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_initialise:n}
+%   A set up for initialisation: just run the code if it exists.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_initialise:n #1
+  {
+    \cs_if_exist_use:cT { \c_@@_code_root_tl \l_keys_path_tl } { {#1} }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_meta_make:n}
+% \begin{macro}{\@@_meta_make:nn}
+%   To create a meta-key, simply set up to pass data through.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_meta_make:n #1
+  {
+    \@@_cmd_set:Vo \l_keys_path_tl
+      {
+        \exp_after:wN \keys_set:nn
+        \exp_after:wN { \l_@@_module_tl } {#1}
+      }
+  }
+\cs_new_protected:Npn \@@_meta_make:nn #1#2
+  { \@@_cmd_set:Vn \l_keys_path_tl { \keys_set:nn {#1} {#2} } }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_prop_put:Nn, \@@_prop_put:cn}
+%   Much the same as other variables, but needs a dedicated auxiliary.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_prop_put:Nn #1#2
+  {
+    \prop_if_exist:NF #1 { \prop_new:N #1 }
+    \exp_after:wN \@@_find_key_module:NNw
+      \exp_after:wN \l_@@_tmpa_tl
+      \exp_after:wN \l_@@_tmpb_tl
+      \l_keys_path_tl / \q_stop
+    \@@_cmd_set:nx { \l_keys_path_tl }
+      {
+        \exp_not:c { prop_ #2 put:Nnn }
+        \exp_not:N #1
+        { \l_@@_tmpb_tl }
+        \exp_not:n { {##1} }
+      }
+  }
+\cs_generate_variant:Nn \@@_prop_put:Nn { c }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_undefine:}
+%   Undefining a key has to be done without \cs{cs_undefine:c} as that
+%   function acts globally.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_undefine:
+  {
+    \clist_map_inline:nn
+      { code , default , groups , inherit , type , validate }
+      {
+        \cs_set_eq:cN
+          { \tl_use:c { c_@@_ ##1 _root_tl } \l_keys_path_tl }
+          \tex_undefined:D
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_value_requirement:nn}
+% \begin{macro}{\@@_validate_forbidden:, \@@_validate_required:}
+% \begin{macro}{\@@_validate_cleanup:w}
+%   Validating key input is done using a second function which runs before
+%   the main key code. Setting that up means setting it equal to a generic
+%   stub which does the check. This approach makes the lookup very fast at
+%   the cost of one additional csname per key that needs it. The cleanup here
+%   has to know the structure of the following code.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_value_requirement:nn #1#2
+  {
+    \str_case:nnF {#2}
+      {
+        { true }
+          {
+            \cs_set_eq:cc
+              { \c_@@_validate_root_tl \l_keys_path_tl }
+              { @@_validate_ #1 : }
+          }
+        { false }
+          {
+            \cs_if_eq:ccT
+              { \c_@@_validate_root_tl \l_keys_path_tl }
+              { @@_validate_ #1 : }
+              {
+                \cs_set_eq:cN
+                  { \c_@@_validate_root_tl \l_keys_path_tl }
+                  \tex_undefined:D
+              }
+          }
+      }
+      {
+        \__kernel_msg_error:nnx { kernel }
+          { key-property-boolean-values-only }
+          { .value_ #1 :n }
+      }
+  }
+\cs_new_protected:Npn \@@_validate_forbidden:
+  {
+    \bool_if:NF \l_@@_no_value_bool
+      {
+        \__kernel_msg_error:nnxx { kernel } { value-forbidden }
+          { \l_keys_path_tl } { \l_keys_value_tl }
+        \@@_validate_cleanup:w
+      }
+  }
+\cs_new_protected:Npn \@@_validate_required:
+  {
+    \bool_if:NT \l_@@_no_value_bool
+      {
+        \__kernel_msg_error:nnx { kernel } { value-required }
+          { \l_keys_path_tl }
+        \@@_validate_cleanup:w
+      }
+  }
+\cs_new_protected:Npn \@@_validate_cleanup:w #1 \cs_end: #2#3 { }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_variable_set:NnnN, \@@_variable_set:cnnN}
+%   Setting a variable takes the type and scope separately so that
+%   it is easy to make a new variable if needed.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_variable_set:NnnN #1#2#3#4
+  {
+    \use:c { #2_if_exist:NF } #1 { \use:c { #2 _new:N } #1 }
+    \@@_cmd_set:nx { \l_keys_path_tl }
+      {
+        \exp_not:c { #2 _ #3 set:N #4 }
+        \exp_not:N #1
+        \exp_not:n  { {##1} }
+      }
+  }
+\cs_generate_variant:Nn \@@_variable_set:NnnN { c }
+%    \end{macrocode}
+% \end{macro}
+%
+% \subsection{Creating key properties}
+%
+% The key property functions are all wrappers for internal functions,
+% meaning that things stay readable and can also be altered later on.
+%
+% Importantly, while key properties have \enquote{normal} argument specs, the
+% underlying code always supplies one braced argument to these. As such, argument
+% expansion is handled by hand rather than using the standard tools. This shows
+% up particularly for the two-argument properties, where things would otherwise
+% go badly wrong.
+%
+% \begin{macro}{.bool_set:N, .bool_set:c}
+% \begin{macro}{.bool_gset:N, .bool_gset:c}
+%   One function for this.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .bool_set:N } #1
+  { \@@_bool_set:Nn #1 { } }
+\cs_new_protected:cpn { \c_@@_props_root_tl .bool_set:c } #1
+  { \@@_bool_set:cn {#1} { } }
+\cs_new_protected:cpn { \c_@@_props_root_tl .bool_gset:N } #1
+  { \@@_bool_set:Nn #1 { g } }
+\cs_new_protected:cpn { \c_@@_props_root_tl .bool_gset:c } #1
+  { \@@_bool_set:cn {#1} { g } }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{.bool_set_inverse:N, .bool_set_inverse:c}
+% \begin{macro}{.bool_gset_inverse:N, .bool_gset_inverse:c}
+%   One function for this.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .bool_set_inverse:N } #1
+  { \@@_bool_set_inverse:Nn #1 { } }
+\cs_new_protected:cpn { \c_@@_props_root_tl .bool_set_inverse:c } #1
+  { \@@_bool_set_inverse:cn {#1} { } }
+\cs_new_protected:cpn { \c_@@_props_root_tl .bool_gset_inverse:N } #1
+  { \@@_bool_set_inverse:Nn #1 { g } }
+\cs_new_protected:cpn { \c_@@_props_root_tl .bool_gset_inverse:c } #1
+  { \@@_bool_set_inverse:cn {#1} { g } }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{.choice:}
+%   Making a choice is handled internally, as it is also needed by
+%   \texttt{.generate_choices:n}.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .choice: }
+  { \@@_choice_make: }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}
+%   {.choices:nn, .choices:Vn, .choices:on, .choices:xn}
+%   For auto-generation of a series of mutually-exclusive choices.
+%   Here, |#1| consists of two separate
+%   arguments, hence the slightly odd-looking implementation.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .choices:nn } #1
+  { \@@_choices_make:nn #1 }
+\cs_new_protected:cpn { \c_@@_props_root_tl .choices:Vn } #1
+  { \exp_args:NV \@@_choices_make:nn #1 }
+\cs_new_protected:cpn { \c_@@_props_root_tl .choices:on } #1
+  { \exp_args:No \@@_choices_make:nn #1 }
+\cs_new_protected:cpn { \c_@@_props_root_tl .choices:xn } #1
+  { \exp_args:Nx \@@_choices_make:nn #1 }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{.code:n}
+%   Creating code is simply a case of passing through to the underlying
+%   \texttt{set} function.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .code:n } #1
+  { \@@_cmd_set:nn { \l_keys_path_tl } {#1} }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{.clist_set:N, .clist_set:c}
+% \begin{macro}{.clist_gset:N, .clist_gset:c}
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .clist_set:N } #1
+  { \@@_variable_set:NnnN #1 { clist } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .clist_set:c } #1
+  { \@@_variable_set:cnnN {#1} { clist } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .clist_gset:N } #1
+  { \@@_variable_set:NnnN #1 { clist } { g } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .clist_gset:c } #1
+  { \@@_variable_set:cnnN {#1} { clist } { g } n }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{.default:n, .default:V, .default:o, .default:x}
+%   Expansion is left to the internal functions.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .default:n } #1
+  { \@@_default_set:n {#1} }
+\cs_new_protected:cpn { \c_@@_props_root_tl .default:V } #1
+  { \exp_args:NV \@@_default_set:n #1 }
+\cs_new_protected:cpn { \c_@@_props_root_tl .default:o } #1
+  { \exp_args:No \@@_default_set:n {#1} }
+\cs_new_protected:cpn { \c_@@_props_root_tl .default:x } #1
+  { \exp_args:Nx \@@_default_set:n {#1} }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{.dim_set:N, .dim_set:c}
+% \begin{macro}{.dim_gset:N, .dim_gset:c}
+% Setting a variable is very easy: just pass the data along.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .dim_set:N } #1
+  { \@@_variable_set:NnnN #1 { dim } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .dim_set:c } #1
+  { \@@_variable_set:cnnN {#1} { dim } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .dim_gset:N } #1
+  { \@@_variable_set:NnnN #1 { dim } { g } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .dim_gset:c } #1
+  { \@@_variable_set:cnnN {#1} { dim } { g } n }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{.fp_set:N, .fp_set:c}
+% \begin{macro}{.fp_gset:N, .fp_gset:c}
+%   Setting a variable is very easy: just pass the data along.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .fp_set:N } #1
+  { \@@_variable_set:NnnN #1 { fp } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .fp_set:c } #1
+  { \@@_variable_set:cnnN {#1} { fp } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .fp_gset:N } #1
+  { \@@_variable_set:NnnN #1 { fp } { g } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .fp_gset:c } #1
+  { \@@_variable_set:cnnN {#1} { fp } { g } n }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{.groups:n}
+%   A single property to create groups of keys.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .groups:n } #1
+  { \@@_groups_set:n {#1} }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{.inherit:n}
+%   Nothing complex: only one variant at the moment!
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .inherit:n } #1
+  { \@@_inherit:n {#1} }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{.initial:n, .initial:V, .initial:o, .initial:x}
+%   The standard hand-off approach.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .initial:n } #1
+  { \@@_initialise:n {#1} }
+\cs_new_protected:cpn { \c_@@_props_root_tl .initial:V } #1
+  { \exp_args:NV \@@_initialise:n #1 }
+\cs_new_protected:cpn { \c_@@_props_root_tl .initial:o } #1
+  { \exp_args:No \@@_initialise:n {#1} }
+\cs_new_protected:cpn { \c_@@_props_root_tl .initial:x } #1
+  { \exp_args:Nx \@@_initialise:n {#1} }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{.int_set:N, .int_set:c}
+% \begin{macro}{.int_gset:N, .int_gset:c}
+%   Setting a variable is very easy: just pass the data along.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .int_set:N } #1
+  { \@@_variable_set:NnnN #1 { int } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .int_set:c } #1
+  { \@@_variable_set:cnnN {#1} { int } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .int_gset:N } #1
+  { \@@_variable_set:NnnN #1 { int } { g } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .int_gset:c } #1
+  { \@@_variable_set:cnnN {#1} { int } { g } n }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{.meta:n}
+%   Making a meta is handled internally.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .meta:n } #1
+  { \@@_meta_make:n {#1} }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{.meta:nn}
+%   Meta with path: potentially lots of variants, but for the moment
+%   no so many defined.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .meta:nn } #1
+  { \@@_meta_make:nn #1 }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{.multichoice:}
+% \begin{macro}
+%   {
+%     .multichoices:nn, .multichoices:Vn, .multichoices:on,
+%       .multichoices:xn,
+%   }
+%   The same idea as \texttt{.choice:} and \texttt{.choices:nn}, but
+%   where more than one choice is allowed.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .multichoice: }
+  { \@@_multichoice_make: }
+\cs_new_protected:cpn { \c_@@_props_root_tl .multichoices:nn } #1
+  { \@@_multichoices_make:nn #1 }
+\cs_new_protected:cpn { \c_@@_props_root_tl .multichoices:Vn } #1
+  { \exp_args:NV \@@_multichoices_make:nn #1 }
+\cs_new_protected:cpn { \c_@@_props_root_tl .multichoices:on } #1
+  { \exp_args:No \@@_multichoices_make:nn #1 }
+\cs_new_protected:cpn { \c_@@_props_root_tl .multichoices:xn } #1
+  { \exp_args:Nx \@@_multichoices_make:nn #1 }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{.muskip_set:N, .muskip_set:c, .muskip_gset:N, .muskip_gset:c}
+%   Setting a variable is very easy: just pass the data along.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .muskip_set:N } #1
+  { \@@_variable_set:NnnN #1 { muskip } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .muskip_set:c } #1
+  { \@@_variable_set:cnnN {#1} { muskip } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .muskip_gset:N } #1
+  { \@@_variable_set:NnnN #1 { muskip } { g } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .muskip_gset:c } #1
+  { \@@_variable_set:cnnN {#1} { muskip } { g } n }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{.prop_put:N, .prop_put:c, .prop_gput:N, .prop_gput:c}
+%   Setting a variable is very easy: just pass the data along.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .prop_put:N } #1
+  { \@@_prop_put:Nn #1 { } }
+\cs_new_protected:cpn { \c_@@_props_root_tl .prop_put:c } #1
+  { \@@_prop_put:cn {#1} { } }
+\cs_new_protected:cpn { \c_@@_props_root_tl .prop_gput:N } #1
+  { \@@_prop_put:Nn #1 { g } }
+\cs_new_protected:cpn { \c_@@_props_root_tl .prop_gput:c } #1
+  { \@@_prop_put:cn {#1} { g } }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{.skip_set:N, .skip_set:c}
+% \begin{macro}{.skip_gset:N, .skip_gset:c}
+%   Setting a variable is very easy: just pass the data along.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .skip_set:N } #1
+  { \@@_variable_set:NnnN #1 { skip } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .skip_set:c } #1
+  { \@@_variable_set:cnnN {#1} { skip } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .skip_gset:N } #1
+  { \@@_variable_set:NnnN #1 { skip } { g } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .skip_gset:c } #1
+  { \@@_variable_set:cnnN {#1} { skip } { g } n }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{.tl_set:N, .tl_set:c}
+% \begin{macro}{.tl_gset:N, .tl_gset:c}
+% \begin{macro}{.tl_set_x:N, .tl_set_x:c}
+% \begin{macro}{.tl_gset_x:N, .tl_gset_x:c}
+%   Setting a variable is very easy: just pass the data along.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .tl_set:N } #1
+  { \@@_variable_set:NnnN #1 { tl } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .tl_set:c } #1
+  { \@@_variable_set:cnnN {#1} { tl } { } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .tl_set_x:N } #1
+  { \@@_variable_set:NnnN #1 { tl } { } x }
+\cs_new_protected:cpn { \c_@@_props_root_tl .tl_set_x:c } #1
+  { \@@_variable_set:cnnN {#1} { tl } { } x }
+\cs_new_protected:cpn { \c_@@_props_root_tl .tl_gset:N } #1
+  { \@@_variable_set:NnnN #1 { tl } { g } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .tl_gset:c } #1
+  { \@@_variable_set:cnnN {#1} { tl } { g } n }
+\cs_new_protected:cpn { \c_@@_props_root_tl .tl_gset_x:N } #1
+  { \@@_variable_set:NnnN #1 { tl } { g } x }
+\cs_new_protected:cpn { \c_@@_props_root_tl .tl_gset_x:c } #1
+  { \@@_variable_set:cnnN {#1} { tl } { g } x }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{.undefine:}
+%   Another simple wrapper.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .undefine: }
+  { \@@_undefine: }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{.value_forbidden:n}
+% \begin{macro}{.value_required:n}
+%   These are very similar, so both call the same function.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_tl .value_forbidden:n } #1
+  { \@@_value_requirement:nn { forbidden } {#1} }
+\cs_new_protected:cpn { \c_@@_props_root_tl .value_required:n } #1
+  { \@@_value_requirement:nn { required } {#1} }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Setting keys}
+%
+% \begin{macro}
+%   {
+%     \keys_set:nn, \keys_set:nV, \keys_set:nv, \keys_set:no,
+%     \@@_set:nn
+%   }
+% \begin{macro}{\@@_set:nnn}
+%   A simple wrapper allowing for nesting.
+%    \begin{macrocode}
+\cs_new_protected:Npn \keys_set:nn #1#2
+  {
+    \use:x
+      {
+        \bool_set_false:N \exp_not:N \l_@@_only_known_bool
+        \bool_set_false:N \exp_not:N \l_@@_filtered_bool
+        \bool_set_false:N \exp_not:N \l_@@_selective_bool
+        \tl_set:Nn \exp_not:N \l_@@_relative_tl
+          { \exp_not:N \q_no_value }
+        \@@_set:nn \exp_not:n { {#1} {#2} }
+        \bool_if:NT \l_@@_only_known_bool
+          { \bool_set_true:N \exp_not:N \l_@@_only_known_bool }
+        \bool_if:NT \l_@@_filtered_bool
+          { \bool_set_true:N \exp_not:N \l_@@_filtered_bool }
+        \bool_if:NT \l_@@_selective_bool
+          { \bool_set_true:N \exp_not:N \l_@@_selective_bool }
+        \tl_set:Nn \exp_not:N \l_@@_relative_tl
+          { \exp_not:o \l_@@_relative_tl }
+      }
+  }
+\cs_generate_variant:Nn \keys_set:nn { nV , nv , no }
+\cs_new_protected:Npn \@@_set:nn #1#2
+  { \exp_args:No \@@_set:nnn \l_@@_module_tl {#1} {#2} }
+\cs_new_protected:Npn \@@_set:nnn #1#2#3
+  {
+    \tl_set:Nx \l_@@_module_tl { \@@_trim_spaces:n {#2} }
+    \keyval_parse:NNn \@@_set_keyval:n \@@_set_keyval:nn {#3}
+    \tl_set:Nn \l_@@_module_tl {#1}
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}
+%   {
+%     \keys_set_known:nnN, \keys_set_known:nVN,
+%     \keys_set_known:nvN, \keys_set_known:noN
+%   }
+% \begin{macro}
+%   {
+%     \keys_set_known:nnnN, \keys_set_known:nVnN,
+%     \keys_set_known:nvnN, \keys_set_known:nonN
+%   }
+% \begin{macro}{\@@_set_known:nnnnN}
+% \begin{macro}
+%   {
+%     \keys_set_known:nn, \keys_set_known:nV,
+%     \keys_set_known:nv, \keys_set_known:no
+%   }
+%  \begin{macro}{\@@_set_known:nnn}
+%   Setting known keys simply means setting the appropriate flag, then
+%   running the standard code. To allow for nested setting, any existing
+%   value of \cs{l_@@_unused_clist} is saved on the stack and reset
+%   afterwards. Note that for speed/simplicity reasons we use a \texttt{tl}
+%   operation to set the \texttt{clist} here!
+%    \begin{macrocode}
+\cs_new_protected:Npn \keys_set_known:nnN #1#2#3
+  {
+    \exp_args:No \@@_set_known:nnnnN
+      \l_@@_unused_clist { \q_no_value } {#1} {#2} #3
+  }
+\cs_generate_variant:Nn \keys_set_known:nnN { nV , nv , no }
+\cs_new_protected:Npn \keys_set_known:nnnN #1#2#3#4
+  {
+    \exp_args:No \@@_set_known:nnnnN
+      \l_@@_unused_clist {#3} {#1} {#2} #4
+  }
+\cs_generate_variant:Nn \keys_set_known:nnnN { nV , nv , no }
+\cs_new_protected:Npn \@@_set_known:nnnnN #1#2#3#4#5
+  {
+    \clist_clear:N \l_@@_unused_clist
+    \@@_set_known:nnn {#2} {#3} {#4}
+    \tl_set:Nx #5 { \exp_not:o { \l_@@_unused_clist } }
+    \tl_set:Nn \l_@@_unused_clist {#1}
+  }
+\cs_new_protected:Npn \keys_set_known:nn #1#2
+  { \@@_set_known:nnn { \q_no_value } {#1} {#2} }
+\cs_generate_variant:Nn \keys_set_known:nn { nV , nv , no }
+\cs_new_protected:Npn \@@_set_known:nnn #1#2#3
+  {
+    \use:x
+      {
+        \bool_set_true:N \exp_not:N \l_@@_only_known_bool
+        \bool_set_false:N \exp_not:N \l_@@_filtered_bool
+        \bool_set_false:N \exp_not:N \l_@@_selective_bool
+        \tl_set:Nn \exp_not:N \l_@@_relative_tl { \exp_not:n {#1} }
+        \@@_set:nn \exp_not:n { {#2} {#3} }
+        \bool_if:NF \l_@@_only_known_bool
+          { \bool_set_false:N \exp_not:N \l_@@_only_known_bool }
+        \bool_if:NT \l_@@_filtered_bool
+          { \bool_set_true:N \exp_not:N \l_@@_filtered_bool }
+        \bool_if:NT \l_@@_selective_bool
+          { \bool_set_true:N \exp_not:N \l_@@_selective_bool }
+        \tl_set:Nn \exp_not:N \l_@@_relative_tl
+          { \exp_not:o \l_@@_relative_tl }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}
+%   {
+%     \keys_set_filter:nnnN, \keys_set_filter:nnVN, \keys_set_filter:nnvN,
+%       \keys_set_filter:nnoN
+%   }
+% \begin{macro}
+%   {
+%     \keys_set_filter:nnnnN, \keys_set_filter:nnVnN,
+%     \keys_set_filter:nnvnN, \keys_set_filter:nnonN
+%   }
+% \begin{macro}{\@@_set_filter:nnnnnN}
+% \begin{macro}
+%   {
+%     \keys_set_filter:nnn, \keys_set_filter:nnV, \keys_set_filter:nnv,
+%       \keys_set_filter:nno
+%   }
+% \begin{macro}{\@@_set_filter:nnnn}
+% \begin{macro}
+%   {
+%     \keys_set_groups:nnn, \keys_set_groups:nnV, \keys_set_groups:nnv,
+%       \keys_set_groups:nno
+%   }
+%  \begin{macro}{\@@_set_selective:nnn}
+%  \begin{macro}{\@@_set_selective:nnnn}
+%   The idea of setting keys in a selective manner again uses flags
+%   wrapped around the basic code. The comments on \cs{keys_set_known:nnN}
+%   also apply here. We have a bit more shuffling to do to keep everything
+%   nestable.
+%    \begin{macrocode}
+\cs_new_protected:Npn \keys_set_filter:nnnN #1#2#3#4
+  {
+    \exp_args:No \@@_set_filter:nnnnnN
+      \l_@@_unused_clist
+        { \q_no_value } {#1} {#2} {#3} #4
+  }
+\cs_generate_variant:Nn \keys_set_filter:nnnN { nnV , nnv , nno }
+\cs_new_protected:Npn \keys_set_filter:nnnnN #1#2#3#4#5
+  {
+    \exp_args:No \@@_set_filter:nnnnnN
+      \l_@@_unused_clist {#4} {#1} {#2} {#3} #5
+  }
+\cs_generate_variant:Nn \keys_set_filter:nnnnN { nnV , nnv , nno }
+\cs_new_protected:Npn \@@_set_filter:nnnnnN #1#2#3#4#5#6
+  {
+    \clist_clear:N \l_@@_unused_clist
+    \@@_set_filter:nnnn {#2} {#3} {#4} {#5}
+    \tl_set:Nx #6 { \exp_not:o { \l_@@_unused_clist } }
+    \tl_set:Nn \l_@@_unused_clist {#1}
+  }
+\cs_new_protected:Npn \keys_set_filter:nnn #1#2#3
+  {\@@_set_filter:nnnn { \q_no_value } {#1} {#2} {#3} }
+\cs_generate_variant:Nn \keys_set_filter:nnn { nnV , nnv , nno }
+\cs_new_protected:Npn \@@_set_filter:nnnn #1#2#3#4
+  {
+    \use:x
+      {
+        \bool_set_false:N \exp_not:N \l_@@_only_known_bool
+        \bool_set_true:N \exp_not:N \l_@@_filtered_bool
+        \bool_set_true:N \exp_not:N \l_@@_selective_bool
+        \tl_set:Nn \exp_not:N \l_@@_relative_tl { \exp_not:n {#1} }
+        \@@_set_selective:nnn \exp_not:n { {#2} {#3} {#4} }
+        \bool_if:NT \l_@@_only_known_bool
+          { \bool_set_true:N \exp_not:N \l_@@_only_known_bool }
+        \bool_if:NF \l_@@_filtered_bool
+          { \bool_set_false:N \exp_not:N \l_@@_filtered_bool }
+        \bool_if:NF \l_@@_selective_bool
+          { \bool_set_false:N \exp_not:N \l_@@_selective_bool }
+        \tl_set:Nn \exp_not:N \l_@@_relative_tl
+          { \exp_not:o \l_@@_relative_tl }
+      }
+  }
+\cs_new_protected:Npn \keys_set_groups:nnn #1#2#3
+  {
+    \use:x
+      {
+        \bool_set_false:N \exp_not:N \l_@@_only_known_bool
+        \bool_set_false:N \exp_not:N \l_@@_filtered_bool
+        \bool_set_true:N \exp_not:N \l_@@_selective_bool
+        \tl_set:Nn \exp_not:N \l_@@_relative_tl
+          { \exp_not:N \q_no_value }
+        \@@_set_selective:nnn \exp_not:n { {#1} {#2} {#3} }
+        \bool_if:NT \l_@@_only_known_bool
+          { \bool_set_true:N \exp_not:N \l_@@_only_known_bool }
+        \bool_if:NF \l_@@_filtered_bool
+          { \bool_set_true:N \exp_not:N \l_@@_filtered_bool }
+        \bool_if:NF \l_@@_selective_bool
+          { \bool_set_false:N \exp_not:N \l_@@_selective_bool }
+        \tl_set:Nn \exp_not:N \l_@@_relative_tl
+          { \exp_not:o \l_@@_relative_tl }
+      }
+  }
+\cs_generate_variant:Nn \keys_set_groups:nnn { nnV , nnv , nno }
+\cs_new_protected:Npn \@@_set_selective:nnn
+  { \exp_args:No \@@_set_selective:nnnn \l_@@_selective_seq }
+\cs_new_protected:Npn \@@_set_selective:nnnn #1#2#3#4
+  {
+    \seq_set_from_clist:Nn \l_@@_selective_seq {#3}
+    \@@_set:nn {#2} {#4}
+    \tl_set:Nn \l_@@_selective_seq {#1}
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_set_keyval:n, \@@_set_keyval:nn}
+% \begin{macro}{\@@_set_keyval:nnn, \@@_set_keyval:onn}
+% \begin{macro}{\@@_find_key_module:NNw}
+% \begin{macro}{\@@_set_selective:}
+%   A shared system once again. First, set the current path and add a
+%   default if needed. There are then checks to see if the a value is
+%   required or forbidden. If everything passes, move on to execute the
+%   code.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_set_keyval:n #1
+  {
+    \bool_set_true:N \l_@@_no_value_bool
+    \@@_set_keyval:onn \l_@@_module_tl {#1} { }
+  }
+\cs_new_protected:Npn \@@_set_keyval:nn #1#2
+  {
+    \bool_set_false:N \l_@@_no_value_bool
+    \@@_set_keyval:onn \l_@@_module_tl {#1} {#2}
+  }
+%    \end{macrocode}
+%   The key path here can be fully defined, after which there is a search
+%   for the key and module names: the user may have passed them with part
+%   of what is actually the module (for our purposes) in the key name. As
+%   that happens on a per-key basis, we use the stack approach to restore
+%   the module name without a group.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_set_keyval:nnn #1#2#3
+  {
+    \tl_set:Nx \l_keys_path_tl
+      {
+        \tl_if_blank:nF {#1}
+          { #1 / }
+        \@@_trim_spaces:n {#2}
+      }
+    \tl_clear:N \l_@@_module_tl
+    \tl_clear:N \l_@@_inherit_tl
+    \exp_after:wN \@@_find_key_module:NNw
+      \exp_after:wN \l_@@_module_tl
+      \exp_after:wN \l_keys_key_tl
+      \l_keys_path_tl / \q_stop
+    \@@_value_or_default:n {#3}
+    \bool_if:NTF \l_@@_selective_bool
+      { \@@_set_selective: }
+      { \@@_execute: }
+    \tl_set:Nn \l_@@_module_tl {#1}
+  }
+\cs_generate_variant:Nn \@@_set_keyval:nnn { o }
+\cs_new_protected:Npn \@@_find_key_module:NNw #1#2#3 / #4 \q_stop
+  {
+    \tl_if_blank:nTF {#4}
+      { \tl_set:Nn #2 {#3} }
+      {
+        \tl_put_right:Nx #1
+          {
+            \tl_if_empty:NF #1 { / }
+            #3
+          }
+        \@@_find_key_module:NNw #1#2 #4 \q_stop
+      }
+  }
+%    \end{macrocode}
+%  If selective setting is active, there are a number of possible sub-cases
+%  to consider. The key name may not be known at all or if it is, it may not
+%  have any groups assigned. There is then the question of whether the
+%  selection is opt-in or opt-out.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_set_selective:
+  {
+    \cs_if_exist:cTF { \c_@@_groups_root_tl \l_keys_path_tl }
+      {
+        \clist_set_eq:Nc \l_@@_groups_clist
+          { \c_@@_groups_root_tl \l_keys_path_tl }
+        \@@_check_groups:
+      }
+      {
+        \bool_if:NTF \l_@@_filtered_bool
+          { \@@_execute: }
+          { \@@_store_unused: }
+      }
+  }
+%    \end{macrocode}
+%    In the case where selective setting requires a comparison of the list
+%    of groups which apply to a key with the list of those which have been
+%    set active. That requires two mappings, and again a different outcome
+%    depending on whether opt-in or opt-out is set.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_check_groups:
+  {
+    \bool_set_false:N \l_@@_tmp_bool
+    \seq_map_inline:Nn \l_@@_selective_seq
+      {
+        \clist_map_inline:Nn \l_@@_groups_clist
+          {
+            \str_if_eq:nnT {##1} {####1}
+              {
+                \bool_set_true:N \l_@@_tmp_bool
+                \clist_map_break:n { \seq_map_break: }
+              }
+          }
+      }
+    \bool_if:NTF \l_@@_tmp_bool
+      {
+        \bool_if:NTF \l_@@_filtered_bool
+          { \@@_store_unused: }
+          { \@@_execute: }
+      }
+      {
+        \bool_if:NTF \l_@@_filtered_bool
+          { \@@_execute: }
+          { \@@_store_unused: }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_value_or_default:n}
+% \begin{macro}{\@@_default_inherit:}
+%   If a value is given, return it as |#1|, otherwise send a default if
+%   available.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_value_or_default:n #1
+  {
+    \bool_if:NTF \l_@@_no_value_bool
+      {
+        \cs_if_exist:cTF { \c_@@_default_root_tl \l_keys_path_tl }
+          {
+            \tl_set_eq:Nc
+              \l_keys_value_tl
+              { \c_@@_default_root_tl \l_keys_path_tl }
+          }
+          {
+            \tl_clear:N \l_keys_value_tl
+            \cs_if_exist:cT
+              { \c_@@_inherit_root_tl \@@_parent:o \l_keys_path_tl }
+              { \@@_default_inherit: }
+          }
+      }
+      { \tl_set:Nn \l_keys_value_tl {#1} }
+  }
+\cs_new_protected:Npn \@@_default_inherit:
+  {
+    \clist_map_inline:cn
+      { \c_@@_inherit_root_tl \@@_parent:o \l_keys_path_tl }
+      {
+        \cs_if_exist:cT
+          { \c_@@_default_root_tl ##1 / \l_keys_key_tl }
+          {
+            \tl_set_eq:Nc
+              \l_keys_value_tl
+              { \c_@@_default_root_tl ##1 / \l_keys_key_tl }
+            \clist_map_break:
+          }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_execute:, \@@_execute_inherit:, \@@_execute_unknown:}
+% \begin{macro}[EXP]{\@@_execute:nn}
+% \begin{macro}{\@@_store_unused:,\@@_store_unused_aux:}
+%   Actually executing a key is done in two parts. First, look for the
+%   key itself, then look for the \texttt{unknown} key with the same
+%   path. If both of these fail, complain. What exactly happens if a key
+%   is unknown depends on whether unknown keys are being skipped or if
+%   an error should be raised.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_execute:
+  {
+    \cs_if_exist:cTF { \c_@@_code_root_tl \l_keys_path_tl }
+      {
+        \cs_if_exist_use:c { \c_@@_validate_root_tl \l_keys_path_tl }
+        \cs:w \c_@@_code_root_tl \l_keys_path_tl \exp_after:wN \cs_end:
+          \exp_after:wN { \l_keys_value_tl }
+      }
+      {
+        \cs_if_exist:cTF
+          { \c_@@_inherit_root_tl \@@_parent:o \l_keys_path_tl }
+          { \@@_execute_inherit: }
+          { \@@_execute_unknown: }
+      }
+  }
+%    \end{macrocode}
+%   To deal with the case where there is no hit, we leave
+%   \cs{@@_execute_unknown:} in the input stream and clean it up using the
+%    break function: that avoids needing a boolean.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_execute_inherit:
+  {
+    \clist_map_inline:cn
+      { \c_@@_inherit_root_tl \@@_parent:o \l_keys_path_tl }
+      {
+        \cs_if_exist:cT
+          { \c_@@_code_root_tl ##1 / \l_keys_key_tl }
+          {
+            \tl_set:Nn \l_@@_inherit_tl {##1}
+            \cs:w \c_@@_code_root_tl ##1 / \l_keys_key_tl
+              \exp_after:wN \cs_end: \exp_after:wN
+              { \l_keys_value_tl }
+            \clist_map_break:n { \use_none:n }
+          }
+      }
+    \@@_execute_unknown:
+  }
+\cs_new_protected:Npn \@@_execute_unknown:
+  {
+    \bool_if:NTF \l_@@_only_known_bool
+      { \@@_store_unused: }
+      {
+        \cs_if_exist:cTF
+          { \c_@@_code_root_tl \l_@@_module_tl / unknown }
+          {
+            \cs:w \c_@@_code_root_tl \l_@@_module_tl / unknown
+              \exp_after:wN \cs_end: \exp_after:wN { \l_keys_value_tl }
+          }
+          {
+            \__kernel_msg_error:nnxx { kernel } { key-unknown }
+             { \l_keys_path_tl } { \l_@@_module_tl }
+          }
+      }
+  }
+\cs_new:Npn \@@_execute:nn #1#2
+  {
+    \cs_if_exist:cTF { \c_@@_code_root_tl #1 }
+      {
+        \cs:w \c_@@_code_root_tl #1 \exp_after:wN \cs_end:
+          \exp_after:wN { \l_keys_value_tl }
+      }
+      {#2}
+  }
+%    \end{macrocode}
+%   When there is no relative path, things here are easy: just save the key
+%   name and value. When we are working with a relative path, first we
+%   need to turn it into a string: that can't happen earlier as we need
+%   to store \cs{q_no_value}. Then, use a standard delimited approach to fish
+%   out the partial path.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_store_unused:
+  {
+    \quark_if_no_value:NTF \l_@@_relative_tl
+      {
+        \clist_put_right:Nx \l_@@_unused_clist
+          {
+            \exp_not:o \l_keys_key_tl
+            \bool_if:NF \l_@@_no_value_bool
+              { = { \exp_not:o \l_keys_value_tl } }
+          }
+      }
+      {
+        \tl_if_empty:NTF \l_@@_relative_tl
+          {
+            \clist_put_right:Nx \l_@@_unused_clist
+              {
+                \exp_not:o \l_keys_path_tl
+                \bool_if:NF \l_@@_no_value_bool
+                  { = { \exp_not:o \l_keys_value_tl } }
+              }
+          }
+          { \@@_store_unused_aux: }
+      }
+  }
+\cs_new_protected:Npn \@@_store_unused_aux:
+  {
+    \tl_set:Nx \l_@@_relative_tl
+      { \exp_args:No \@@_trim_spaces:n \l_@@_relative_tl }
+    \use:x 
+      {
+        \cs_set_protected:Npn \@@_store_unused:w
+          ####1 \l_@@_relative_tl /
+          ####2 \l_@@_relative_tl /
+          ####3 \exp_not:N \q_stop
+      }
+        {
+          \tl_if_blank:nF {##1}
+            {
+              \__kernel_msg_error:nnxx { kernel } { bad-relative-key-path }
+                \l_keys_path_tl
+                \l_@@_relative_tl
+            }
+          \clist_put_right:Nx \l_@@_unused_clist
+            {
+              \exp_not:n {##2}
+              \bool_if:NF \l_@@_no_value_bool
+                { = { \exp_not:o \l_keys_value_tl } }
+            }
+        }
+    \use:x
+      {
+        \@@_store_unused:w \l_keys_path_tl
+          \l_@@_relative_tl / \l_@@_relative_tl /
+          \exp_not:N \q_stop
+      }
+  }
+\cs_new_protected:Npn \@@_store_unused:w { }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_choice_find:n}
+% \begin{macro}[EXP]{\@@_choice_find:nn}
+% \begin{macro}[EXP]{\@@_multichoice_find:n}
+%   Executing a choice has two parts. First, try the choice given, then
+%   if that fails call the unknown key. That always exists, as it is created
+%   when a choice is first made. So there is no need for any escape code.
+%   For multiple choices, the same code ends up used in a mapping.
+%    \begin{macrocode}
+\cs_new:Npn \@@_choice_find:n #1
+  {
+    \tl_if_empty:NTF \l_@@_inherit_tl
+      { \@@_choice_find:nn { \l_keys_path_tl } {#1} }
+      {
+        \@@_choice_find:nn
+          { \l_@@_inherit_tl / \l_keys_key_tl } {#1}
+      }
+  }
+\cs_new:Npn \@@_choice_find:nn #1#2
+  {
+    \cs_if_exist:cTF { \c_@@_code_root_tl #1 / \@@_trim_spaces:n {#2} }
+      { \use:c { \c_@@_code_root_tl #1 / \@@_trim_spaces:n {#2} } {#2} }
+      { \use:c { \c_@@_code_root_tl #1 / unknown } {#2} }
+  }
+\cs_new:Npn \@@_multichoice_find:n #1
+  { \clist_map_function:nN {#1} \@@_choice_find:n }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Utilities}
+%
+% \begin{macro}[EXP]{\@@_parent:n, \@@_parent:o}
+% \begin{macro}[EXP]{\@@_parent:w}
+%   Used to strip off the ending part of the key path after the last~|/|.
+%    \begin{macrocode}
+\cs_new:Npn \@@_parent:n #1
+  { \@@_parent:w #1 / / \q_stop { } }
+\cs_generate_variant:Nn \@@_parent:n { o }
+\cs_new:Npn \@@_parent:w #1 / #2 / #3 \q_stop #4
+  {
+    \tl_if_blank:nTF {#2}
+      {
+        \tl_if_blank:nF {#4}
+          { \use_none:n #4 }
+      }
+      {
+        \@@_parent:w #2 / #3 \q_stop { #4 / #1 }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_trim_spaces:n}
+% \begin{macro}[EXP]
+%   {
+%     \@@_trim_spaces_auxi:w   ,
+%     \@@_trim_spaces_auxii:w  ,
+%     \@@_trim_spaces_auxiii:w
+%   }
+%   Space stripping has to allow for the fact that the key here might have
+%   several parts, and spaces need to be stripped from each part.
+%    \begin{macrocode}
+\cs_new:Npn \@@_trim_spaces:n #1
+  {
+    \exp_after:wN \@@_trim_spaces_auxi:w \tl_to_str:n {#1}
+      / \q_nil \q_stop
+    
+  }
+\cs_new:Npn \@@_trim_spaces_auxi:w #1 / #2 \q_stop
+  {
+    \quark_if_nil:nTF {#2}
+      { \tl_trim_spaces:n {#1} }
+      { \@@_trim_spaces_auxii:w #1 / #2 }
+  }
+\cs_new:Npn \@@_trim_spaces_auxii:w #1 / #2 / \q_nil
+  {
+    \tl_trim_spaces:n {#1}
+    \@@_trim_spaces_auxiii:w #2 / \q_recursion_tail / \q_recursion_stop
+  }
+\cs_set:Npn \@@_trim_spaces_auxiii:w #1 /
+  {
+    \quark_if_recursion_tail_stop:n {#1}
+    \tl_trim_spaces:n { / #1 }
+    \@@_trim_spaces_auxiii:w
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP,pTF]{\keys_if_exist:nn}
+%   A utility for others to see if a key exists.
+%    \begin{macrocode}
+\prg_new_conditional:Npnn \keys_if_exist:nn #1#2 { p , T , F , TF }
+  {
+    \cs_if_exist:cTF
+      { \c_@@_code_root_tl \@@_trim_spaces:n { #1 / #2 } }
+      { \prg_return_true: }
+      { \prg_return_false: }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP,pTF]{\keys_if_choice_exist:nnn}
+%   Just an alternative view on \cs{keys_if_exist:nnTF}.
+%    \begin{macrocode}
+\prg_new_conditional:Npnn \keys_if_choice_exist:nnn #1#2#3
+  { p , T , F , TF }
+  {
+    \cs_if_exist:cTF
+      { \c_@@_code_root_tl \@@_trim_spaces:n { #1 / #2 / #3 } }
+      { \prg_return_true: }
+      { \prg_return_false: }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\keys_show:nn, \keys_log:nn, \@@_show:Nnn}
+%   To show a key, show its code using a message.
+%    \begin{macrocode}
+\cs_new_protected:Npn \keys_show:nn
+  { \@@_show:Nnn \msg_show:nnxxxx }
+\cs_new_protected:Npn \keys_log:nn
+  { \@@_show:Nnn \msg_log:nnxxxx }
+\cs_new_protected:Npn \@@_show:Nnn #1#2#3
+  {
+    #1 { LaTeX / kernel } { show-key }
+      { \@@_trim_spaces:n { #2 / #3 } }
+      {
+        \keys_if_exist:nnT {#2} {#3}
+          {
+            \exp_args:Nnf \msg_show_item_unbraced:nn { code }
+              {
+                \exp_args:Nc \cs_replacement_spec:N
+                  {
+                    \c_@@_code_root_tl
+                    \@@_trim_spaces:n { #2 / #3 }
+                  }
+              }
+          }
+      }
+      { } { }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \subsection{Messages}
+%
+% For when there is a need to complain.
+%    \begin{macrocode}
+\__kernel_msg_new:nnnn { kernel } { bad-relative-key-path }
+  { The~key~'#1'~is~not~inside~the~'#2'~path. }
+  { The~key~'#1'~cannot~be~expressed~relative~to~path~'#2'. }
+\__kernel_msg_new:nnnn { kernel } { boolean-values-only }
+  { Key~'#1'~accepts~boolean~values~only. }
+  { The~key~'#1'~only~accepts~the~values~'true'~and~'false'. }
+\__kernel_msg_new:nnnn { kernel } { key-choice-unknown }
+  { Key~'#1'~accepts~only~a~fixed~set~of~choices. }
+  {
+    The~key~'#1'~only~accepts~predefined~values,~
+    and~'#2'~is~not~one~of~these.
+  }
+\__kernel_msg_new:nnnn { kernel } { key-unknown }
+  { The~key~'#1'~is~unknown~and~is~being~ignored. }
+  {
+    The~module~'#2'~does~not~have~a~key~called~'#1'.\\
+    Check~that~you~have~spelled~the~key~name~correctly.
+  }
+\__kernel_msg_new:nnnn { kernel } { nested-choice-key }
+  { Attempt~to~define~'#1'~as~a~nested~choice~key. }
+  {
+    The~key~'#1'~cannot~be~defined~as~a~choice~as~the~parent~key~'#2'~is~
+    itself~a~choice.
+  }
+\__kernel_msg_new:nnnn { kernel } { value-forbidden }
+  { The~key~'#1'~does~not~take~a~value. }
+  {
+    The~key~'#1'~should~be~given~without~a~value.\\
+    The~value~'#2'~was~present:~the~key~will~be~ignored.
+  }
+\__kernel_msg_new:nnnn { kernel } { value-required }
+  { The~key~'#1'~requires~a~value. }
+  {
+    The~key~'#1'~must~have~a~value.\\
+    No~value~was~present:~the~key~will~be~ignored.
+  }
+\__kernel_msg_new:nnn { kernel } { show-key }
+  {
+    The~key~#1~
+    \tl_if_empty:nTF {#2}
+      { is~undefined. }
+      { has~the~properties: #2 . }
+  }
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%</initex|package>
+%    \end{macrocode}
+%
+%\end{implementation}
+%
+%\PrintIndex
-- 
cgit v1.2.3

