[latex3-commits] [latex3/latex3] undef-prop: Improve error recovery for prop get/put/pop etc with invalid prop (b3d5a0531)

github at latex-project.org github at latex-project.org
Mon Feb 19 02:00:17 CET 2024


Repository : https://github.com/latex3/latex3
On branch  : undef-prop
Link       : https://github.com/latex3/latex3/commit/b3d5a0531cd754bed528aa80dc6804b552c283b0

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

commit b3d5a0531cd754bed528aa80dc6804b552c283b0
Author: Bruno Le Floch <blflatex at gmail.com>
Date:   Mon Feb 19 02:00:17 2024 +0100

    Improve error recovery for prop get/put/pop etc with invalid prop
    
    Now applying these operations to an undefined prop or prop that
    came up from incorrect tl operations will raise an error and define
    the prop essentially with \prop_new:N.  This avoids cascading errors
    and errors that are incomprehensible to the end-user.


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

b3d5a0531cd754bed528aa80dc6804b552c283b0
 l3kernel/l3prop.dtx              | 137 ++++++++++++++++++----------
 l3kernel/testfiles/m3prop008.lvt | 117 ++++++++++++++++++++++++
 l3kernel/testfiles/m3prop008.tlg | 187 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 392 insertions(+), 49 deletions(-)

diff --git a/l3kernel/l3prop.dtx b/l3kernel/l3prop.dtx
index acab35c56..3c423be47 100644
--- a/l3kernel/l3prop.dtx
+++ b/l3kernel/l3prop.dtx
@@ -1504,64 +1504,103 @@
 % consecutive entries \cs{@@_pair:wn} \meta{key_i} \cs{s_@@}
 % \Arg{value_i}.  If the \meta{key} is present in the \meta{property
 % list} then the \meta{true code} is left in the input stream, with
-% |#2|, |#3|, and |#4| replaced by the \meta{entries before},
+% |#1|, |#2|, and |#3| replaced by the \meta{entries before},
 % \meta{value}, and \meta{entries after}.  If the \meta{key} is not
 % present in the \meta{property list} then the \meta{false code} is left
 % in the input stream.  Only the \meta{true code} is used in the
 % replacement text of a macro defined internally, which requires
 % |##|~doubling.
 %
-% \begin{macro}{\@@_split:NnTFn}
-% \begin{macro}{\@@_split_aux:NnTFn}
-% \begin{macro}{\@@_split_aux:w}
-%   The aim is to split the \meta{property list} at the given \meta{key}
-%   into the \meta{extract_1} before the key--value pair, the
-%   \meta{value} associated with the \meta{key} and the \meta{extract_2}
-%   after the key--value pair.  This is done using a delimited function,
-%   whose definition is as follows, where the \meta{key} is turned into
-%   a string.
+% \begin{macro}
+%   {
+%     \@@_split:NnTFn, \@@_split_aux:nNTFn, \@@_split_test:wn,
+%     \@@_split_flat:w, \@@_split_linked:w, \@@_split_wrong:Nw
+%   }
+%   The aim is to distinguish four cases: a flat prop that contains the
+%   given \meta{key}, a flat prop that does not contain it, a linked
+%   prop, and an invalid prop.  The last case includes those that are
+%   set to \tn{relax} by \texttt{c}-expansion, as well as unrelated
+%   token list variables since these unfortunately used to
+%   \enquote{work} in earlier implementations.
+%   In the first three cases we run the \texttt{T}, \texttt{F}, and
+%   \texttt{n} arguments, and in the last case we raise an error, set
+%   the variable to a known state (empty prop), and run the \texttt{F}
+%   code (some conditionals such as \cs{prop_pop:NnNTF} otherwise blow
+%   up pretty badly).
+%
+%   The first distinction between these cases is done by
+%   \cs{@@_split_test:wn}, which looks for the argument after \cs{s_@@}.
+%   For a flat prop it will be \cs{@@_chk:w}, which leads to running
+%   \cs{@@_split_flat:w}, explained below.  For a linked prop it is the
+%   prefix, consisting of characters, so we end up running
+%   \cs{@@_split_linked:w}, which cleans up and selects the
+%   aforementioned \texttt{n} argument.  For invalid props, or rather,
+%   variables that do not contain \cs{s_@@}, the argument includes
+%   \cs{fi:}, and we end up calling \cs{@@_split_wrong:Nw}, which calls
+%   \cs{prop_show:N} to raise a detailed error stating how the variable
+%   is wrong.
+%
+%   Let us return to \cs{@@_split_flat:w}.  This function is defined
+%   dynamically as
 %   \begin{quote}
-%     \cs{cs_set:Npn} \cs{@@_split_aux:w} |#1| \cs{@@_chk:w} |#2| \\
-%     \quad \cs{@@_pair:wn} \meta{key} \cs{s_@@} |#3| \\
-%     \quad |#4| \cs{s_@@_mark} |#5| |#6| \cs{s_@@_stop} \\
-%     \quad |{| |#5| \Arg{true code} |}|
+%     \cs{cs_set:Npn} \cs{@@_split_flat:w} \cs{@@_split_linked:w} |#1| \\
+%     \quad \cs{@@_pair:wn} \meta{key} \cs{s_@@} |#2| \\
+%     \quad |#3| \cs{s_@@_mark} |#4| |#5| \cs{s_@@_stop} \\
+%     \quad |{| |#4| \Arg{true code} |}|
 %   \end{quote}
-%
-%   If the \meta{key} is present in the property list,
-%   \cs{@@_split_aux:w}'s |#2| is the part before the \meta{key}, |#3|
-%   is the \meta{value}, |#4| is the part after the \meta{key}, |#5| is
-%   \cs{use_i:nnn}, and |#6| is additional tokens that we do not care
-%   about.  The \meta{true code} is left in the input stream, and can
-%   use the parameters |#2|, |#3|, |#4| for the three parts of the
-%   property list as desired.  Namely, the original property list is in
-%   this case \cs{s_@@} \cs{@@_chk:w} |#2| \cs{@@_pair:wn} \meta{key}
-%   \cs{s_@@} |{#3}| |#4|.
-%
-%   If the \meta{key} is not there, then the \meta{function} is
-%   \cs{use_ii:nnn}, which keeps the \meta{false code}.  If the property
-%   list uses the doubly-linked list storage, then the argument
-%   delimited by \cs{@@_chk:w} includes the whole property list, |#2|,
-%   |#3|, |#4| are empty, and |#5| is \cs{use_iii:nnn}.  In all three
-%   cases, the appopriate code among \meta{true code}, \meta{false
-%   code}, and \meta{linked code} is run.
+%   Its job is to seek the \meta{key} in the property list (known to be
+%   flat at this stage) by using an argument |#1| delimited essentially
+%   by that key.  If indeed the variable contained the \meta{key}, then
+%   |#1|~is the \meta{extract_1} before the key--value pair, |#2|~is the
+%   \meta{value} associated with the \meta{key}, |#3|~is the
+%   \meta{extract_2} after the key--value pair, |#4|~is \cs{use_i:nnn},
+%   and we run \cs{use_i:nnn} \Arg{true code} \Arg{false code} \Arg{link
+%   code}, selecting the \meta{true code}.  Otherwise, the whole
+%   property list together with \cs{s_@@_mark} \cs{use_i:nnn} is taken
+%   in as |#1|, then |#2| is some tokens |?| \cs{fi:}
+%   \cs{@@_split_wrong:Nw} \meta{variable} that were only useful in the
+%   case of invalid props, |#3|~is empty, and most importantly |#4| is
+%   \cs{use_ii:nnn}.  This command selects the \meta{false code}.
+%
+%   Note that we define \cs{@@_split_flat:w} in all cases even though it
+%   is only used in the flat case.  Indeed, to avoid taking in the whole
+%   property list (which may be large) as an argument more than strictly
+%   necessary, we would have to keep the \meta{true code} positioned
+%   before the expansion of the prop variable in order to use it in the
+%   definition.  The only way to do that is to store it using an
+%   assignment so we might as well just perform the assignment that we
+%   can actually use in the flat case.
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_split:NnTFn #1#2
-  { \exp_args:NNo \@@_split_aux:NnTFn #1 { \tl_to_str:n {#2} } }
-\cs_new_protected:Npn \@@_split_aux:NnTFn #1#2#3
-  {
-    \cs_set:Npn \@@_split_aux:w ##1 \@@_chk:w ##2
-      \@@_pair:wn #2 \s_@@ ##3 ##4 \s_@@_mark ##5 ##6 \s_@@_stop
-      { ##5 {#3} }
-    \exp_after:wN \@@_split_aux:w #1 \s_@@_mark \use_i:nnn
-      \@@_pair:wn #2 \s_@@ { } \s_@@_mark \use_ii:nnn
-      \@@_chk:w
-      \@@_pair:wn #2 \s_@@ { } \s_@@_mark \use_iii:nnn
+  {
+    \exp_after:wN \@@_split_aux:nNTFn
+    \exp_after:wN { \tl_to_str:n {#2} } #1
+  }
+\cs_new_protected:Npn \@@_split_aux:nNTFn #1#2#3
+  {
+    \cs_set:Npn \@@_split_flat:w \@@_split_linked:w ##1
+      \@@_pair:wn #1 \s_@@ ##2 ##3 \s_@@_mark ##4 ##5 \s_@@_stop
+      { ##4 {#3} }
+    \exp_after:wN \@@_split_test:wn #2 \s_@@_mark \use_i:nnn
+      \@@_pair:wn #1 \s_@@ { ? \fi: \@@_split_wrong:Nw #2 }
+      \s_@@_mark \use_ii:nnn
       \s_@@_stop
   }
+\cs_new:Npn \@@_split_flat:w { }
+\cs_new_protected:Npn \@@_split_test:wn #1 \s_@@ #2
+  {
+    \if_meaning:w \@@_chk:w #2 \exp_after:wN \@@_split_flat:w \fi:
+    \@@_split_linked:w
+  }
+\cs_new_protected:Npn \@@_split_linked:w #1 \s_@@_stop #2#3 {#3}
+\cs_new_protected:Npn \@@_split_wrong:Nw #1#2 \s_@@_stop #3#4
+  {
+    \prop_show:N #1
+    \cs_gset_eq:NN #1 \c_empty_prop
+    #3
+  }
 %    \end{macrocode}
 % \end{macro}
-% \end{macro}
-% \end{macro}
 %
 % \begin{macro}[tested = m3prop002]
 %   {
@@ -1611,7 +1650,7 @@
 \cs_new_protected:Npn \@@_get:NnnTF #1#2#3#4#5
   {
     \@@_split:NnTFn #1 {#2}
-      { #3 {##3} #4 }
+      { #3 {##2} #4 }
       {#5}
       { \exp_after:wN \@@_get_linked:w #1 {#2} {#3} {#4} {#5} }
   }
@@ -1715,8 +1754,8 @@
   {
     \@@_split:NnTFn #1 {#2}
       {
-        #4 #1 { \exp_not:n { \s_@@ \@@_chk:w ##2 ##4 } }
-        #5 {##3}
+        #4 #1 { \exp_not:n { \s_@@ \@@_chk:w ##1 ##3 } }
+        #5 {##2}
         #6
       }
       {#7}
@@ -2000,8 +2039,8 @@
       {
         #1 #2 #3
           {
-            \s_@@ \@@_chk:w \exp_not:n {##2}
-            \l_@@_internal_tl \exp_not:n {##4}
+            \s_@@ \@@_chk:w \exp_not:n {##1}
+            \l_@@_internal_tl \exp_not:n {##3}
           }
       }
       { #2 #3 { \exp_not:o {#3} \l_@@_internal_tl } }
diff --git a/l3kernel/testfiles/m3prop008.lvt b/l3kernel/testfiles/m3prop008.lvt
new file mode 100644
index 000000000..58e598ca8
--- /dev/null
+++ b/l3kernel/testfiles/m3prop008.lvt
@@ -0,0 +1,117 @@
+%
+% Copyright (C) The LaTeX Project
+%
+
+\documentclass{minimal}
+\input{regression-test}
+
+
+\ExplSyntaxOn
+\debug_on:n { deprecation , log-functions } % NOT check-declarations
+\ExplSyntaxOff
+
+
+\begin{document}
+\START
+\AUTHOR{Bruno Le Floch}
+\ExplSyntaxOn
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\TEST { setting~undefined~property~lists }
+  {
+    \prop_clear:N \l_A_prop
+    \prop_log:N \l_A_prop
+    \prop_set_from_keyval:Nn \l_tmpa_prop { a = b }
+    \prop_gset_eq:NN \g_B_prop \l_tmpa_prop
+    \prop_log:N \g_B_prop
+    \prop_set_from_keyval:Nn \l_C_prop { C = C }
+    \prop_log:N \l_C_prop
+  }
+
+\TEST { setting~c-type~undefined~property~lists }
+  {
+    \prop_clear:c { l_AA_prop }
+    \prop_log:c { l_AA_prop }
+    \prop_set_from_keyval:Nn \l_tmpa_prop { a = b }
+    \prop_gset_eq:cN { g_BB_prop } \l_tmpa_prop
+    \prop_log:c { g_BB_prop }
+    \prop_set_from_keyval:cn { l_CC_prop } { C = C }
+    \prop_log:c { l_CC_prop }
+  }
+
+\tl_new:N \l_A_tl
+\tl_new:N \g_B_tl
+\tl_new:N \l_C_tl
+
+\TEST { setting~invalid~property~lists }
+  {
+    \prop_clear:N \l_A_tl
+    \prop_log:N \l_A_tl
+    \prop_set_from_keyval:Nn \l_tmpa_prop { a = b }
+    \prop_gset_eq:NN \g_B_tl \l_tmpa_prop
+    \prop_log:N \g_B_tl
+    \prop_set_from_keyval:Nn \l_C_tl { C = C }
+    \prop_log:N \l_C_tl
+  }
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\cs_undefine:N \l_A_prop
+\cs_undefine:N \g_B_prop
+\cs_undefine:N \l_C_prop
+\cs_undefine:N \l_AA_prop
+\cs_undefine:N \g_BB_prop
+\cs_undefine:N \l_CC_prop
+
+\TEST { splitting~undefined~property~lists }
+  {
+    \prop_log:N \l_A_prop
+    \prop_get:NnNTF \l_A_prop { A } \l_tmpa_tl { \ERROR } { \FALSE }
+    \prop_log:N \l_A_prop
+    \tl_log:N \l_tmpa_tl
+    \prop_get:NnN \g_B_prop { A } \l_tmpa_tl
+    \prop_log:N \g_B_prop
+    \prop_put:Nnn \l_C_prop { A } { B }
+    \prop_log:N \l_C_prop
+    \prop_pop:NnNTF \l_C_prop { A } \l_tmpa_tl { \TRUE } { \ERROR }
+    \prop_log:N \l_C_prop
+    \tl_log:N \l_tmpa_tl
+  }
+
+\TEST { splitting~c-type~undefined~property~lists }
+  {
+    \prop_log:c { l_AA_prop }
+    \prop_get:cnNTF { l_AA_prop } { A } \l_tmpa_tl { \ERROR } { \FALSE }
+    \prop_log:c { l_AA_prop }
+    \tl_log:N \l_tmpa_tl
+    \prop_get:cnN { g_BB_prop } { A } \l_tmpa_tl
+    \prop_log:c { g_BB_prop }
+    \prop_put:cnn { l_CC_prop } { A } { B }
+    \prop_log:c { l_CC_prop }
+    \prop_pop:cnNTF { l_CC_prop } { A } \l_tmpa_tl { \TRUE } { \ERROR }
+    \prop_log:c { l_CC_prop }
+    \tl_log:N \l_tmpa_tl
+  }
+
+\tl_clear:N \l_A_tl
+\tl_gclear:N \g_B_tl
+\tl_clear:N \l_C_tl
+
+\TEST { splitting~invalid~property~lists }
+  {
+    \prop_log:N \l_A_tl
+    \prop_get:NnNTF \l_A_tl { A } \l_tmpa_tl { \ERROR } { \FALSE }
+    \prop_log:N \l_A_tl
+    \tl_log:N \l_tmpa_tl
+    \prop_get:NnN \g_B_tl { A } \l_tmpa_tl
+    \tl_log:N \l_tmpa_tl
+    \prop_put:Nnn \l_C_tl { A } { B }
+    \prop_log:N \l_C_tl
+    \prop_pop:NnNTF \l_C_tl { A } \l_tmpa_tl { \TRUE } { \ERROR }
+    \prop_log:N \l_C_tl
+    \tl_log:N \l_tmpa_tl
+  }
+
+\END
+
diff --git a/l3kernel/testfiles/m3prop008.tlg b/l3kernel/testfiles/m3prop008.tlg
new file mode 100644
index 000000000..0161bb731
--- /dev/null
+++ b/l3kernel/testfiles/m3prop008.tlg
@@ -0,0 +1,187 @@
+This is a generated file for the LaTeX (2e + expl3) validation system.
+Don't change this file in any respect.
+Author: Bruno Le Floch
+============================================================
+TEST 1: setting undefined property lists
+============================================================
+The property list \l_A_prop is empty
+> .
+The property list \g_B_prop contains the pairs (without outer braces):
+>  {a}  =>  {b}.
+The property list \l_C_prop contains the pairs (without outer braces):
+>  {C}  =>  {C}.
+============================================================
+============================================================
+TEST 2: setting c-type undefined property lists
+============================================================
+The property list \l_AA_prop is empty
+> .
+The property list \g_BB_prop contains the pairs (without outer braces):
+>  {a}  =>  {b}.
+The property list \l_CC_prop contains the pairs (without outer braces):
+>  {C}  =>  {C}.
+============================================================
+Defining \l_A_tl on line ...
+Defining \g_B_tl on line ...
+Defining \l_C_tl on line ...
+============================================================
+TEST 3: setting invalid property lists
+============================================================
+The property list \l_A_tl is empty
+> .
+The property list \g_B_tl contains the pairs (without outer braces):
+>  {a}  =>  {b}.
+The property list \l_C_tl contains the pairs (without outer braces):
+>  {C}  =>  {C}.
+============================================================
+============================================================
+TEST 4: splitting undefined property lists
+============================================================
+! LaTeX Error: Variable \l_A_prop undefined.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+LaTeX has been asked to show a variable \l_A_prop, but this has not been defined yet.
+! Undefined control sequence.
+<argument> \l_A_prop 
+l. ...  }
+The control sequence at the end of the top line
+of your error message was never \def'ed. If you have
+misspelled it (e.g., `\hobx'), type `I' and the correct
+spelling (e.g., `I\hbox'). Otherwise just continue,
+and I'll forget about whatever was undefined.
+! LaTeX Error: Variable \l_A_prop undefined.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+LaTeX has been asked to show a variable \l_A_prop, but this has not been defined yet.
+FALSE
+The property list \l_A_prop is empty
+> .
+> \l_tmpa_tl=.
+! Undefined control sequence.
+<argument> \g_B_prop 
+l. ...  }
+The control sequence at the end of the top line
+of your error message was never \def'ed. If you have
+misspelled it (e.g., `\hobx'), type `I' and the correct
+spelling (e.g., `I\hbox'). Otherwise just continue,
+and I'll forget about whatever was undefined.
+! LaTeX Error: Variable \g_B_prop undefined.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+LaTeX has been asked to show a variable \g_B_prop, but this has not been defined yet.
+The property list \g_B_prop is empty
+> .
+! Undefined control sequence.
+<argument> \l_C_prop 
+l. ...  }
+The control sequence at the end of the top line
+of your error message was never \def'ed. If you have
+misspelled it (e.g., `\hobx'), type `I' and the correct
+spelling (e.g., `I\hbox'). Otherwise just continue,
+and I'll forget about whatever was undefined.
+! LaTeX Error: Variable \l_C_prop undefined.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+LaTeX has been asked to show a variable \l_C_prop, but this has not been defined yet.
+The property list \l_C_prop contains the pairs (without outer braces):
+>  {A}  =>  {B}.
+TRUE
+The property list \l_C_prop is empty
+> .
+> \l_tmpa_tl=B.
+============================================================
+============================================================
+TEST 5: splitting c-type undefined property lists
+============================================================
+! LaTeX Error: Variable \l_AA_prop undefined.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+LaTeX has been asked to show a variable \l_AA_prop, but this has not been defined yet.
+! LaTeX Error: Variable \l_AA_prop undefined.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+LaTeX has been asked to show a variable \l_AA_prop, but this has not been defined yet.
+FALSE
+The property list \l_AA_prop is empty
+> .
+> \l_tmpa_tl=.
+! LaTeX Error: Variable \g_BB_prop undefined.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+LaTeX has been asked to show a variable \g_BB_prop, but this has not been defined yet.
+The property list \g_BB_prop is empty
+> .
+! LaTeX Error: Variable \l_CC_prop undefined.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+LaTeX has been asked to show a variable \l_CC_prop, but this has not been defined yet.
+The property list \l_CC_prop contains the pairs (without outer braces):
+>  {A}  =>  {B}.
+TRUE
+The property list \l_CC_prop is empty
+> .
+> \l_tmpa_tl=B.
+============================================================
+============================================================
+TEST 6: splitting invalid property lists
+============================================================
+! LaTeX Error: Variable '\l_A_tl' is not a valid prop.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_A_tl' with value
+should be a prop variable, but it does not have the correct internal structure:
+    \s__prop \__prop_chk:w 
+! LaTeX Error: Variable '\l_A_tl' is not a valid prop.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_A_tl' with value
+should be a prop variable, but it does not have the correct internal structure:
+    \s__prop \__prop_chk:w 
+FALSE
+The property list \l_A_tl is empty
+> .
+> \l_tmpa_tl=.
+! LaTeX Error: Variable '\g_B_tl' is not a valid prop.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\g_B_tl' with value
+should be a prop variable, but it does not have the correct internal structure:
+    \s__prop \__prop_chk:w 
+> \l_tmpa_tl=\q_no_value .
+! LaTeX Error: Variable '\l_C_tl' is not a valid prop.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_C_tl' with value
+should be a prop variable, but it does not have the correct internal structure:
+    \s__prop \__prop_chk:w 
+The property list \l_C_tl contains the pairs (without outer braces):
+>  {A}  =>  {B}.
+TRUE
+The property list \l_C_tl is empty
+> .
+> \l_tmpa_tl=B.
+============================================================





More information about the latex3-commits mailing list.