[latex3-commits] [git/LaTeX3-latex3-latex3] master: Tighten xparse signature checks: allow "!" only on trailing optionals (9b7df82)

Bruno Le Floch bruno at le-floch.fr
Sun Sep 23 23:24:05 CEST 2018


Repository : https://github.com/latex3/latex3
On branch  : master
Link       : https://github.com/latex3/latex3/commit/9b7df82f382776ae6441924c55b157c7deba92f6

>---------------------------------------------------------------

commit 9b7df82f382776ae6441924c55b157c7deba92f6
Author: Bruno Le Floch <bruno at le-floch.fr>
Date:   Sun Sep 23 18:47:49 2018 +0200

    Tighten xparse signature checks: allow "!" only on trailing optionals
    
    Previously "!" only applied to trailing optional but silently did nothing
    on other arguments.  Now it produces an error at definition time.


>---------------------------------------------------------------

9b7df82f382776ae6441924c55b157c7deba92f6
 l3packages/xparse/testfiles/xparse001.ptex.tlg  |    6 +-
 l3packages/xparse/testfiles/xparse001.tlg       |    6 +-
 l3packages/xparse/testfiles/xparse001.uptex.tlg |    6 +-
 l3packages/xparse/xparse.dtx                    |  208 ++++++++++++++---------
 4 files changed, 132 insertions(+), 94 deletions(-)

diff --git a/l3packages/xparse/testfiles/xparse001.ptex.tlg b/l3packages/xparse/testfiles/xparse001.ptex.tlg
index 0338abf..306069d 100644
--- a/l3packages/xparse/testfiles/xparse001.ptex.tlg
+++ b/l3packages/xparse/testfiles/xparse001.ptex.tlg
@@ -577,9 +577,9 @@ l. ...}
 * character.
 *************************************************
 > \foo=\protected macro:->\__xparse_start:nNNnnn {+t\par O{default}mmm}\foo 
-\foo code {\__xparse_grab_t_long:w \par \__xparse_grab_D:w
-[]\__xparse_grab_m_3:w }{\c_novalue_tl {\prg_do_nothing: default}\c_novalue_tl
-\c_novalue_tl \c_novalue_tl }{}.
+\foo code {\__xparse_grab_t:w \par \__xparse_grab_D:w []\__xparse_grab_m_3:w
+}{\c_novalue_tl {\prg_do_nothing: default}\c_novalue_tl \c_novalue_tl
+\c_novalue_tl }{}.
 <recently read> }
 l. ...}
 > \foo code=\protected\long macro:#1#2#3#4#5->(#1)(#2)(#3)(#4).
diff --git a/l3packages/xparse/testfiles/xparse001.tlg b/l3packages/xparse/testfiles/xparse001.tlg
index 2b00ced..d31420a 100644
--- a/l3packages/xparse/testfiles/xparse001.tlg
+++ b/l3packages/xparse/testfiles/xparse001.tlg
@@ -577,9 +577,9 @@ l. ...}
 * character.
 *************************************************
 > \foo=\protected macro:->\__xparse_start:nNNnnn {+t\par O{default}mmm}\foo 
-\foo code {\__xparse_grab_t_long:w \par \__xparse_grab_D:w
-[]\__xparse_grab_m_3:w }{\c_novalue_tl {\prg_do_nothing: default}\c_novalue_tl
-\c_novalue_tl \c_novalue_tl }{}.
+\foo code {\__xparse_grab_t:w \par \__xparse_grab_D:w []\__xparse_grab_m_3:w
+}{\c_novalue_tl {\prg_do_nothing: default}\c_novalue_tl \c_novalue_tl
+\c_novalue_tl }{}.
 <recently read> }
 l. ...}
 > \foo code=\protected\long macro:#1#2#3#4#5->(#1)(#2)(#3)(#4).
diff --git a/l3packages/xparse/testfiles/xparse001.uptex.tlg b/l3packages/xparse/testfiles/xparse001.uptex.tlg
index 0338abf..306069d 100644
--- a/l3packages/xparse/testfiles/xparse001.uptex.tlg
+++ b/l3packages/xparse/testfiles/xparse001.uptex.tlg
@@ -577,9 +577,9 @@ l. ...}
 * character.
 *************************************************
 > \foo=\protected macro:->\__xparse_start:nNNnnn {+t\par O{default}mmm}\foo 
-\foo code {\__xparse_grab_t_long:w \par \__xparse_grab_D:w
-[]\__xparse_grab_m_3:w }{\c_novalue_tl {\prg_do_nothing: default}\c_novalue_tl
-\c_novalue_tl \c_novalue_tl }{}.
+\foo code {\__xparse_grab_t:w \par \__xparse_grab_D:w []\__xparse_grab_m_3:w
+}{\c_novalue_tl {\prg_do_nothing: default}\c_novalue_tl \c_novalue_tl
+\c_novalue_tl }{}.
 <recently read> }
 l. ...}
 > \foo code=\protected\long macro:#1#2#3#4#5->(#1)(#2)(#3)(#4).
diff --git a/l3packages/xparse/xparse.dtx b/l3packages/xparse/xparse.dtx
index c9685d2..d3001e1 100644
--- a/l3packages/xparse/xparse.dtx
+++ b/l3packages/xparse/xparse.dtx
@@ -953,6 +953,13 @@
 %    \end{macrocode}
 % \end{variable}
 %
+% \begin{variable}{\l_@@_ignore_spaces_bool}
+%   For trailing optionals.
+%    \begin{macrocode}
+\bool_new:N \l_@@_ignore_spaces_bool
+%    \end{macrocode}
+% \end{variable}
+%
 % \begin{variable}{\l_@@_last_delimiters_tl}
 %   Holds the delimiters (first tokens) of all optional arguments since
 %   the previous mandatory argument, to warn about cases where it would
@@ -971,13 +978,6 @@
 %    \end{macrocode}
 % \end{variable}
 %
-% \begin{variable}{\l_@@_ignore_spaces_bool}
-%   For trailing optionals.
-%    \begin{macrocode}
-\bool_new:N \l_@@_ignore_spaces_bool
-%    \end{macrocode}
-% \end{variable}
-%
 % \begin{variable}{\l_@@_m_args_int}
 %   The number of \texttt{m} arguments: if this is the same as the total
 %   number of arguments, then a short-cut can be taken in the creation of
@@ -1028,15 +1028,20 @@
 %    \end{macrocode}
 % \end{variable}
 %
-% \begin{variable}{\l_@@_some_long_bool, \l_@@_some_short_bool}
-%   Both of these flags are set while normalizing the argument
-%   specification.  To grab arguments expandably, all short arguments
-%   must appear before long arguments, so as soon as the first long
-%   argument is seen (other than \texttt{t}-type, whose long status is
-%   ignored) the \texttt{some_long} flag is set.  The
-%   \texttt{some_short} flag is used for expandable commands, to know
-%   whether to define a short auxiliary too.
-%    \begin{macrocode}
+% \begin{variable}
+%   {\l_@@_some_ignore_spaces_bool, \l_@@_some_long_bool, \l_@@_some_short_bool}
+%   These flags are set while normalizing the argument specification.
+%   The \texttt{ignore_spaces} one is used to detect when |!| is used on
+%   an argument that is not a trailing optional argument.
+%   The other two are used to check whether all short arguments appear
+%   before long arguments: this is needed to grab arguments expandably.
+%   As soon as the first long argument is seen (other than
+%   \texttt{t}-type, whose long status is ignored) the
+%   \texttt{some_long} flag is set.  The \texttt{some_short} flag is
+%   used for expandable commands, to know whether to define a short
+%   auxiliary too.
+%    \begin{macrocode}
+\bool_new:N \l_@@_some_ignore_spaces_bool
 \bool_new:N \l_@@_some_long_bool
 \bool_new:N \l_@@_some_short_bool
 %    \end{macrocode}
@@ -1521,50 +1526,49 @@
 % specification is valid before the main parsing run.  If it is not
 % valid the entire set up is abandoned to avoid any strange internal
 % errors.  A function is provided for each argument type that will grab
-% any extra arguments and call the loop function.
-%
-% The first thing that is done in the loop is to check that the various
-% argument types have the correct number of data items associated with
-% them.  The opportunity is also taken to make sure that where a single
-% character is required, one has actually been supplied.
-%
-% The second is that processors and the marker~|+| for long arguments
-% must be followed by arguments.
-%
-% The third is to check for forbidden types for expandable commands,
-% namely \texttt{G}/\texttt{v} always, and \texttt{l}/\texttt{u} after
-% optional arguments (\pkg{xparse} may have inserted braces due to a
-% failed search for an optional argument).
-%
-% The fourth is that an optional argument should not be followed by a
-% mandatory argument with the same delimiter, as otherwise the optional
-% argument could never be omitted.
-%
-% The fifth is to keep track in \cs{l_@@_some_long_bool} and
-% \cs{l_@@_some_short_bool} of whether the command has some long/short
-% arguments.
-%
-% The sixth is to keep track in \cs{l_@@_grab_expandably_bool} of
-% whether all arguments are \texttt{m}/\texttt{l}/\texttt{u} type and
-% short arguments appear before long ones, in which case they can be
-% grabbed expandably just as safely as they could be grabbed expandably.
-% Regardless of that, arguments of expandable commands will be grabbed
-% expandably and arguments of environments will not (because the list of
-% arguments built by non-expandable grabbing is used to pass them to the
-% end-environment code).
-%
-% The last is to count mandatory arguments, used later to detect which
-% optional arguments are trailing.
+% any extra data items and call the loop function after performing the
+% following checks and tasks.
+% \begin{itemize}
+%   \item Check that each argument has the correct number of data items
+%     associated with it, and that where a single character is required,
+%     one has actually been supplied.
+%   \item Check that processors and the markers~|+| and~|!| are followed
+%     by an argument for which they make sense, and are not redundant.
+%   \item Check the absence of forbidden types for expandable commands,
+%     namely \texttt{G}/\texttt{v} always, and \texttt{l}/\texttt{u}
+%     after optional arguments (\pkg{xparse} may have inserted braces
+%     due to a failed search for an optional argument).
+%   \item Check that no optional argument is followed by a mandatory
+%     argument with the same delimiter, as otherwise the optional
+%     argument could never be omitted.
+%   \item Keep track in \cs{l_@@_some_long_bool} and
+%     \cs{l_@@_some_short_bool} of whether the command has some
+%     long/short arguments.
+%   \item Keep track in \cs{l_@@_grab_expandably_bool} of whether all
+%     arguments are \texttt{m}/\texttt{l}/\texttt{u} type and short
+%     arguments appear before long ones, in which case they can be
+%     grabbed expandably just as safely as they could be grabbed
+%     nonexpandably.  Regardless of that, arguments of expandable
+%     commands will be grabbed expandably and arguments of environments
+%     will not (because the list of arguments built by non-expandable
+%     grabbing is used to pass them to the end-environment code).
+%   \item Count mandatory arguments, used later to detect which optional
+%     arguments are trailing.
+% \end{itemize}
+% Further checks happen at the end of the loop:
+% \begin{itemize}
+% \item that there are at most $9$ arguments;
+% \item that an expandable command does not end with an optional
+%   argument (this case is detected by using the fact that
+%   \cs{l_@@_last_delimiters_tl} is cleared by every mandatory argument
+%   and filled by every optional argument).
+% \end{itemize}
 %
 % \begin{macro}{\@@_normalize_arg_spec:n}
 % \begin{macro}{\@@_normalize_arg_spec_loop:n}
 %   Loop through the argument specification, calling an auxiliary
 %   specific to each argument type.  If any argument is unknown stop the
-%   definition.  After the loop, if there are more than
-%   $9$ arguments, stop.  Additionally, expandable commands may not end
-%   with an optional argument; this case is detected by using the fact
-%   that \cs{l_@@_last_delimiters_tl} is cleared by every mandatory
-%   argument and filled by every optional argument.
+%   definition.
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_normalize_arg_spec:n #1
   {
@@ -1573,10 +1577,11 @@
     \tl_clear:N \l_@@_last_delimiters_tl
     \tl_clear:N \l_@@_arg_spec_tl
     \bool_set_true:N \l_@@_grab_expandably_bool
+    \bool_set_false:N \l_@@_ignore_spaces_bool
     \bool_set_false:N \l_@@_long_bool
+    \bool_set_false:N \l_@@_some_ignore_spaces_bool
     \bool_set_false:N \l_@@_some_long_bool
     \bool_set_false:N \l_@@_some_short_bool
-    \bool_set_false:N \l_@@_ignore_spaces_bool
     \@@_normalize_arg_spec_loop:n #1
       \q_recursion_tail \q_recursion_tail \q_recursion_tail \q_recursion_stop
     \int_compare:nNnT \l_@@_current_arg_int > 9
@@ -1691,7 +1696,11 @@
 \cs_new_protected:cpn { @@_normalize_type_+:w } #1
   {
     \quark_if_recursion_tail_stop_do:nn {#1} { \@@_bad_arg_spec:wn }
-    \tl_put_right:Nn \l_@@_arg_spec_tl { + }
+    \bool_if:NT \l_@@_long_bool
+      {
+        \__kernel_msg_error:nnxx { xparse } { two-markers }
+          { \iow_char:N \\ \l_@@_function_tl } { + }
+      }
     \bool_set_true:N \l_@@_long_bool
     \int_decr:N \l_@@_current_arg_int
     \@@_normalize_arg_spec_loop:n {#1}
@@ -1699,7 +1708,13 @@
 \cs_new_protected:cpn { @@_normalize_type_!:w } #1
   {
     \quark_if_recursion_tail_stop_do:nn {#1} { \@@_bad_arg_spec:wn }
-    \tl_put_right:Nn \l_@@_arg_spec_tl { ! }
+    \bool_if:NT \l_@@_ignore_spaces_bool
+      {
+        \__kernel_msg_error:nnxx { xparse } { two-markers }
+          { \iow_char:N \\ \l_@@_function_tl } { ! }
+      }
+    \bool_set_true:N \l_@@_ignore_spaces_bool
+    \bool_set_true:N \l_@@_some_ignore_spaces_bool
     \int_decr:N \l_@@_current_arg_int
     \@@_normalize_arg_spec_loop:n {#1}
   }
@@ -1770,9 +1785,14 @@
   {
     \quark_if_recursion_tail_stop_do:Nn #1 { \@@_bad_arg_spec:wn }
     \@@_single_char_check:n {#1}
-    \tl_put_right:Nn \l_@@_arg_spec_tl { t #1 }
+    \tl_put_right:Nx \l_@@_arg_spec_tl
+      {
+        \bool_if:NT \l_@@_ignore_spaces_bool { ! }
+        t \exp_not:n {#1}
+      }
     \tl_put_right:Nn \l_@@_last_delimiters_tl {#1}
     \bool_set_false:N \l_@@_grab_expandably_bool
+    \bool_set_false:N \l_@@_ignore_spaces_bool
     \bool_set_false:N \l_@@_long_bool
     \@@_normalize_arg_spec_loop:n
   }
@@ -1800,17 +1820,13 @@
 \cs_new_protected:Npn \@@_normalize_type_l:w
   {
     \@@_normalize_check_lu:N l
-    \@@_add_arg_spec:n { l }
-    \int_incr:N \l_@@_mandatory_args_int
-    \tl_clear:N \l_@@_last_delimiters_tl
+    \@@_add_arg_spec_mandatory:n { l }
     \@@_normalize_arg_spec_loop:n
   }
 \cs_new_protected:Npn \@@_normalize_type_m:w
   {
     \@@_delimiter_check:nnn { } { m } { \iow_char:N \{ }
-    \@@_add_arg_spec:n { m }
-    \int_incr:N \l_@@_mandatory_args_int
-    \tl_clear:N \l_@@_last_delimiters_tl
+    \@@_add_arg_spec_mandatory:n { m }
     \@@_normalize_arg_spec_loop:n
   }
 \cs_new_protected:Npn \@@_normalize_type_R:w #1#2#3
@@ -1819,27 +1835,21 @@
     \@@_single_char_check:n {#1}
     \@@_single_char_check:n {#2}
     \@@_delimiter_check:nnn {#1} { R/r } { \tl_to_str:n {#1} }
-    \@@_add_arg_spec:n { R #1 #2 {#3} }
-    \int_incr:N \l_@@_mandatory_args_int
-    \tl_clear:N \l_@@_last_delimiters_tl
     \bool_set_false:N \l_@@_grab_expandably_bool
+    \@@_add_arg_spec_mandatory:n { R #1 #2 {#3} }
     \@@_normalize_arg_spec_loop:n
   }
 \cs_new_protected:Npn \@@_normalize_type_u:w #1
   {
     \quark_if_recursion_tail_stop_do:nn {#1} { \@@_bad_arg_spec:wn }
     \@@_normalize_check_lu:N u
-    \@@_add_arg_spec:n { u {#1} }
-    \int_incr:N \l_@@_mandatory_args_int
-    \tl_clear:N \l_@@_last_delimiters_tl
+    \@@_add_arg_spec_mandatory:n { u {#1} }
     \@@_normalize_arg_spec_loop:n
   }
 \cs_new_protected:Npn \@@_normalize_type_v:w
   {
     \@@_normalize_check_gv:N v
-    \@@_add_arg_spec:n { v }
-    \int_incr:N \l_@@_mandatory_args_int
-    \tl_clear:N \l_@@_last_delimiters_tl
+    \@@_add_arg_spec_mandatory:n { v }
     \@@_normalize_arg_spec_loop:n
   }
 %    \end{macrocode}
@@ -1936,13 +1946,16 @@
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}{\@@_add_arg_spec:n}
+% \begin{macro}{\@@_add_arg_spec:n, \@@_add_arg_spec_mandatory:n}
 %   When adding an argument to the argument specification, set the
 %   \texttt{some_long} or \texttt{some_short} booleans as appropriate
-%   and clear the boolean keeping track of whether the argument is long.
+%   and clear the booleans keeping track of |+| and |!| markers.
 %   Before that, test for a short argument following some long
 %   arguments: this is forbidden for expandable commands and prevents
 %   grabbing arguments expandably.
+%
+%   For mandatory arguments do some more work, in particular complain if
+%   they were preceeded by~|!|.
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_add_arg_spec:n #1
   {
@@ -1961,8 +1974,26 @@
     \bool_if:NTF \l_@@_long_bool
       { \bool_set_true:N \l_@@_some_long_bool }
       { \bool_set_true:N \l_@@_some_short_bool }
+    \tl_put_right:Nx \l_@@_arg_spec_tl
+      {
+        \bool_if:NT \l_@@_long_bool { + }
+        \bool_if:NT \l_@@_ignore_spaces_bool { ! }
+        \exp_not:n {#1}
+      }
     \bool_set_false:N \l_@@_long_bool
-    \tl_put_right:Nn \l_@@_arg_spec_tl {#1}
+    \bool_set_false:N \l_@@_ignore_spaces_bool
+  }
+\cs_new_protected:Npn \@@_add_arg_spec_mandatory:n #1
+  {
+    \bool_if:NT \l_@@_some_ignore_spaces_bool
+      {
+        \__kernel_msg_error:nnxx { xparse } { ignore-space-non-trailing }
+          { \iow_char:N \\ \l_@@_function_tl } { \tl_to_str:n {#1} }
+        \@@_bad_def:wn
+      }
+    \int_incr:N \l_@@_mandatory_args_int
+    \tl_clear:N \l_@@_last_delimiters_tl
+    \@@_add_arg_spec:n {#1}
   }
 %    \end{macrocode}
 % \end{macro}
@@ -1972,8 +2003,8 @@
 % \begin{macro}{\@@_prepare_signature:n}
 % \begin{macro}{\@@_prepare_signature:N}
 % \begin{macro}{\@@_prepare_signature_bypass:N}
-%   Actually creating the signature uses the same loop approach as counting
-%   up mandatory arguments. There are first a number of variables which need
+%   Actually creating the signature uses the same loop approach as
+%   normalizing the signature. There are first a number of variables which need
 %   to be set to track what is going on. Many of these variables are unused
 %   when defining expandable commands.
 %    \begin{macrocode}
@@ -3012,19 +3043,15 @@
 % \end{macro}
 %
 % \begin{macro}{\@@_grab_t:w}
-% \begin{macro}{\@@_grab_t_long:w}
 % \begin{macro}{\@@_grab_t_ignore_spaces:w}
-% \begin{macro}{\@@_grab_t_long_ignore_spaces:w}
 % \begin{macro}{\@@_grab_t_aux:NNw}
 %   Dealing with a token is quite easy. Check the match, remove the
 %   token if needed and add a flag to the output.
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_grab_t:w
   { \@@_grab_t_aux:NNw \peek_meaning_remove_ignore_spaces:NTF }
-\cs_new_eq:NN \@@_grab_t_long:w \@@_grab_t:w
 \cs_new_protected:Npn \@@_grab_t_ignore_spaces:w
   { \@@_grab_t_aux:NNw \peek_meaning_remove:NTF }
-\cs_new_eq:NN \@@_grab_t_long_ignore_spaces:w \@@_grab_t_ignore_spaces:w
 \cs_new_protected:Npn \@@_grab_t_aux:NNw #1#2#3 \@@_run_code:
   {
     \tl_set:Nn \l_@@_signature_tl {#3}
@@ -3040,8 +3067,6 @@
 % \end{macro}
 % \end{macro}
 % \end{macro}
-% \end{macro}
-% \end{macro}
 %
 % \begin{macro}{\@@_grab_u:w}
 % \begin{macro}{\@@_grab_u_long:w}
@@ -4152,6 +4177,13 @@
     The~default~values~of~two~or~more~arguments~of~'#1'~depend~on~each~
     other~in~a~way~that~cannot~be~resolved.
   }
+\__kernel_msg_new:nnnn { xparse } { ignore-space-non-trailing }
+  { Prefix~'!'~used~before~mandatory~argument~'#2'~of~command~'#1'. }
+  {
+    The~prefix~'!'~can~only~apply~to~trailing~optional~arguments.
+    \\ \\
+    LaTeX~will~ignore~this~entire~definition.
+  }
 \__kernel_msg_new:nnnn { xparse } { missing-required }
   { Failed~to~find~required~argument~starting~with~'#2'~for~command~'#1'. }
   {
@@ -4219,6 +4251,12 @@
     This~cannot~be~implemented. \\ \\
     LaTeX~will~ignore~this~entire~definition.
   }
+\__kernel_msg_new:nnnn { xparse } { two-markers }
+  { Two~'#2'~apply~to~the~same~argument~in~argument~specification~of~command~'#1'. }
+  {
+    The~argument~specification~provided~has~two~markers~'#2'~applying~
+    to~the~same~argument;~these~are~redundant.
+  }
 \__kernel_msg_new:nnnn { xparse } { unknown-argument-type }
   { Unknown~argument~type~'#2'~for~the~command~'#1'. }
   {





More information about the latex3-commits mailing list