diff options
Diffstat (limited to 'Master/texmf-dist/source/latex/l3kernel/l3keys.dtx')
-rw-r--r-- | Master/texmf-dist/source/latex/l3kernel/l3keys.dtx | 665 |
1 files changed, 364 insertions, 301 deletions
diff --git a/Master/texmf-dist/source/latex/l3kernel/l3keys.dtx b/Master/texmf-dist/source/latex/l3kernel/l3keys.dtx index 7b834f6c7a7..0f109a3d695 100644 --- a/Master/texmf-dist/source/latex/l3kernel/l3keys.dtx +++ b/Master/texmf-dist/source/latex/l3kernel/l3keys.dtx @@ -1,6 +1,6 @@ % \iffalse meta-comment % -%% File: l3keys.dtx Copyright (C) 2006-2015 The LaTeX3 Project +%% File: l3keys.dtx Copyright (C) 2006-2016 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 @@ -37,7 +37,7 @@ \documentclass[full]{l3doc} %</driver> %<*driver|package> -\GetIdInfo$Id: l3keys.dtx 6284 2015-11-17 10:10:56Z bruno $ +\GetIdInfo$Id: l3keys.dtx 6711 2016-09-21 07:04:35Z joseph $ {L3 Key-value interfaces} %</driver|package> %<*driver> @@ -179,7 +179,7 @@ % Some other properties are mutually exclusive, notably |.value_required:n| % and |.value_forbidden:n|, and so will replace one another. However, % properties covering non-exclusive behaviours may be given in any order. Thus -% for example the following defintions are equivalent. +% for example the following definitions are equivalent. % \begin{verbatim} % \keys_define:nn { mymodule } % { @@ -873,20 +873,18 @@ % % \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} % -% For historical reasons this code uses the `keyval' module prefix. -% -% \begin{variable}{\g_@@_level_int} -% To allow nesting of \cs{keyval_parse:NNn}, an integer is needed for -% the current level. -% \begin{macrocode} -\int_new:N \g_@@_level_int -% \end{macrocode} -% \end{variable} -% % \begin{variable}{\l_@@_key_tl, \l_@@_value_tl} % The current key name and value. % \begin{macrocode} @@ -896,166 +894,206 @@ % \end{variable} % % \begin{variable}{\l_@@_sanitise_tl} -% \begin{variable}{\l_@@_parse_tl} -% Token list variables for dealing with awkward category codes in the +% A token list variable for dealing with awkward category codes in the % input. % \begin{macrocode} \tl_new:N \l_@@_sanitise_tl -\tl_new:N \l_@@_parse_tl % \end{macrocode} % \end{variable} -% \end{variable} % -% \begin{macro}{\@@_parse:n} -% The parsing function first deals with the category codes for -% |=| and |,|, so that there are no odd events. The input is then -% handed off to the element by element system. +% \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. This particular quark is chosen as +% it fits in with \cs{__tl_trim_spaces:nn} and allows a performance enhancement +% as the token can be carried through. 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}[aux]{\@@_sanitise_equals:, \@@_sanitise_comma:} +% \begin{macro}[aux] +% { +% \@@_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:Npx \@@_parse:n #1 + \cs_new_protected:Npn \@@_sanitise_equals: { - \group_begin: - \tl_set:Nn \exp_not:N \l_@@_sanitise_tl {#1} - \tl_replace_all:Nnn \exp_not:N \l_@@_sanitise_tl - { \exp_not:N = } { \token_to_str:N = } - \tl_replace_all:Nnn \exp_not:N \l_@@_sanitise_tl - { \exp_not:N , } { \token_to_str:N , } - \tl_clear:N \exp_not:N \l_@@_parse_tl - \exp_not:N \exp_after:wN - \exp_not:N \@@_parse_elt:w \exp_not:N \exp_after:wN - \exp_not:N \q_nil \exp_not:N \l_@@_sanitise_tl - \token_to_str:N , \exp_not:N \q_recursion_tail - \token_to_str:N , \exp_not:N \q_recursion_stop - \exp_not:N \exp_after:wN \group_end: - \exp_not:N \l_@@_parse_tl + \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}{\@@_parse_elt:w} -% Each item to be parsed will have \cs{q_nil} added to the front. -% Hence the blank test here can always be used to find a totally -% empty argument. To allow rapid matching for an |=| while not stripping -% any braces, another \cs{q_nil} needed before the next phase of the -% parser. Finally, loop around for the next item, adding in the -% \cs{q_nil}: this happens whatever the nature of the current argument -% as the end-of-recursion will clear up in all cases. +% \begin{macro}[aux]{\@@_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 \@@_parse_elt:w #1 , +\cs_new_protected:Npn \@@_loop:NNw #1#2#3 , { - \tl_if_blank:oF { \use_none:n #1 } - { - \quark_if_recursion_tail_stop:o { \use_none:n #1 } - \@@_split_key_value:w #1 \q_nil = = \q_stop - } - \@@_parse_elt:w \q_nil + \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_key_value:w} -% \begin{macro}[aux]{\@@_split_key:w} -% Split the key and value using a delimited argument. The \cs{q_nil} -% values added earlier ensure that no braces will be stripped as part -% of this process. A blank test can then be used on |#3|: it is only -% empty if there was no |=| in the original input. In that case, strip -% a \cs{q_nil} from the end of the key name then hand on to remove other -% things and store as \cs{l_@@_key_tl} before adding to the output token -% list. In the case where there is an |=|, first tidy up the key, this time -% without a trailing \cs{q_nil}, then do a check to ensure that |#3| is -% exactly one token (|=|). With that done, the final stage is to hand off -% to tidy up the value. +% \begin{macro}[aux]{\@@_split:NNw, \@@_split_value:NNw} +% \begin{macro}[aux]{\@@_split_tidy:w} +% \begin{macro}[aux]{\@@_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_key_value:w #1 = #2 = #3 \q_stop +\cs_new_protected:Npn \@@_split:NNw #1#2#3 = { - \tl_if_blank:nTF {#3} - { - \@@_split_key:w #1 \q_stop - \tl_put_right:Nx \l_@@_parse_tl + \@@_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: \etex_detokenize:D \exp_after:wN { \use_none:n #4 } + \scan_stop: + \@@_def:Nn \l_@@_value_tl {#3} + \cs_set:Npx \@@_action: { - \exp_not:c - { - @@_key_no_value_elt_ - \int_use:N \g_@@_level_int - :n - } + \exp_not:N #2 { \exp_not:o \l_@@_key_tl } + { \exp_not:o \l_@@_value_tl } } - } - { - \@@_split:Nn \l_@@_key_tl {#1} - \tl_if_blank:oTF { \use_none:n #3 } - { \@@_split_value:w \q_nil #2 \q_stop } + \else: + \cs_set:Npn \@@_action: { \__msg_kernel_error:nn { kernel } { misplaced-equals-sign } } - } + \fi: + \fi: + \@@_action: } -\cs_new_protected:Npn \@@_split_key:w #1 \q_nil \q_stop - { \@@_split:Nn \l_@@_key_tl {#1} } -% \end{macrocode} -% \end{macro} -% \end{macro} -% -% \begin{macro}{\@@_split:Nn} -% \begin{macro}[aux]{\@@_split:Nw} -% There are two possible cases here. The first case is that |#1| is -% surrounded by braces, in which case the |\use_none:nnn #1 \q_nil \q_nil| -% will yield \cs{q_nil}. There, we can remove the leading \cs{q_nil}, the -% braces and any spaces around the outside with \cs{use_ii:nnn}. On the -% other hand, if there are no braces then the second branch removes the -% leading \cs{q_nil} and any surrounding spaces. -% \begin{macrocode} -\cs_new_protected:Npn \@@_split:Nn #1#2 +\cs_new_protected:Npn \@@_split_tidy:w #1 \q_stop { - \quark_if_nil:oTF { \use_none:nnn #2 \q_nil \q_nil } - { \tl_set:Nx #1 { \exp_not:o { \use_ii:nnn #2 \q_nil } } } - { \@@_split:Nw #1 #2 \q_stop } + \if:w \scan_stop: \etex_detokenize:D \exp_after:wN { \use_none:n #1 } + \scan_stop: + \else: + \exp_after:wN \@@_empty_key: + \fi: } -\cs_new_protected:Npn \@@_split:Nw #1 \q_nil #2 \q_stop - { \tl_set:Nx #1 { \tl_trim_spaces:n {#2} } } +\cs_new:Npn \@@_action: { } +\cs_new_protected:Npn \@@_empty_key: + { \__msg_kernel_error:nn { kernel } { misplaced-equals-sign } } % \end{macrocode} % \end{macro} % \end{macro} -% -% \begin{macro}{\@@_split_value:w} -% As this stage there is just the value to deal with. The leading and -% trailing \cs{q_nil} tokens are removed in two steps before storing the -% value with spaces stripped (see \cs{@@_split:Nn}). Doing the storage -% of key and value in one shot will put exactly the right number of -% brace groups into the output. -% \begin{macrocode} -\cs_new_protected:Npn \@@_split_value:w #1 \q_nil \q_stop - { - \@@_split:Nn \l_@@_value_tl {#1} - \tl_put_right:Nx \l_@@_parse_tl - { - \exp_not:c - { @@_key_value_elt_ \int_use:N \g_@@_level_int :nn } - { \exp_not:o \l_@@_key_tl } - { \exp_not:o \l_@@_value_tl } - } - } -% \end{macrocode} % \end{macro} % -% \begin{macro}{\keyval_parse:NNn} -% The outer parsing routine just sets up the processing functions and -% hands off. +% \begin{macro}[aux]{\@@_def:Nn} +% \begin{macro}[aux, EXP]{\@@_def_aux:n} +% \begin{macro}[aux, EXP]{\@@_def_aux:w} +% First trim spaces off, then potentially remove a set of braces. By using +% the internal interface |\__tl_trim_spaces:nn| we can take advantage of the +% fact it needs a leading |\q_mark| in this process. The |\exp_after:wN| +% removes the quark, the delimited argument deals with any braces. % \begin{macrocode} -\cs_new_protected:Npn \keyval_parse:NNn #1#2#3 - { - \int_gincr:N \g_@@_level_int - \cs_gset_eq:cN - { @@_key_no_value_elt_ \int_use:N \g_@@_level_int :n } #1 - \cs_gset_eq:cN - { @@_key_value_elt_ \int_use:N \g_@@_level_int :nn } #2 - \@@_parse:n {#3} - \int_gdecr:N \g_@@_level_int - } +\cs_new_protected:Npn \@@_def:Nn #1#2 + { \tl_set:Nx #1 { \__tl_trim_spaces:nn {#2} \@@_def_aux:n } } +\cs_new:Npn \@@_def_aux:n #1 + { \exp_after:wN \@@_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} @@ -1073,11 +1111,21 @@ %<@@=keys> % \end{macrocode} % -% \begin{variable}{\c_@@_code_root_tl, \c_@@_info_root_tl} -% The prefixes for the code and variables of the keys themselves. +% \begin{variable} +% { +% \c_@@_code_root_tl , +% \c_@@_default_root_tl , +% \c_@@_groups_root_tl, +% \c_@@_info_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_@@_info_root_tl { key~info~>~ } +\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_@@_info_root_tl { key~info~>~ } +\tl_const:Nn \c_@@_validate_root_tl { key~validate~>~ } % \end{macrocode} % \end{variable} % @@ -1203,7 +1251,7 @@ \cs_new_protected:Npn \@@_define:nnn #1#2#3 { \tl_set:Nx \l_@@_module_tl { \@@_remove_spaces:n {#2} } - \keyval_parse:NNn \@@_define_elt:n \@@_define_elt:nn {#3} + \keyval_parse:NNn \@@_define:n \@@_define:nn {#3} \tl_set:Nn \l_@@_module_tl {#1} } \cs_generate_variant:Nn \@@_define:nnn { o } @@ -1211,33 +1259,33 @@ % \end{macro} % \end{macro} % -% \begin{macro}[int]{\@@_define_elt:n} -% \begin{macro}[int]{\@@_define_elt:nn} -% \begin{macro}[aux]{\@@_define_elt_aux:nn} +% \begin{macro}[int]{\@@_define:n} +% \begin{macro}[int]{\@@_define:nn} +% \begin{macro}[aux]{\@@_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_elt:n #1 +\cs_new_protected:Npn \@@_define:n #1 { \bool_set_true:N \l_@@_no_value_bool - \@@_define_elt_aux:nn {#1} { } + \@@_define_aux:nn {#1} { } } -\cs_new_protected:Npn \@@_define_elt:nn #1#2 +\cs_new_protected:Npn \@@_define:nn #1#2 { \bool_set_false:N \l_@@_no_value_bool - \@@_define_elt_aux:nn {#1} {#2} + \@@_define_aux:nn {#1} {#2} } -\cs_new_protected:Npn \@@_define_elt_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_key:n {#2} } + { \@@_define_code:n {#2} } { - \str_if_eq_x:nnF { \l_@@_property_tl } { .abort: } - { - \__msg_kernel_error:nnxx { kernel } { property-unknown } + \tl_if_empty:NF \l_@@_property_tl + { + \__msg_kernel_error:nnxx { kernel } { property-unknown } { \l_@@_property_tl } { \l_keys_path_tl } } } @@ -1255,45 +1303,57 @@ % \begin{macrocode} \cs_new_protected:Npn \@@_property_find:n #1 { - \tl_set:Nx \l_keys_path_tl { \l_@@_module_tl / } - \tl_if_in:nnTF {#1} { . } - { \@@_property_find:w #1 \q_stop } + \tl_set:Nx \l_@@_property_tl { \@@_remove_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 + \__msg_kernel_error:nnn { kernel } { key-no-property } {#4} + } { - \__msg_kernel_error:nnx { kernel } { key-no-property } {#1} - \tl_set:Nn \l_@@_property_tl { .abort: } + \str_if_eq:nnTF {#3} { . } + { + \tl_set:Nx \l_keys_path_tl { \l_@@_module_tl / #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_find:w #1 . #2 \q_stop +\cs_new_protected:Npn \@@_property_search:w #1 . #2 \q_stop { - \tl_set:Nx \l_keys_path_tl + \str_if_eq:nnTF {#2} { . } { - \l_keys_path_tl - \@@_remove_spaces:n {#1} + \tl_set:Nx \l_keys_path_tl { \l_keys_path_tl } + \tl_set:Nn \l_@@_property_tl { . #1 } } - \tl_if_in:nnTF {#2} { . } { - \tl_set:Nx \l_keys_path_tl { \l_keys_path_tl . } - \@@_property_find:w #2 \q_stop + \tl_set:Nx \l_keys_path_tl { \l_keys_path_tl . #1 } + \@@_property_search:w #2 \q_stop } - { \tl_set:Nn \l_@@_property_tl { . #2 } } } % \end{macrocode} % \end{macro} % \end{macro} % -% \begin{macro}[int]{\@@_define_key:n} -% \begin{macro}[aux]{\@@_define_key:w} +% \begin{macro}[int]{\@@_define_code:n} +% \begin{macro}[aux, 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 % is missing the earlier tests will have failed. % \begin{macrocode} -\cs_new_protected:Npn \@@_define_key:n #1 +\cs_new_protected:Npn \@@_define_code:n #1 { \bool_if:NTF \l_@@_no_value_bool { - \exp_after:wN \@@_define_key:w + \exp_after:wN \@@_define_code:w \l_@@_property_tl \q_stop { \use:c { \c_@@_props_root_tl \l_@@_property_tl } } { @@ -1304,7 +1364,11 @@ } { \use:c { \c_@@_props_root_tl \l_@@_property_tl } {#1} } } -\cs_new_protected:Npn \@@_define_key:w #1 : #2 \q_stop +\use:x + { + \cs_new:Npn \exp_not:N \__keys_define_code:w + ##1 \c_colon_str ##2 \exp_not:N \q_stop + } { \tl_if_empty:nTF {#2} } % \end{macrocode} % \end{macro} @@ -1322,7 +1386,7 @@ % current scope but exists at a higher level, we do not use \cs{prop_new:c} % but rather \cs{prop_set_eq:cN}. The function \cs{__chk_log:x} only % writes to the log file if logging all new functions is active: without -% it keys would not show up (as we are not using \cs{..._new}). +% it keys would not show up (as we are not using \cs[no-index]{..._new}). % \begin{macrocode} \cs_new_protected:Npn \@@_ensure_exist:n #1 { @@ -1330,6 +1394,7 @@ { \prop_set_eq:cN { \c_@@_info_root_tl #1 } \c_empty_prop } + \cs_if_exist:cF { \c_@@_code_root_tl #1 } { \__chk_log:x { Defining~key~#1~ \msg_line_context: } @@ -1396,11 +1461,11 @@ % nested! As multichoices and choices are essentially the same bar one % function, the code is given together. % \begin{macrocode} -\cs_new_protected_nopar:Npn \@@_choice_make: +\cs_new_protected:Npn \@@_choice_make: { \@@_choice_make:N \@@_choice_find:n } -\cs_new_protected_nopar:Npn \@@_multichoice_make: +\cs_new_protected:Npn \@@_multichoice_make: { \@@_choice_make:N \@@_multichoice_find:n } -\cs_new_protected_nopar:Npn \@@_choice_make:N #1 +\cs_new_protected:Npn \@@_choice_make:N #1 { \prop_if_exist:cTF { \c_@@_info_root_tl \@@_parent:o \l_keys_path_tl } @@ -1416,7 +1481,7 @@ } { \@@_choice_make_aux:N #1 } } -\cs_new_protected_nopar:Npn \@@_choice_make_aux:N #1 +\cs_new_protected:Npn \@@_choice_make_aux:N #1 { \@@_cmd_set:nn { \l_keys_path_tl } { #1 {##1} } \prop_put:cnn { \c_@@_info_root_tl \l_keys_path_tl } { choice } @@ -1450,9 +1515,9 @@ % Auto-generating choices means setting up the root key as a choice, then % defining each choice in turn. % \begin{macrocode} -\cs_new_protected_nopar:Npn \@@_choices_make:nn +\cs_new_protected:Npn \@@_choices_make:nn { \@@_choices_make:Nnn \@@_choice_make: } -\cs_new_protected_nopar:Npn \@@_multichoices_make:nn +\cs_new_protected:Npn \@@_multichoices_make:nn { \@@_choices_make:Nnn \@@_multichoice_make: } \cs_new_protected:Npn \@@_choices_make:Nnn #1#2#3 { @@ -1489,19 +1554,21 @@ % \end{macro} % % \begin{macro}[int]{\@@_default_set:n} -% Setting a default value is easy. +% 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 { - \@@_ensure_exist:V \l_keys_path_tl \tl_if_empty:nTF {#1} { - \prop_remove:cn { \c_@@_info_root_tl \l_keys_path_tl } - { default } + \cs_set_eq:cN + { \c_@@_default_root_tl \l_keys_path_tl } + \tex_undefined:D } { - \prop_put:cnn { \c_@@_info_root_tl \l_keys_path_tl } - { default } {#1} + \cs_set:cpx + { \c_@@_default_root_tl \l_keys_path_tl } + { \exp_not:n {#1} } } } % \end{macrocode} @@ -1530,21 +1597,14 @@ % \end{macro} % % \begin{macro}[int]{\@@_initialise:n} -% \begin{macro}[aux]{\@@_initialise:wn} -% A set up for initialisation from which the key system requires that -% the path is split up into a module and a key name. At this stage, -% \cs{l_keys_path_tl} will contain \texttt{/} so a split is easy to do. +% A set up for initialisation: just run the code if it exists. % \begin{macrocode} \cs_new_protected:Npn \@@_initialise:n #1 { - \@@_ensure_exist:V \l_keys_path_tl - \exp_after:wN \@@_initialise:wn \l_keys_path_tl \q_stop {#1} + \cs_if_exist_use:cT { \c_@@_code_root_tl \l_keys_path_tl } { {#1} } } -\cs_new_protected:Npn \@@_initialise:wn #1 / #2 \q_stop #3 - { \keys_set:nn {#1} { #2 = {#3} } } % \end{macrocode} % \end{macro} -% \end{macro} % % \begin{macro}[int]{\@@_meta_make:n} % \begin{macro}[int]{\@@_meta_make:nn} @@ -1568,40 +1628,78 @@ % Undefining a key has to be done without \cs{cs_undefine:c} as that % function acts globally. % \begin{macrocode} -\cs_new_protected_nopar:Npn \@@_undefine: +\cs_new_protected:Npn \@@_undefine: { - \cs_set_eq:cN { \c_@@_code_root_tl \l_keys_path_tl } \tex_undefined:D - \cs_set_eq:cN { \c_@@_info_root_tl \l_keys_path_tl } \tex_undefined:D + \clist_map_inline:nn + { code , default , groups , info , 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}[int]{\@@_value_requirement:nn} -% Values can be required or forbidden by having the appropriate marker -% set. First, both the required and forbidden ones are clear, just in case! +% \begin{macro}[aux]{\@@_validate_forbidden:, \@@_validate_required:} +% \begin{macro}[aux]{\@@_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 { - \@@_ensure_exist:V \l_keys_path_tl - \prop_remove:cn { \c_@@_info_root_tl \l_keys_path_tl } - { required } - \prop_remove:cn { \c_@@_info_root_tl \l_keys_path_tl } - { forbidden } - \str_if_eq:nnTF {#2} { true } - { - \prop_put:cnn { \c_@@_info_root_tl \l_keys_path_tl } - {#1} { true } - } + \str_case:nnF {#2} { - \str_if_eq:nnF {#2} { false } + { true } { - \__msg_kernel_error:nnx { kernel } { property-boolean-values-only } - { .value_ #1 :n } + \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 + } + } + } + { + \__msg_kernel_error:nnx { kernel } { property-boolean-values-only } + { .value_ #1 :n } } } +\cs_new_protected:Npn \@@_validate_forbidden: + { + \bool_if:NF \l_@@_no_value_bool + { + \__msg_kernel_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 + { + \__msg_kernel_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}[int]{\@@_variable_set:NnnN, \@@_variable_set:cnnN} % Setting a variable takes the type and scope separately so that @@ -1668,7 +1766,7 @@ % Making a choice is handled internally, as it is also needed by % \texttt{.generate_choices:n}. % \begin{macrocode} -\cs_new_protected_nopar:cpn { \c_@@_props_root_tl .choice: } +\cs_new_protected:cpn { \c_@@_props_root_tl .choice: } { \@@_choice_make: } % \end{macrocode} % \end{macro} @@ -1824,7 +1922,7 @@ % The same idea as \texttt{.choice:} and \texttt{.choices:nn}, but % where more than one choice is allowed. % \begin{macrocode} -\cs_new_protected_nopar:cpn { \c_@@_props_root_tl .multichoice: } +\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 } @@ -1854,7 +1952,6 @@ % \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} @@ -1886,7 +1983,7 @@ % \begin{macro}{.undefine:} % Another simple wrapper. % \begin{macrocode} -\cs_new_protected_nopar:cpn { \c_@@_props_root_tl .undefine: } +\cs_new_protected:cpn { \c_@@_props_root_tl .undefine: } { \@@_undefine: } % \end{macrocode} % \end{macro} @@ -1909,12 +2006,12 @@ % \begin{macro}[aux]{\@@_set:nnn, \@@_set:onn} % A simple wrapper again. % \begin{macrocode} -\cs_new_protected_nopar:Npn \keys_set:nn +\cs_new_protected:Npn \keys_set:nn { \@@_set:onn { \l_@@_module_tl } } \cs_new_protected:Npn \@@_set:nnn #1#2#3 { \tl_set:Nx \l_@@_module_tl { \@@_remove_spaces:n {#2} } - \keyval_parse:NNn \@@_set_elt:n \@@_set_elt:nn {#3} + \keyval_parse:NNn \@@_set:n \@@_set:nn {#3} \tl_set:Nn \l_@@_module_tl {#1} } \cs_generate_variant:Nn \keys_set:nn { nV , nv , no } @@ -1937,10 +2034,10 @@ % 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} +% afterwards. Note that for speed/simplicity reasons we use a \texttt{tl} % operation to set the \texttt{clist} here! % \begin{macrocode} -\cs_new_protected_nopar:Npn \keys_set_known:nnN +\cs_new_protected:Npn \keys_set_known:nnN { \@@_set_known:onnN \l_@@_unused_clist } \cs_generate_variant:Nn \keys_set_known:nnN { nV , nv , no } \cs_new_protected:Npn \@@_set_known:nnnN #1#2#3#4 @@ -1965,25 +2062,25 @@ % % \begin{macro} % { -% \keys_set_filter:nnnN, \keys_set_filter:nnVN, \keys_set_filter:nnvN +% \keys_set_filter:nnnN, \keys_set_filter:nnVN, \keys_set_filter:nnvN, % \keys_set_filter:nnoN % } % \begin{macro}[aux]{\@@_set_filter:nnnnN, \@@_set_filter:onnnN} % \begin{macro} % { -% \keys_set_filter:nnn, \keys_set_filter:nnV, \keys_set_filter:nnv +% \keys_set_filter:nnn, \keys_set_filter:nnV, \keys_set_filter:nnv, % \keys_set_filter:nno % } % \begin{macro} % { -% \keys_set_groups:nnn, \keys_set_groups:nnV, \keys_set_groups:nnv +% \keys_set_groups:nnn, \keys_set_groups:nnV, \keys_set_groups:nnv, % \keys_set_groups:nno % } % 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. % \begin{macrocode} -\cs_new_protected_nopar:Npn \keys_set_filter:nnnN +\cs_new_protected:Npn \keys_set_filter:nnnN { \@@_set_filter:onnnN \l_@@_unused_clist } \cs_generate_variant:Nn \keys_set_filter:nnnN { nnV , nnv , nno } \cs_new_protected:Npn \@@_set_filter:nnnnN #1#2#3#4#5 @@ -2018,24 +2115,24 @@ % \end{macro} % \end{macro} % -% \begin{macro}[int]{\@@_set_elt:n, \@@_set_elt:nn} -% \begin{macro}[aux]{\@@_set_elt_aux:nnn, \@@_set_elt_aux:onn} +% \begin{macro}[int]{\@@_set:n, \@@_set:nn} +% \begin{macro}[aux]{\@@_set_aux:nnn, \@@_set_aux:onn} % \begin{macro}[aux]{\@@_find_key_module:w} -% \begin{macro}[aux]{\@@_set_elt_aux:, \@@_set_elt_selective:} +% \begin{macro}[aux]{\@@_set_aux:, \@@_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_elt:n #1 +\cs_new_protected:Npn \@@_set:n #1 { \bool_set_true:N \l_@@_no_value_bool - \@@_set_elt_aux:onn \l_@@_module_tl {#1} { } + \@@_set_aux:onn \l_@@_module_tl {#1} { } } -\cs_new_protected:Npn \@@_set_elt:nn #1#2 +\cs_new_protected:Npn \@@_set:nn #1#2 { \bool_set_false:N \l_@@_no_value_bool - \@@_set_elt_aux:onn \l_@@_module_tl {#1} {#2} + \@@_set_aux:onn \l_@@_module_tl {#1} {#2} } % \end{macrocode} % The key path here can be fully defined, after which there is a search @@ -2044,19 +2141,18 @@ % 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_elt_aux:nnn #1#2#3 +\cs_new_protected:Npn \@@_set_aux:nnn #1#2#3 { - \tl_set:Nx \l_keys_path_tl - { \l_@@_module_tl / \@@_remove_spaces:n {#2} } + \tl_set:Nx \l_keys_path_tl { #1 / \@@_remove_spaces:n {#2} } \tl_clear:N \l_@@_module_tl \exp_after:wN \@@_find_key_module:w \l_keys_path_tl / \q_stop \@@_value_or_default:n {#3} \bool_if:NTF \l_@@_selective_bool - { \@@_set_elt_selective: } - { \@@_set_elt_aux: } + { \@@_set_selective: } + { \@@_execute: } \tl_set:Nn \l_@@_module_tl {#1} } -\cs_generate_variant:Nn \@@_set_elt_aux:nnn { o } +\cs_generate_variant:Nn \@@_set_aux:nnn { o } \cs_new_protected:Npn \@@_find_key_module:w #1 / #2 \q_stop { \tl_if_blank:nTF {#2} @@ -2070,37 +2166,13 @@ \@@_find_key_module:w #2 \q_stop } } -\cs_new_protected_nopar:Npn \@@_set_elt_aux: - { - \bool_if:nTF - { - \@@_if_value_p:n { required } && - \l_@@_no_value_bool - } - { - \__msg_kernel_error:nnx { kernel } { value-required } - { \l_keys_path_tl } - } - { - \bool_if:nTF - { - \@@_if_value_p:n { forbidden } && - ! \l_@@_no_value_bool - } - { - \__msg_kernel_error:nnxx { kernel } { value-forbidden } - { \l_keys_path_tl } { \l_keys_value_tl } - } - { \@@_execute: } - } - } % \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_nopar:Npn \@@_set_elt_selective: +\cs_new_protected:Npn \@@_set_selective: { \prop_if_exist:cTF { \c_@@_info_root_tl \l_keys_path_tl } { @@ -2109,13 +2181,13 @@ { \@@_check_groups: } { \bool_if:NTF \l_@@_filtered_bool - { \@@_set_elt_aux: } + { \@@_execute: } { \@@_store_unused: } } } { \bool_if:NTF \l_@@_filtered_bool - { \@@_set_elt_aux: } + { \@@_execute: } { \@@_store_unused: } } } @@ -2125,7 +2197,7 @@ % 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_nopar:Npn \@@_check_groups: +\cs_new_protected:Npn \@@_check_groups: { \bool_set_false:N \l_@@_tmp_bool \seq_map_inline:Nn \l_@@_selective_seq @@ -2143,11 +2215,11 @@ { \bool_if:NTF \l_@@_filtered_bool { \@@_store_unused: } - { \@@_set_elt_aux: } + { \@@_execute: } } { \bool_if:NTF \l_@@_filtered_bool - { \@@_set_elt_aux: } + { \@@_execute: } { \@@_store_unused: } } } @@ -2165,8 +2237,12 @@ { \bool_if:NTF \l_@@_no_value_bool { - \prop_get:cnNF { \c_@@_info_root_tl \l_keys_path_tl } - { default } \l_keys_value_tl + \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 } } { \tl_set:Nn \l_keys_value_tl {#1} } @@ -2174,25 +2250,7 @@ % \end{macrocode} % \end{macro} % -% \begin{macro}[int]{\@@_if_value_p:n} -% To test if a value is required or forbidden. A simple check for -% the existence of the appropriate marker. -% \begin{macrocode} -\prg_new_conditional:Npnn \@@_if_value:n #1 { p } - { - \prop_if_exist:cTF { \c_@@_info_root_tl \l_keys_path_tl } - { - \prop_if_in:cnTF { \c_@@_info_root_tl \l_keys_path_tl } {#1} - { \prg_return_true: } - { \prg_return_false: } - } - { \prg_return_false: } - } -% \end{macrocode} -% \end{macro} -% -% \begin{macro}[int]{\@@_execute:} -% \begin{macro}[aux]{\@@_execute_unknown:} +% \begin{macro}[aux]{\@@_execute:, \@@_execute_unknown:} % \begin{macro}[aux, EXP]{\@@_execute:nn} % \begin{macro}[aux]{\@@_store_unused:} % Actually executing a key is done in two parts. First, look for the @@ -2201,14 +2259,26 @@ % is unknown depends on whether unknown keys are being skipped or if % an error should be raised. % \begin{macrocode} -\cs_new_protected_nopar:Npn \@@_execute: - { \@@_execute:nn { \l_keys_path_tl } { \@@_execute_unknown: } } -\cs_new_protected_nopar:Npn \@@_execute_unknown: +\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 } + } + { \@@_execute_unknown: } + } +\cs_new_protected:Npn \@@_execute_unknown: { \bool_if:NTF \l_@@_only_known_bool { \@@_store_unused: } { - \@@_execute:nn { \l_@@_module_tl / unknown } + \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 } + } { \__msg_kernel_error:nnxx { kernel } { key-unknown } { \l_keys_path_tl } { \l_@@_module_tl } @@ -2224,7 +2294,7 @@ } {#2} } -\cs_new_protected_nopar:Npn \@@_store_unused: +\cs_new_protected:Npn \@@_store_unused: { \clist_put_right:Nx \l_@@_unused_clist { @@ -2237,7 +2307,6 @@ % \end{macro} % \end{macro} % \end{macro} -% \end{macro} % % \begin{macro}[int, EXP]{\@@_choice_find:n} % \begin{macro}[int, EXP]{\@@_multichoice_find:n} @@ -2292,7 +2361,7 @@ % \end{macro} % % \begin{macro}[EXP,pTF]{\keys_if_choice_exist:nnn} -% Just an alternative view on \cs{keys_if_exist:nn(TF)}. +% 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 } @@ -2354,12 +2423,6 @@ \__msg_kernel_new:nnnn { kernel } { boolean-values-only } { Key~'#1'~accepts~boolean~values~only. } { The~key~'#1'~only~accepts~the~values~'true'~and~'false'. } -\__msg_kernel_new:nnnn { kernel } { choice-unknown } - { Choice~'#2'~unknown~for~key~'#1'. } - { - The~key~'#1'~takes~a~limited~number~of~values.\\ - The~input~given,~'#2',~is~not~on~the~list~accepted. - } \__msg_kernel_new:nnnn { kernel } { key-choice-unknown } { Key~'#1'~accepts~only~a~fixed~set~of~choices. } { @@ -2430,13 +2493,13 @@ % % \subsection{Deprecated functions} % -% \begin{macro}{.value_forbidden:} +% \begin{macro}[deprecated = 2017]{.value_forbidden:} % \begin{macro}{.value_required:} % Deprecated 2015-07-14. % \begin{macrocode} -\cs_new_protected_nopar:cpn { \c_@@_props_root_tl .value_forbidden: } +\cs_new_protected:cpn { \c_@@_props_root_tl .value_forbidden: } { \@@_value_requirement:nn { forbidden } { true } } -\cs_new_protected_nopar:cpn { \c_@@_props_root_tl .value_required: } +\cs_new_protected:cpn { \c_@@_props_root_tl .value_required: } { \@@_value_requirement:nn { required } { true } } % \end{macrocode} % \end{macro} |