[latex3-commits] [git/LaTeX3-latex3-latex3] main: Validate internal structure when showing complex datatypes (fixes #884) (d4253c7db)

Bruno Le Floch blflatex at gmail.com
Thu Apr 29 22:08:23 CEST 2021


Repository : https://github.com/latex3/latex3
On branch  : main
Link       : https://github.com/latex3/latex3/commit/d4253c7dbdb308e01aecf3a91e111f45fee62a62

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

commit d4253c7dbdb308e01aecf3a91e111f45fee62a62
Author: Bruno Le Floch <blflatex at gmail.com>
Date:   Thu Apr 29 01:07:58 2021 +0200

    Validate internal structure when showing complex datatypes (fixes #884)
    
    I opted not to fix the fact that \int_show:N, \dim_show:N etc are
    equivalent (hence accept the wrong type of data) because this is messy
    to check.


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

d4253c7dbdb308e01aecf3a91e111f45fee62a62
 l3kernel/CHANGELOG.md                   |   1 +
 l3kernel/l3clist.dtx                    |  27 ++++--
 l3kernel/l3fp-assign.dtx                |  18 +++-
 l3kernel/l3fp.dtx                       |   4 +-
 l3kernel/l3kernel-functions.dtx         |  13 +++
 l3kernel/l3msg.dtx                      |  25 +++++
 l3kernel/l3prg.dtx                      |  16 +++-
 l3kernel/l3prop.dtx                     |  25 ++++-
 l3kernel/l3regex.dtx                    | 159 +++++++++++++++++++++++++++++++-
 l3kernel/l3seq.dtx                      |  21 ++++-
 l3kernel/l3str.dtx                      |  16 +++-
 l3kernel/l3tl.dtx                       |  50 +++++++++-
 l3kernel/testfiles/m3show003.luatex.tlg | 137 +++++++++++++++++++++++++++
 l3kernel/testfiles/m3show003.lvt        |  31 +++++++
 l3kernel/testfiles/m3show003.tlg        | 137 +++++++++++++++++++++++++++
 15 files changed, 646 insertions(+), 34 deletions(-)

diff --git a/l3kernel/CHANGELOG.md b/l3kernel/CHANGELOG.md
index c628e5746..388fe7d2c 100644
--- a/l3kernel/CHANGELOG.md
+++ b/l3kernel/CHANGELOG.md
@@ -25,6 +25,7 @@ this project uses date-based 'snapshot' version identifiers.
 - Show printable characters explicitly in `\regex_show:n`
 - Regex replacement now errors when using a submatch (`\1` etc) for which
   the regex has too few groups
+- Showing complex datatypes now validates their internal structure (issue #884)
 
 ### Fixed
 - Evalutate integer constants only once (issue #861)
diff --git a/l3kernel/l3clist.dtx b/l3kernel/l3clist.dtx
index 6299dd115..36ec3bacc 100644
--- a/l3kernel/l3clist.dtx
+++ b/l3kernel/l3clist.dtx
@@ -703,7 +703,7 @@
 %
 % \section{Viewing comma lists}
 %
-% \begin{function}[updated = 2015-08-03]{\clist_show:N, \clist_show:c}
+% \begin{function}[updated = 2021-04-29]{\clist_show:N, \clist_show:c}
 %   \begin{syntax}
 %     \cs{clist_show:N} \meta{comma list}
 %   \end{syntax}
@@ -717,7 +717,7 @@
 %   Displays the entries in the comma list in the terminal.
 % \end{function}
 %
-% \begin{function}[added = 2014-08-22, updated = 2015-08-03]{\clist_log:N, \clist_log:c}
+% \begin{function}[added = 2014-08-22, updated = 2021-04-29]{\clist_log:N, \clist_log:c}
 %   \begin{syntax}
 %     \cs{clist_log:N} \meta{comma list}
 %   \end{syntax}
@@ -2009,8 +2009,9 @@
 % \subsection{Viewing comma lists}
 %
 % \begin{macro}{\clist_show:N, \clist_show:c, \clist_log:N, \clist_log:c, \@@_show:NN}
-%   Apply the general \cs{__kernel_chk_defined:NT} and
-%   \cs{msg_show:nnnnnn}.
+%   Apply the general \cs{__kernel_chk_tl_type:NnnT} with \cs{exp_not:o}
+%   |#2| serving as a dummy code to prevent a check performed by this
+%   auxiliary.
 %    \begin{macrocode}
 \cs_new_protected:Npn \clist_show:N { \@@_show:NN \__kernel_msg_show:nnxxxx }
 \cs_generate_variant:Nn \clist_show:N { c }
@@ -2018,12 +2019,20 @@
 \cs_generate_variant:Nn \clist_log:N { c }
 \cs_new_protected:Npn \@@_show:NN #1#2
   {
-    \__kernel_chk_defined:NT #2
+    \__kernel_chk_tl_type:NnnT #2 { clist } { \exp_not:o #2 }
       {
-        #1 { clist } { show }
-          { \token_to_str:N #2 }
-          { \clist_map_function:NN #2 \msg_show_item:n }
-          { } { }
+        \int_compare:nNnTF { \clist_count:N #2 }
+          = { \exp_args:No \clist_count:n #2 }
+          {
+            #1 { clist } { show }
+              { \token_to_str:N #2 }
+              { \clist_map_function:NN #2 \msg_show_item:n }
+              { } { }
+          }
+          {
+            \__kernel_msg_error:nnxx { kernel } { non-clist }
+              { \token_to_str:N #2 } { \tl_to_str:N #2 }
+          }
       }
   }
 %    \end{macrocode}
diff --git a/l3kernel/l3fp-assign.dtx b/l3kernel/l3fp-assign.dtx
index 30d9cc9ad..7da8b1dfa 100644
--- a/l3kernel/l3fp-assign.dtx
+++ b/l3kernel/l3fp-assign.dtx
@@ -165,6 +165,7 @@
 % \subsection{Showing values}
 %
 % \begin{macro}{\fp_show:N, \fp_show:c, \fp_log:N, \fp_log:c, \@@_show:NN}
+% \begin{macro}[EXP]{\@@_show_validate:w}
 %   This shows the result of computing its argument by
 %   passing the right data to \cs{tl_show:n} or \cs{tl_log:n}.
 %    \begin{macrocode}
@@ -174,11 +175,26 @@
 \cs_generate_variant:Nn \fp_log:N { c }
 \cs_new_protected:Npn \@@_show:NN #1#2
   {
-    \__kernel_chk_defined:NT #2
+    \__kernel_chk_tl_type:NnnT #2 { fp }
+      {
+        \str_if_eq:eeTF { \tl_head:N #2 } { \s_@@_tuple } { \exp_not:o #2 }
+          {
+            \exp_after:wN \@@_show_validate:w #2
+            \s_@@ \@@_chk:w ??? ; \s_@@_stop
+          }
+      }
       { \exp_args:Nx #1 { \token_to_str:N #2 = \fp_to_tl:N #2 } }
   }
+\cs_new:Npn \@@_show_validate:w
+    #1 \s_@@ \@@_chk:w #2#3#4#5 ; #6 \s_@@_stop
+  {
+    \token_if_eq_meaning:NNTF #2 1
+      { \s_@@ \@@_chk:w #2 #3 {#4} #5 ; }
+      { \s_@@ \@@_chk:w #2 #3 #4 #5 ; }
+  }
 %    \end{macrocode}
 % \end{macro}
+% \end{macro}
 %
 % \begin{macro}{\fp_show:n, \fp_log:n}
 %   Use general tools.
diff --git a/l3kernel/l3fp.dtx b/l3kernel/l3fp.dtx
index 4be73958d..0deb4e25f 100644
--- a/l3kernel/l3fp.dtx
+++ b/l3kernel/l3fp.dtx
@@ -769,7 +769,7 @@
 %
 % \section{Viewing floating points}
 %
-% \begin{function}[added = 2012-05-08, updated = 2015-08-07,
+% \begin{function}[added = 2012-05-08, updated = 2021-04-29,
 %   tested = m3fp002]{\fp_show:N, \fp_show:c, \fp_show:n}
 %   \begin{syntax}
 %     \cs{fp_show:N} \meta{fp~var}
@@ -779,7 +779,7 @@
 %   result in the terminal.
 % \end{function}
 %
-% \begin{function}[added = 2014-08-22, updated = 2015-08-07]
+% \begin{function}[added = 2014-08-22, updated = 2021-04-29]
 %   {\fp_log:N, \fp_log:c, \fp_log:n}
 %   \begin{syntax}
 %     \cs{fp_log:N} \meta{fp~var}
diff --git a/l3kernel/l3kernel-functions.dtx b/l3kernel/l3kernel-functions.dtx
index 09a3f3022..32ae13ce9 100644
--- a/l3kernel/l3kernel-functions.dtx
+++ b/l3kernel/l3kernel-functions.dtx
@@ -92,6 +92,19 @@
 %   purposes.
 % \end{function}
 %
+% \begin{function}{\__kernel_chk_tl_type:NnnT}
+%   \begin{syntax}
+%     \cs{__kernel_chk_tl_type:NnnT} \meta{control sequence} \Arg{specific type} \\
+%     \ \ \Arg{reconstruction} \Arg{true code}
+%   \end{syntax}
+%   Helper to test that the \meta{control sequence} is a variable of the
+%   given \meta{specific type} of token list.  Produces suitable error
+%   messages if the \meta{control sequence} does not exist, or if it is
+%   not a token list variable at all, or if the \meta{control sequence}
+%   differs from the result of |x|-expanding \meta{reconstruction}.  If
+%   all of these tests succeed then the \meta{true code} is run.
+% \end{function}
+%
 % \begin{function}{\__kernel_cs_parm_from_arg_count:nnF}
 %   \begin{syntax}
 %     \cs{__kernel_cs_parm_from_arg_count:nnF} \Arg{follow-on} \Arg{args} \Arg{false code}
diff --git a/l3kernel/l3msg.dtx b/l3kernel/l3msg.dtx
index 258af244b..3f7fe9af1 100644
--- a/l3kernel/l3msg.dtx
+++ b/l3kernel/l3msg.dtx
@@ -2015,6 +2015,31 @@
     LaTeX~has~been~asked~to~show~a~variable~#1,~but~this~has~not~
     been~defined~yet.
   }
+\__kernel_msg_new:nnnn { kernel } { bad-type }
+  { Variable~'#1'~is~not~a~valid~#3. }
+  {
+    \c_@@_coding_error_text_tl
+    The~variable~'#1'~with~\tl_if_empty:nTF {#4} {meaning} {value}\\\\
+    \iow_indent:n {#2}\\\\
+    should~be~a~#3~variable,~but~
+    \tl_if_empty:nTF {#4}
+      { it~is~not \str_if_eq:nnF {#3} { bool } { ~a~short~macro } . }
+      {
+        it~does~not~have~the~correct~
+        \str_if_eq:nnTF {#2} {#4}
+          { category~codes. }
+          { internal~structure:\\\\\iow_indent:n {#4} }
+      }
+  }
+\__kernel_msg_new:nnnn { kernel } { non-clist }
+  { Variable~'#1'~is~not~a~valid~clist. }
+  {
+    \c_@@_coding_error_text_tl
+    The~variable~'#1'~with~value\\\\
+    \iow_indent:n {#2}\\\\
+    should~be~a~clist~variable,~but~it~includes~empty~or~blank~items~
+    without~braces.
+  }
 %    \end{macrocode}
 %
 % Some errors are only needed in package mode if debugging is enabled by
diff --git a/l3kernel/l3prg.dtx b/l3kernel/l3prg.dtx
index 0b6eecbbc..1f9f08679 100644
--- a/l3kernel/l3prg.dtx
+++ b/l3kernel/l3prg.dtx
@@ -313,7 +313,7 @@
 %   based on this result.
 % \end{function}
 %
-% \begin{function}[added = 2012-02-09, updated = 2015-08-01]{\bool_show:N, \bool_show:c}
+% \begin{function}[added = 2012-02-09, updated = 2021-04-29]{\bool_show:N, \bool_show:c}
 %   \begin{syntax}
 %     \cs{bool_show:N} \meta{boolean}
 %   \end{syntax}
@@ -328,7 +328,7 @@
 %   terminal.
 % \end{function}
 %
-% \begin{function}[added = 2014-08-22, updated = 2015-08-03]{\bool_log:N, \bool_log:c}
+% \begin{function}[added = 2014-08-22, updated = 2021-04-29]{\bool_log:N, \bool_log:c}
 %   \begin{syntax}
 %     \cs{bool_log:N} \meta{boolean}
 %   \end{syntax}
@@ -987,7 +987,17 @@
 \cs_new_protected:Npn \@@_show:NN #1#2
   {
     \__kernel_chk_defined:NT #2
-      { \exp_args:Nx #1 { \token_to_str:N #2 = \@@_to_str:n {#2} } }
+      {
+        \token_case_meaning:NnF #2
+          {
+            \c_true_bool { \exp_args:Nx #1 { \token_to_str:N #2 = true } }
+            \c_false_bool { \exp_args:Nx #1 { \token_to_str:N #2 = false } }
+          }
+          {
+            \__kernel_msg_error:nnxxx { kernel } { bad-type }
+              { \token_to_str:N #2 } { \token_to_meaning:N #2 } { bool }
+          }
+      }
   }
 %    \end{macrocode}
 % \end{macro}
diff --git a/l3kernel/l3prop.dtx b/l3kernel/l3prop.dtx
index f55de55fa..7cc144073 100644
--- a/l3kernel/l3prop.dtx
+++ b/l3kernel/l3prop.dtx
@@ -480,14 +480,14 @@
 %
 % \section{Viewing property lists}
 %
-% \begin{function}[updated = 2015-08-01]{\prop_show:N, \prop_show:c}
+% \begin{function}[updated = 2021-04-29]{\prop_show:N, \prop_show:c}
 %   \begin{syntax}
 %     \cs{prop_show:N} \meta{property list}
 %   \end{syntax}
 %   Displays the entries in the \meta{property list} in the terminal.
 % \end{function}
 %
-% \begin{function}[added = 2014-08-12, updated = 2015-08-01]{\prop_log:N, \prop_log:c}
+% \begin{function}[added = 2014-08-12, updated = 2021-04-29]{\prop_log:N, \prop_log:c}
 %   \begin{syntax}
 %     \cs{prop_log:N} \meta{property list}
 %   \end{syntax}
@@ -1346,8 +1346,10 @@
 %
 % \begin{macro}[tested = m3show001]
 %   {\prop_show:N, \prop_show:c, \prop_log:N, \prop_log:c}
-%   Apply the general \cs{__kernel_chk_defined:NT} and
-%   \cs{msg_show:nnnnnn}. Contrarily to sequences and comma lists,
+% \begin{macro}{\@@_show:NN}
+% \begin{macro}[rEXP]{\@@_show_validate:w}
+%   Apply the general \cs{__kernel_chk_tl_type:NnnT}.
+%   Contrarily to sequences and comma lists,
 %   we use \cs{msg_show_item:nn} to format both the key and the value
 %   for each pair.
 %    \begin{macrocode}
@@ -1357,7 +1359,12 @@
 \cs_generate_variant:Nn \prop_log:N { c }
 \cs_new_protected:Npn \@@_show:NN #1#2
   {
-    \__kernel_chk_defined:NT #2
+    \__kernel_chk_tl_type:NnnT #2 { prop }
+      {
+        \s_@@
+        \exp_after:wN \use_i:nn \exp_after:wN \@@_show_validate:w #2
+        \@@_pair:wn \q_recursion_tail \s_@@ { } \q_recursion_stop
+      }
       {
         #1 { prop } { show }
           { \token_to_str:N #2 }
@@ -1365,8 +1372,16 @@
           { } { }
       }
   }
+\cs_new:Npn \@@_show_validate:w #1 \@@_pair:wn #2 \s_@@ #3
+  {
+    \quark_if_recursion_tail_stop:n {#2}
+    \exp_not:N \@@_pair:wn \tl_to_str:n {#2} \s_@@ \exp_not:n { {#3} }
+    \@@_show_validate:w
+  }
 %    \end{macrocode}
 % \end{macro}
+% \end{macro}
+% \end{macro}
 %
 %    \begin{macrocode}
 %</package>
diff --git a/l3kernel/l3regex.dtx b/l3kernel/l3regex.dtx
index 8cf3071a0..30d4abd78 100644
--- a/l3kernel/l3regex.dtx
+++ b/l3kernel/l3regex.dtx
@@ -544,7 +544,7 @@
 %   which never change.
 % \end{function}
 %
-% \begin{function}[added = 2021-04-26]
+% \begin{function}[added = 2021-04-26, updated = 2021-04-29]
 %   {\regex_show:N, \regex_show:n, \regex_log:N, \regex_log:n}
 %   \begin{syntax}
 %     \cs{regex_show:n} \Arg{regex}
@@ -3626,6 +3626,160 @@
 %
 % \subsubsection{Showing regexes}
 %
+% \begin{macro}[rEXP]
+%   {
+%     \@@_clean_bool:n, \@@_clean_int:n, \@@_clean_int_aux:N,
+%     \@@_clean_regex:n, \@@_clean_regex_loop:w, \@@_clean_branch:n,
+%     \@@_clean_branch_loop:n, \@@_clean_assertion:Nn,
+%     \@@_clean_class:NnnnN, \@@_clean_group:nnnN, \@@_clean_class:n,
+%     \@@_clean_class_loop:nnn, \@@_clean_exact_cs:n,
+%     \@@_clean_exact_cs:w
+%   }
+%   Before showing a regex we check that it is \enquote{clean} in the
+%   sense that it has the correct internal structure.  We do this (in
+%   the implementation of \cs{regex_show:N} and \cs{regex_log:N}) by
+%   comparing it with a cleaned-up version of the same regex.  Along the
+%   way we also need similar functions for other types: all
+%   \cs[no-index]{@@_clean_\meta{type}:n} functions produce valid
+%   \meta{type} tokens (bool, explicit integer, etc.\@) from arbitrary
+%   input, and the output coincides with the input if that was valid.
+%    \begin{macrocode}
+\cs_new:Npn \@@_clean_bool:n #1
+  {
+    \tl_if_single:nTF {#1}
+      { \bool_if:NTF #1 \c_true_bool \c_false_bool }
+      { \c_true_bool }
+  }
+\cs_new:Npn \@@_clean_int:n #1
+  {
+    \tl_if_head_eq_meaning:nNTF {#1} -
+      { - \exp_args:No \@@_clean_int:n { \use_none:n #1 } }
+      { \int_eval:n { 0 \str_map_function:nN {#1} \@@_clean_int_aux:N } }
+  }
+\cs_new:Npn \@@_clean_int_aux:N #1
+  {
+    \if_int_compare:w 1 < 1 #1 ~
+      #1
+    \else:
+      \exp_after:wN \str_map_break:
+    \fi:
+  }
+\cs_new:Npn \@@_clean_regex:n #1
+  {
+    \@@_clean_regex_loop:w #1
+    \@@_branch:n { \q_recursion_tail } \q_recursion_stop
+  }
+\cs_new:Npn \@@_clean_regex_loop:w #1 \@@_branch:n #2
+  {
+    \quark_if_recursion_tail_stop:n {#2}
+    \@@_branch:n { \@@_clean_branch:n {#2} }
+    \@@_clean_regex_loop:w
+  }
+\cs_new:Npn \@@_clean_branch:n #1
+  {
+    \@@_clean_branch_loop:n #1
+    ? ? ? ? ? ? \prg_break_point:
+  }
+\cs_new:Npn \@@_clean_branch_loop:n #1
+  {
+    \tl_if_single:nF {#1} { \prg_break: }
+    \token_case_meaning:NnF #1
+      {
+        \@@_command_K: { #1 \@@_clean_branch_loop:n }
+        \@@_assertion:Nn { #1 \@@_clean_assertion:Nn }
+        \@@_class:NnnnN { #1 \@@_clean_class:NnnnN }
+        \@@_group:nnnN { #1 \@@_clean_group:nnnN }
+        \@@_group_no_capture:nnnN { #1 \@@_clean_group:nnnN }
+        \@@_group_resetting:nnnN { #1 \@@_clean_group:nnnN }
+      }
+      { \prg_break: }
+  }
+\cs_new:Npn \@@_clean_assertion:Nn #1#2
+  {
+    \@@_clean_bool:n {#1}
+    \tl_if_single:nF {#2} { { \@@_A_test: } \prg_break: }
+    \token_case_meaning:NnTF #2
+      {
+        \@@_A_test: { }
+        \@@_G_test: { }
+        \@@_Z_test: { }
+        \@@_b_test: { }
+      }
+      { {#2} }
+      { { \@@_A_test: } \prg_break: }
+    \@@_clean_branch_loop:n
+  }
+\cs_new:Npn \@@_clean_class:NnnnN #1#2#3#4#5
+  {
+    \@@_clean_bool:n {#1}
+    { \@@_clean_class:n {#2} }
+    { \int_max:nn { 0 } { \@@_clean_int:n {#3} } }
+    { \int_max:nn { -1 } { \@@_clean_int:n {#4} } }
+    \@@_clean_bool:n {#5}
+    \@@_clean_branch_loop:n
+  }
+\cs_new:Npn \@@_clean_group:nnnN #1#2#3#4
+  {
+    { \@@_clean_regex:n {#1} }
+    { \int_max:nn { 0 } { \@@_clean_int:n {#2} } }
+    { \int_max:nn { -1 } { \@@_clean_int:n {#3} } }
+    \@@_clean_bool:n {#4}
+    \@@_clean_branch_loop:n
+  }
+\cs_new:Npn \@@_clean_class:n #1
+  { \@@_clean_class_loop:nnn #1 ????? \prg_break_point: }
+\cs_new:Npn \@@_clean_class_loop:nnn #1#2#3
+  {
+    \tl_if_single:nF {#1} { \prg_break: }
+    \token_case_meaning:NnTF #1
+      {
+        \@@_item_cs:n { #1 { \@@_clean_regex:n {#2} } }
+        \@@_item_exact_cs:n { #1 { \@@_clean_exact_cs:n {#2} } }
+        \@@_item_caseful_equal:n { #1 { \@@_clean_int:n {#2} } }
+        \@@_item_caseless_equal:n { #1 { \@@_clean_int:n {#2} } }
+        \@@_item_reverse:n { #1 { \@@_clean_class:n {#2} } }
+      }
+      { \@@_clean_class_loop:nnn {#3} }
+      {
+        \token_case_meaning:NnTF #1
+          {
+            \@@_item_caseful_range:nn { }
+            \@@_item_caseless_range:nn { }
+            \@@_item_exact:nn { }
+          }
+          { #1 { \@@_clean_int:n {#2} } { \@@_clean_int:n {#3} } }
+          {
+            \token_case_meaning:NnTF #1
+              {
+                \@@_item_catcode:nT { }
+                \@@_item_catcode_reverse:nT { }
+              }
+              {
+                #1 { \@@_clean_int:n {#2} } { \@@_clean_class:n {#3} }
+                \@@_clean_class_loop:nnn
+              }
+              { \prg_break: }
+          }
+      }
+  }
+\cs_new:Npn \@@_clean_exact_cs:n #1
+  {
+    \exp_last_unbraced:Nf \use_none:n
+      {
+        \@@_clean_exact_cs:w #1
+        \scan_stop: \q_recursion_tail \scan_stop:
+        \q_recursion_stop
+      }
+  }
+\cs_new:Npn \@@_clean_exact_cs:w #1 \scan_stop:
+  {
+    \quark_if_recursion_tail_stop:n {#1}
+    \scan_stop: \tl_to_str:n {#1}
+    \@@_clean_exact_cs:w
+  }
+%    \end{macrocode}
+% \end{macro}
+%
 % \begin{macro}{\@@_show:N}
 %   Within a group and within \cs{tl_build_begin:N} \ldots{} \cs{tl_build_end:N} we
 %   redefine all the function that can appear in a compiled regex, then
@@ -6045,7 +6199,8 @@
 \cs_new_protected:Npn \regex_log:N { \@@_show:NN \__kernel_msg_log:nnxxxx }
 \cs_new_protected:Npn \@@_show:NN #1#2
   {
-    \__kernel_chk_defined:NT #2
+    \__kernel_chk_tl_type:NnnT #2 { regex }
+      { \exp_args:No \@@_clean_regex:n {#2} }
       {
         \@@_show:N #2
         #1 { regex } { show }
diff --git a/l3kernel/l3seq.dtx b/l3kernel/l3seq.dtx
index a0a806589..3c03b042b 100644
--- a/l3kernel/l3seq.dtx
+++ b/l3kernel/l3seq.dtx
@@ -988,14 +988,14 @@
 %
 % \section{Viewing sequences}
 %
-% \begin{function}[updated = 2015-08-01]{\seq_show:N, \seq_show:c}
+% \begin{function}[updated = 2021-04-29]{\seq_show:N, \seq_show:c}
 %   \begin{syntax}
 %     \cs{seq_show:N} \meta{sequence}
 %   \end{syntax}
 %   Displays the entries in the \meta{sequence} in the terminal.
 % \end{function}
 %
-% \begin{function}[added = 2014-08-12, updated = 2015-08-01]{\seq_log:N, \seq_log:c}
+% \begin{function}[added = 2014-08-12, updated = 2021-04-29]{\seq_log:N, \seq_log:c}
 %   \begin{syntax}
 %     \cs{seq_log:N} \meta{sequence}
 %   \end{syntax}
@@ -2359,8 +2359,9 @@
 % \subsection{Viewing sequences}
 %
 % \begin{macro}{\seq_show:N, \seq_show:c, \seq_log:N, \seq_log:c, \@@_show:NN}
+% \begin{macro}[rEXP]{\@@_show_validate:nn}
 % \UnitTested
-%   Apply the general \cs{msg_show:nnnnnn}.
+%   Apply the general \cs{__kernel_chk_tl_type:NnnT}.
 %    \begin{macrocode}
 \cs_new_protected:Npn \seq_show:N { \@@_show:NN \__kernel_msg_show:nnxxxx }
 \cs_generate_variant:Nn \seq_show:N { c }
@@ -2368,7 +2369,12 @@
 \cs_generate_variant:Nn \seq_log:N { c }
 \cs_new_protected:Npn \@@_show:NN #1#2
   {
-    \__kernel_chk_defined:NT #2
+    \__kernel_chk_tl_type:NnnT #2 { seq }
+      {
+        \s_@@
+        \exp_after:wN \use_i:nn \exp_after:wN \@@_show_validate:nn #2
+        \q_recursion_tail \q_recursion_tail \q_recursion_stop
+      }
       {
         #1 { seq } { show }
           { \token_to_str:N #2 }
@@ -2376,8 +2382,15 @@
           { } { }
       }
   }
+\cs_new:Npn \@@_show_validate:nn #1#2
+  {
+    \quark_if_recursion_tail_stop:n {#2}
+    \@@_wrap_item:n {#2}
+    \@@_show_validate:nn
+  }
 %    \end{macrocode}
 % \end{macro}
+% \end{macro}
 %
 % \subsection{Scratch sequences}
 %
diff --git a/l3kernel/l3str.dtx b/l3kernel/l3str.dtx
index 953d36646..8b3dfccb5 100644
--- a/l3kernel/l3str.dtx
+++ b/l3kernel/l3str.dtx
@@ -797,7 +797,7 @@
 %
 % \section{Viewing strings}
 %
-% \begin{function}[added = 2015-09-18]
+% \begin{function}[added = 2015-09-18, updated = 2021-04-29]
 %   {\str_show:N, \str_show:c, \str_show:n}
 %   \begin{syntax}
 %     \cs{str_show:N} \meta{str~var}
@@ -805,7 +805,7 @@
 %   Displays the content of the \meta{str~var} on the terminal.
 % \end{function}
 %
-% \begin{function}[added = 2019-02-15]
+% \begin{function}[added = 2019-02-15, updated = 2021-04-29]
 %   {\str_log:N, \str_log:c, \str_log:n}
 %   \begin{syntax}
 %     \cs{str_log:N} \meta{str~var}
@@ -1983,10 +1983,18 @@
 %   Displays a string on the terminal.
 %    \begin{macrocode}
 \cs_new_eq:NN \str_show:n \tl_show:n
-\cs_new_eq:NN \str_show:N \tl_show:N
+\cs_new_protected:Npn \str_show:N #1
+  {
+    \__kernel_chk_tl_type:NnnT #1 { str } { \tl_to_str:N #1 }
+      { \tl_show:N #1 }
+  }
 \cs_generate_variant:Nn \str_show:N { c }
 \cs_new_eq:NN \str_log:n \tl_log:n
-\cs_new_eq:NN \str_log:N \tl_log:N
+\cs_new_protected:Npn \str_log:N #1
+  {
+    \__kernel_chk_tl_type:NnnT #1 { str } { \tl_to_str:N #1 }
+      { \tl_log:N #1 }
+  }
 \cs_generate_variant:Nn \str_log:N { c }
 %    \end{macrocode}
 % \end{macro}
diff --git a/l3kernel/l3tl.dtx b/l3kernel/l3tl.dtx
index 15c26d379..dd11b5159 100644
--- a/l3kernel/l3tl.dtx
+++ b/l3kernel/l3tl.dtx
@@ -1169,7 +1169,7 @@
 %
 % \section{Viewing token lists}
 %
-% \begin{function}[updated = 2015-08-01]{\tl_show:N, \tl_show:c}
+% \begin{function}[updated = 2021-04-29]{\tl_show:N, \tl_show:c}
 %   \begin{syntax}
 %     \cs{tl_show:N} \meta{tl~var}
 %   \end{syntax}
@@ -1191,7 +1191,7 @@
 %   \end{texnote}
 % \end{function}
 %
-% \begin{function}[added = 2014-08-22, updated = 2015-08-01]{\tl_log:N, \tl_log:c}
+% \begin{function}[added = 2014-08-22, updated = 2021-04-29]{\tl_log:N, \tl_log:c}
 %   \begin{syntax}
 %     \cs{tl_log:N} \meta{tl~var}
 %   \end{syntax}
@@ -3531,8 +3531,16 @@
   {
     \__kernel_chk_defined:NT #2
       {
-        \exp_args:Ne #1
-          { \token_to_str:N #2 = \__kernel_exp_not:w \exp_after:wN {#2} }
+        \exp_args:Nf \tl_if_empty:nTF
+          { \cs_prefix_spec:N #2 \cs_argument_spec:N #2 }
+          {
+            \exp_args:Ne #1
+              { \token_to_str:N #2 = \__kernel_exp_not:w \exp_after:wN {#2} }
+          }
+          {
+            \__kernel_msg_error:nnxxx { kernel } { bad-type }
+              { \token_to_str:N #2 } { \token_to_meaning:N #2 } { tl }
+          }
       }
   }
 %    \end{macrocode}
@@ -3584,6 +3592,40 @@
 %    \end{macrocode}
 % \end{macro}
 %
+% \begin{macro}{\__kernel_chk_tl_type:NnnT}
+%   Helper for checking that |#1| has the correct internal structure to
+%   be of a certain type.  Make sure that it is defined and that it is a
+%   token list, namely a macro with no \tn{long} nor \tn{protected}
+%   prefix.  Then compare |#1| to an attempt at reconstructing a valid
+%   structure of the given type using |#2| (see implementation of
+%   \cs{seq_show:N} for instance).  If that is successful run the
+%   requested code~|#4|.
+%    \begin{macrocode}
+\cs_new_protected:Npn \__kernel_chk_tl_type:NnnT #1#2#3#4
+  {
+    \__kernel_chk_defined:NT #1
+      {
+        \exp_args:Nf \tl_if_empty:nTF
+          { \cs_prefix_spec:N #1 \cs_argument_spec:N #1 }
+          {
+            \tl_set:Nx \l_@@_internal_a_tl {#3}
+            \tl_if_eq:NNTF #1 \l_@@_internal_a_tl
+              {#4}
+              {
+                \__kernel_msg_error:nnxxxx { kernel } { bad-type }
+                  { \token_to_str:N #1 } { \tl_to_str:N #1 }
+                  {#2} { \tl_to_str:N \l_@@_internal_a_tl }
+              }
+          }
+          {
+            \__kernel_msg_error:nnxxx { kernel } { bad-type }
+              { \token_to_str:N #1 } { \token_to_meaning:N #1 } {#2}
+          }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
 % \subsection{Internal scan marks}
 %
 % \begin{variable}{\s_@@_nil,\s_@@_mark,\s_@@_stop}
diff --git a/l3kernel/testfiles/m3show003.luatex.tlg b/l3kernel/testfiles/m3show003.luatex.tlg
index 89b88b09a..bfa810551 100644
--- a/l3kernel/testfiles/m3show003.luatex.tlg
+++ b/l3kernel/testfiles/m3show003.luatex.tlg
@@ -251,3 +251,140 @@ l. ...  }
 <recently read> }
 l. ...  }
 ============================================================
+============================================================
+TEST 17: Bad types
+============================================================
+! LaTeX3 Error: Variable 'A' is not a valid bool.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable 'A' with meaning
+    the letter A
+should be a bool variable, but it is not.
+! LaTeX3 Error: Variable '\c_empty_tl' is not a valid bool.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\c_empty_tl' with meaning
+    macro:->
+should be a bool variable, but it is not.
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid clist.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    a,
+should be a clist variable, but it includes empty or blank items without
+braces.
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid str.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    a,
+should be a str variable, but it does not have the correct category codes.
+> \l_tmpa_int=0.
+<recently read> }
+l. ...  }
+> \l_tmpa_dim=0.0pt.
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid fp.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    a,
+should be a fp variable, but it does not have the correct internal structure:
+    \s__fp \__fp_chk:w ???;
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid fp.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    \s__fp \__fp_chk:w 10{1}{1000}{0000}{0000}{0000};;
+should be a fp variable, but it does not have the correct internal structure:
+    \s__fp \__fp_chk:w 10{1}{1000}{0000}{0000}{0000};
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid prop.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    \s__fp \__fp_chk:w 10{1}{1000}{0000}{0000}{0000};;
+should be a prop variable, but it does not have the correct internal
+structure:
+    \s__prop 
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid seq.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    \s__fp \__fp_chk:w 10{1}{1000}{0000}{0000}{0000};;
+should be a seq variable, but it does not have the correct internal structure:
+    \s__seq \__seq_item:n {1}\__seq_item:n {1}\__seq_item:n
+    {0000}\__seq_item:n {0000}\__seq_item:n {;}
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid seq.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    \s__seq \__seq_item:n {a}\__seq_item:n {b}?{c}
+should be a seq variable, but it does not have the correct internal structure:
+    \s__seq \__seq_item:n {a}\__seq_item:n {b}\__seq_item:n {c}
+! LaTeX3 Error: Variable '\use:n' is not a valid tl.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\use:n' with meaning
+    \long macro:#1->#1
+should be a tl variable, but it is not a short macro.
+============================================================
+============================================================
+TEST 18: Bad types for regex
+============================================================
+! LaTeX3 Error: Variable '\use:n' is not a valid regex.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\use:n' with meaning
+    \long macro:#1->#1
+should be a regex variable, but it is not a short macro.
+> Compiled regex variable \l_tmpa_tl:.
+<recently read> }
+l. ...  }
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid regex.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    {\__regex_class:NnnnN \c_true_bool {\__regex_item_caseful_equal:n
+    {97}}{1}{0}\c_false_bool }\__regex_branch:n {\__regex_class:NnnnN
+    \c_true_bool {\__regex_item_caseful_equal:n {98}}{1}{0}\c_false_bool
+    }\__regex_branch:n {\__regex_group:nnnN {\__regex_branch:n
+    {\__regex_class:NnnnN \c_true_bool {\__regex_item_caseful_equal:n
+    {99}}{1}{0}\c_false_bool \__regex_class:NnnnN \c_true_bool
+    {\__regex_item_caseful_equal:n {100}}{1}{0}\c_false_bool
+    \__regex_class:NnnnN \c_true_bool {\__regex_item_caseful_equal:n
+    {101}\__regex_item_caseful_equal:n {102}}{0}{-1}\c_true_bool
+    \__regex_command_K: }}{1}{0}\c_false_bool }
+should be a regex variable, but it does not have the correct internal
+structure:
+    \__regex_branch:n {\__regex_class:NnnnN \c_true_bool
+    {\__regex_item_caseful_equal:n {98}}{1}{0}\c_false_bool }\__regex_branch:n
+    {\__regex_group:nnnN {\__regex_branch:n {\__regex_class:NnnnN \c_true_bool
+    {\__regex_item_caseful_equal:n {99}}{1}{0}\c_false_bool
+    \__regex_class:NnnnN \c_true_bool {\__regex_item_caseful_equal:n
+    {100}}{1}{0}\c_false_bool \__regex_class:NnnnN \c_true_bool
+    {\__regex_item_caseful_equal:n {101}\__regex_item_caseful_equal:n
+    {102}}{0}{-1}\c_true_bool \__regex_command_K: }}{1}{0}\c_false_bool }
+============================================================
diff --git a/l3kernel/testfiles/m3show003.lvt b/l3kernel/testfiles/m3show003.lvt
index f824df3f6..9da17d853 100644
--- a/l3kernel/testfiles/m3show003.lvt
+++ b/l3kernel/testfiles/m3show003.lvt
@@ -225,4 +225,35 @@
     }
   }
 
+
+\TEST { Bad~types }
+  {
+    \bool_show:N A
+    \bool_log:N \c_empty_tl
+    \tl_set:Nn \l_tmpa_tl { a , }
+    \clist_show:N \l_tmpa_tl
+    \str_show:N \l_tmpa_tl
+    \dim_show:N \l_tmpa_int % not caught
+    \int_log:N \l_tmpa_dim % not caught
+    \fp_show:N \l_tmpa_tl
+    \tl_set:Nx \l_tmpa_tl { \c_one_fp ; }
+    \fp_log:N \l_tmpa_tl
+    \prop_show:N \l_tmpa_tl
+    \seq_show:N \l_tmpa_tl
+    \seq_set_split:Nnn \l_tmpa_seq { } { ab }
+    \tl_set:Nx \l_tmpa_tl { \exp_not:V \l_tmpa_seq ? { c } }
+    \seq_show:N \l_tmpa_tl
+    \tl_show:N \use:n
+  }
+
+\TEST { Bad~types~for~regex }
+  {
+    \regex_show:N \use:n
+    \regex_show:N \l_tmpa_tl
+    \regex_set:Nn \l_tmpa_regex { a | b | (cd[ef]*?\K) }
+    \tl_set:Nx \l_tmpa_tl
+      { \exp_after:wN \use_none:n \l_tmpa_regex }
+    \regex_show:N \l_tmpa_tl
+  }
+
 \END
diff --git a/l3kernel/testfiles/m3show003.tlg b/l3kernel/testfiles/m3show003.tlg
index a1d997f53..0123e0e25 100644
--- a/l3kernel/testfiles/m3show003.tlg
+++ b/l3kernel/testfiles/m3show003.tlg
@@ -249,3 +249,140 @@ l. ...  }
 <recently read> }
 l. ...  }
 ============================================================
+============================================================
+TEST 17: Bad types
+============================================================
+! LaTeX3 Error: Variable 'A' is not a valid bool.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable 'A' with meaning
+    the letter A
+should be a bool variable, but it is not.
+! LaTeX3 Error: Variable '\c_empty_tl' is not a valid bool.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\c_empty_tl' with meaning
+    macro:->
+should be a bool variable, but it is not.
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid clist.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    a,
+should be a clist variable, but it includes empty or blank items without
+braces.
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid str.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    a,
+should be a str variable, but it does not have the correct category codes.
+> \l_tmpa_int=0.
+<recently read> }
+l. ...  }
+> \l_tmpa_dim=0.0pt.
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid fp.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    a,
+should be a fp variable, but it does not have the correct internal structure:
+    \s__fp \__fp_chk:w ???;
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid fp.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    \s__fp \__fp_chk:w 10{1}{1000}{0000}{0000}{0000};;
+should be a fp variable, but it does not have the correct internal structure:
+    \s__fp \__fp_chk:w 10{1}{1000}{0000}{0000}{0000};
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid prop.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    \s__fp \__fp_chk:w 10{1}{1000}{0000}{0000}{0000};;
+should be a prop variable, but it does not have the correct internal
+structure:
+    \s__prop 
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid seq.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    \s__fp \__fp_chk:w 10{1}{1000}{0000}{0000}{0000};;
+should be a seq variable, but it does not have the correct internal structure:
+    \s__seq \__seq_item:n {1}\__seq_item:n {1}\__seq_item:n
+    {0000}\__seq_item:n {0000}\__seq_item:n {;}
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid seq.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    \s__seq \__seq_item:n {a}\__seq_item:n {b}?{c}
+should be a seq variable, but it does not have the correct internal structure:
+    \s__seq \__seq_item:n {a}\__seq_item:n {b}\__seq_item:n {c}
+! LaTeX3 Error: Variable '\use:n' is not a valid tl.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\use:n' with meaning
+    \long macro:#1->#1
+should be a tl variable, but it is not a short macro.
+============================================================
+============================================================
+TEST 18: Bad types for regex
+============================================================
+! LaTeX3 Error: Variable '\use:n' is not a valid regex.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\use:n' with meaning
+    \long macro:#1->#1
+should be a regex variable, but it is not a short macro.
+> Compiled regex variable \l_tmpa_tl:.
+<recently read> }
+l. ...  }
+! LaTeX3 Error: Variable '\l_tmpa_tl' is not a valid regex.
+For immediate help type H <return>.
+ ...                                              
+l. ...  }
+This is a coding error.
+The variable '\l_tmpa_tl' with value
+    {\__regex_class:NnnnN \c_true_bool {\__regex_item_caseful_equal:n
+    {97}}{1}{0}\c_false_bool }\__regex_branch:n {\__regex_class:NnnnN
+    \c_true_bool {\__regex_item_caseful_equal:n {98}}{1}{0}\c_false_bool
+    }\__regex_branch:n {\__regex_group:nnnN {\__regex_branch:n
+    {\__regex_class:NnnnN \c_true_bool {\__regex_item_caseful_equal:n
+    {99}}{1}{0}\c_false_bool \__regex_class:NnnnN \c_true_bool
+    {\__regex_item_caseful_equal:n {100}}{1}{0}\c_false_bool
+    \__regex_class:NnnnN \c_true_bool {\__regex_item_caseful_equal:n
+    {101}\__regex_item_caseful_equal:n {102}}{0}{-1}\c_true_bool
+    \__regex_command_K: }}{1}{0}\c_false_bool }
+should be a regex variable, but it does not have the correct internal
+structure:
+    \__regex_branch:n {\__regex_class:NnnnN \c_true_bool
+    {\__regex_item_caseful_equal:n {98}}{1}{0}\c_false_bool }\__regex_branch:n
+    {\__regex_group:nnnN {\__regex_branch:n {\__regex_class:NnnnN \c_true_bool
+    {\__regex_item_caseful_equal:n {99}}{1}{0}\c_false_bool
+    \__regex_class:NnnnN \c_true_bool {\__regex_item_caseful_equal:n
+    {100}}{1}{0}\c_false_bool \__regex_class:NnnnN \c_true_bool
+    {\__regex_item_caseful_equal:n {101}\__regex_item_caseful_equal:n
+    {102}}{0}{-1}\c_true_bool \__regex_command_K: }}{1}{0}\c_false_bool }
+============================================================





More information about the latex3-commits mailing list.