[latex3-commits] [git/LaTeX3-latex3-latex3] l3text: Make \text_expand:n and \text_<type>case:n f-type expandable (81618b557)

Joseph Wright joseph.wright at morningstar2.co.uk
Sat Dec 7 10:12:40 CET 2019


Repository : https://github.com/latex3/latex3
On branch  : l3text
Link       : https://github.com/latex3/latex3/commit/81618b557c1a86bbaad3ed3676870b81de3fe6fc

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

commit 81618b557c1a86bbaad3ed3676870b81de3fe6fc
Author: Joseph Wright <joseph.wright at morningstar2.co.uk>
Date:   Sat Dec 7 09:12:40 2019 +0000

    Make \text_expand:n and \text_<type>case:n f-type expandable
    
    User text can contain document commands which themselves take arguments.
    With the e-type approach, when used in running text (no e/x-type
    expanson), we end up with
    
        \exp_not:n { \foo } \exp_not:n { { arg } }
    
    or similar The first \exp_not:n is a no-op here, so \foo (assuming 1
    agument) grabs \exp_not:n rather than the _result_ of applying it to
    "{arg}". Depending on the exact details for implementation of \foo, all
    sorts of crap then happens.
    
    With f-type expansion, we get
    
        \exp_not:n { \foo { arg } }
    
    which is fine: the same no-op inserts exactly the tokens we want.


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

81618b557c1a86bbaad3ed3676870b81de3fe6fc
 l3kernel/l3text.dtx | 458 +++++++++++++++++++++++++++++++++-------------------
 1 file changed, 292 insertions(+), 166 deletions(-)

diff --git a/l3kernel/l3text.dtx b/l3kernel/l3text.dtx
index 2530198ad..dcc654d5d 100644
--- a/l3kernel/l3text.dtx
+++ b/l3kernel/l3text.dtx
@@ -57,7 +57,7 @@
 % here concern conversion of textual content for example in case changing,
 % generation of bookmarks and extraction to tags.
 %
-% \begin{function}[rEXP, added = 2019-11-20]{\text_expand:n}
+% \begin{function}[EXP, added = 2019-11-20]{\text_expand:n}
 %   \begin{syntax}
 %     \cs{text_expand:n} \Arg{text}
 %   \end{syntax}
@@ -74,7 +74,7 @@
 %   and \cs{l_text_letterlike_tl} are excluded from expansion.
 % \end{function}
 %
-% \begin{function}[rEXP, added = 2019-11-20]
+% \begin{function}[EXP, added = 2019-11-20]
 %   {
 %     \text_lowercase:n,  \text_uppercase:n,  \text_titlecase:n,
 %       \text_titlecase_first:n,
@@ -421,7 +421,6 @@
 %    \end{macrocode}
 % \end{macro}
 %
-%
 % \subsection{Configuration variables}
 %
 % \begin{variable}{\l_text_accents_tl, \l_text_letterlike_tl}
@@ -500,48 +499,79 @@
 % \end{variable}
 % \end{variable}
 %
-% \begin{macro}[rEXP]{\text_expand:n}
-% \begin{macro}[rEXP]{\@@_expand_loop:w}
-% \begin{macro}[rEXP]{\@@_expand_group:n}
-% \begin{macro}[rEXP]{\@@_expand_space:w}
-% \begin{macro}[rEXP]
+% \begin{macro}[EXP]{\text_expand:n, \@@_expand:n}
+% \begin{macro}[EXP]{\@@_expand_result:n}
+% \begin{macro}[EXP]{\@@_expand_store:n, \@@_expand_store:o}
+% \begin{macro}[EXP]{\@@_expand_store:nw}
+% \begin{macro}[EXP]{\@@_expand_end:w}
+% \begin{macro}[EXP]{\@@_expand_loop:w}
+% \begin{macro}[EXP]{\@@_expand_group:n}
+% \begin{macro}[EXP]{\@@_expand_space:w}
+% \begin{macro}[EXP]
 %   {
 %    \@@_expand_N_type:N       ,
 %    \@@_expand_N_type_auxi:N  ,
 %    \@@_expand_N_type_auxii:N ,
 %    \@@_expand_N_type_auxiii:N
 %   }
-% \begin{macro}[rEXP]{\@@_expand_math_search:NNN}
-% \begin{macro}[rEXP]{\@@_expand_math_loop:Nw}
-% \begin{macro}[rEXP]{\@@_expand_math_N_type:NN}
-% \begin{macro}[rEXP]{\@@_expand_math_group:Nn}
-% \begin{macro}[rEXP]{\@@_expand_math_space:Nw}
-% \begin{macro}[rEXP]{\@@_expand_exclude:N}
-% \begin{macro}[rEXP]{\@@_expand_exclude:nN}
-% \begin{macro}[rEXP]{\@@_expand_exclude:NN}
-% \begin{macro}[rEXP]{\@@_expand_exclude:Nn}
-% \begin{macro}[rEXP]{\@@_expand_letterlike:N}
-% \begin{macro}[rEXP]{\@@_expand_letterlike:NN}
-% \begin{macro}[rEXP]
+% \begin{macro}[EXP]{\@@_expand_math_search:NNN}
+% \begin{macro}[EXP]{\@@_expand_math_loop:Nw}
+% \begin{macro}[EXP]{\@@_expand_math_N_type:NN}
+% \begin{macro}[EXP]{\@@_expand_math_group:Nn}
+% \begin{macro}[EXP]{\@@_expand_math_space:Nw}
+% \begin{macro}[EXP]{\@@_expand_exclude:N}
+% \begin{macro}[EXP]{\@@_expand_exclude:nN}
+% \begin{macro}[EXP]{\@@_expand_exclude:NN}
+% \begin{macro}[EXP]{\@@_expand_exclude:Nn}
+% \begin{macro}[EXP]{\@@_expand_letterlike:N}
+% \begin{macro}[EXP]{\@@_expand_letterlike:NN}
+% \begin{macro}[EXP]
 %   {
 %     \@@_expand_implicit:N ,
 %     \@@_expand_explicit:N ,
 %     \@@_expand_cs:N       ,
 %     \@@_expand_protect:N
 %   }
-% \begin{macro}[rEXP]{\@@_expand_protect:nN}
-% \begin{macro}[rEXP]{\@@_expand_protect:Nw}
-% \begin{macro}[rEXP]{\@@_expand_cs_expand:N}
+% \begin{macro}[EXP]{\@@_expand_protect:nN}
+% \begin{macro}[EXP]{\@@_expand_protect:Nw}
+% \begin{macro}[EXP]{\@@_expand_cs_expand:N}
 %   After precautions against |&| tokens, start a simple loop: that of
 %   course means that \enquote{text} cannot contain the two recursion
-%   quarks.
+%   quarks. The loop here must be \texttt{f}-type expandable; we have
+%   arbitrary user commands which might be protected \emph{and} take
+%   arguments, and if the expansion code is used in a typesetting
+%   context, that will otherwise explode. (The same issue applies more
+%   clearly to case changing: see the example there.)
 %    \begin{macrocode}
 \cs_new:Npn \text_expand:n #1
   {
+    \__kernel_exp_not:w \exp_after:wN
+      {
+        \exp:w
+        \@@_expand:n {#1}
+      }
+  }
+\cs_new:Npn \@@_expand:n #1
+  {
     \group_align_safe_begin:
     \@@_expand_loop:w #1
       \q_recursion_tail \q_recursion_stop
+    \@@_expand_result:n { }
+  }
+%    \end{macrocode}
+%   The approach to making the code \texttt{f}-type expandable is to usee
+%   a marker result token and to shuffle the collected tokens
+%    \begin{macrocode}
+\cs_new:Npn \@@_expand_store:n #1
+  { \@@_expand_store:nw {#1} }
+\cs_generate_variant:Nn \@@_expand_store:n { o }
+\cs_new:Npn \@@_expand_store:nw #1#2 \@@_expand_result:n #3
+  { #2 \@@_expand_result:n { #3 #1 } }
+\cs_new:Npn \@@_expand_end:w #1 \@@_expand_result:n #2
+  {
     \group_align_safe_end:
+    \exp_end:
+    #2
   }
 %    \end{macrocode}
 %   The main loop is a standard \enquote{tl action}; groups are handled
@@ -561,15 +591,19 @@
   }
 \cs_new:Npn \@@_expand_group:n #1
   {
-    {
-      \@@_expand_loop:w #1
-        \q_recursion_tail \q_recursion_stop
-    }
+    \@@_expand_store:o
+      {
+        \exp_after:wN
+          {
+            \exp:w
+            \@@_expand:n {#1}
+          }
+      }
     \@@_expand_loop:w
   }
 \exp_last_unbraced:NNo \cs_new:Npn \@@_expand_space:w \c_space_tl
   {
-    \c_space_tl
+    \@@_expand_store:n { ~ }
     \@@_expand_loop:w
   }
 %    \end{macrocode}
@@ -581,7 +615,8 @@
 %    \begin{macrocode}
 \cs_new:Npx \@@_expand_N_type:N #1
   {
-    \exp_not:N \quark_if_recursion_tail_stop:N #1
+    \exp_not:N \quark_if_recursion_tail_stop_do:Nn #1
+      { \exp_not:N \@@_expand_end:w }
     \exp_not:N \bool_lazy_any:nTF
       {
         { \exp_not:N \token_if_eq_meaning_p:NN #1 \c_space_token }
@@ -594,7 +629,7 @@
             \c_@@_mathchardef_space_token
         }
       }
-      { \exp_not:N  \@@_expand_space:w \c_space_tl }
+      { \exp_not:N \@@_expand_space:w \c_space_tl }
       { \exp_not:N \@@_expand_N_type_auxi:N #1 }
   }
 %    \end{macrocode}
@@ -610,7 +645,7 @@
       { \token_if_eq_meaning_p:NN #1 \c_@@_chardef_group_begin_token }
       { \token_if_eq_meaning_p:NN #1 \c_@@_mathchardef_group_begin_token }
       {
-        \c_left_brace_str
+        \@@_expand_store:o \c_left_brace_str
         \@@_expand_loop:w
       }
       {
@@ -618,7 +653,7 @@
           { \token_if_eq_meaning_p:NN #1 \c_@@_chardef_group_end_token }
           { \token_if_eq_meaning_p:NN #1 \c_@@_mathchardef_group_end_token }
           {
-            \c_right_brace_str
+            \@@_expand_store:o \c_right_brace_str
             \@@_expand_loop:w
           }
           { \@@_expand_N_type_auxii:N #1 }
@@ -665,7 +700,7 @@
       {
         \use_i_delimit_by_q_recursion_stop:nw
            {
-             \exp_not:n {#1}
+             \@@_expand_store:n {#1}
              \@@_expand_math_loop:Nw #3
            }
       }
@@ -684,21 +719,22 @@
   }
 \cs_new:Npn \@@_expand_math_N_type:NN #1#2
   {
-    \quark_if_recursion_tail_stop:N #2
-    \exp_not:n {#2}
+    \quark_if_recursion_tail_stop_do:Nn #2
+      { \@@_expand_end:w }
+    \@@_expand_store:n {#2}
     \token_if_eq_meaning:NNTF #2 #1
       { \@@_expand_loop:w }
       { \@@_expand_math_loop:Nw #1 }
   }
 \cs_new:Npn \@@_expand_math_group:Nn #1#2
   {
-    { \exp_not:n {#2} }
+    \@@_expand_store:n { {#2} }
     \@@_expand_math_loop:Nw #1
   }
 \exp_after:wN \cs_new:Npn \exp_after:wN \@@_expand_math_space:Nw 
   \exp_after:wN # \exp_after:wN 1 \c_space_tl
   {
-    \c_space_tl
+    \@@_expand_store:n { ~ }
     \@@_expand_math_loop:Nw #1
   }
 %    \end{macrocode}
@@ -735,7 +771,7 @@
   }
 \cs_new:Npn \@@_expand_exclude:Nn #1#2
   {
-    \exp_not:n { #1 {#2} }
+    \@@_expand_store:n { #1 {#2} }
     \@@_expand_loop:w
   }
 %    \end{macrocode}
@@ -756,7 +792,7 @@
       {
         \use_i_delimit_by_q_recursion_stop:nw
           {
-            \exp_not:n {#1}
+            \@@_expand_store:n {#1}
             \@@_expand_loop:w
           }
       }
@@ -781,7 +817,7 @@
     \token_if_cs:NTF #1
       { \@@_expand_cs:N #1 }
       {
-        \exp_not:n {#1}
+        \@@_expand_store:n {#1}
         \@@_expand_loop:w 
       }
   }
@@ -809,10 +845,10 @@
     \quark_if_nil:nTF {#4}
       {
         \cs_if_exist:cTF {#2}
-          { \exp_not:c {#2} }
-          { \exp_not:n { \protect #1 } }
+          { \exp_args:Ne \@@_expand_store:n { \exp_not:c {#2} } }
+          { \@@_expand_store:n { \protect #1 } }
       }
-      { \exp_not:n { \protect #1 } }
+      { \@@_expand_store:n { \protect #1 } }
     \@@_expand_loop:w
   }
 %    \end{macrocode}
@@ -824,7 +860,7 @@
     \@@_if_expandable:NTF #1
       { \exp_after:wN \@@_expand_loop:w #1 }
       {
-        \exp_not:n {#1}
+        \@@_expand_store:n {#1}
         \@@_expand_loop:w
       }
   }
@@ -849,17 +885,21 @@
 % \end{macro}
 % \end{macro}
 % \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
 %
 % \subsection{Case changing}
 %
-% \begin{macro}[rEXP]
+% \begin{macro}[EXP]
 %   {
 %     \text_lowercase:n,
 %     \text_uppercase:n,
 %     \text_titlecase:n,
 %     \text_titlecase_first:n
 %   }
-% \begin{macro}[rEXP]
+% \begin{macro}[EXP]
 %   {
 %     \text_lowercase:nn,
 %     \text_uppercase:nn,
@@ -869,90 +909,134 @@
 %   The user level functions here are all wrappers around the internal
 %   functions for case changing.
 %    \begin{macrocode}
-\cs_new:Npn \text_lowercase:n { \@@_change_case:nnn { lower } { } }
-\cs_new:Npn \text_uppercase:n { \@@_change_case:nnn { upper } { } }
-\cs_new:Npn \text_titlecase:n { \@@_change_case:nnn { title } { } }
-\cs_new:Npn \text_titlecase_first:n { \@@_change_case:nnn { titleonly } { } }
-\cs_new:Npn \text_lowercase:nn { \@@_change_case:nnn { lower } }
-\cs_new:Npn \text_uppercase:nn { \@@_change_case:nnn { upper } }
-\cs_new:Npn \text_titlecase:nn { \@@_change_case:nnn { title } }
-\cs_new:Npn \text_titlecase_first:nn { \@@_change_case:nnn { titleonly } }
-%    \end{macrocode}
-% \end{macro}
-% \end{macro}
-%
-% \begin{macro}[rEXP]{\@@_change_case:nnn, \@@_change_case_aux:nnn}
-% \begin{macro}[rEXP]{\@@_change_case_loop:nnw}
-% \begin{macro}[rEXP]{\@@_change_case_end:w}
-% \begin{macro}[rEXP]
+\cs_new:Npn \text_lowercase:n #1
+  { \@@_change_case:nnn { lower } { } {#1} }
+\cs_new:Npn \text_uppercase:n #1
+  { \@@_change_case:nnn { upper } { } {#1} }
+\cs_new:Npn \text_titlecase:n #1
+  { \@@_change_case:nnn { title } { } {#1} }
+\cs_new:Npn \text_titlecase_first:n #1
+  { \@@_change_case:nnn { titleonly } { } {#1} }
+\cs_new:Npn \text_lowercase:nn #1#2
+  { \@@_change_case:nnn { lower } {#1} {#2} }
+\cs_new:Npn \text_uppercase:nn #1#2
+  { \@@_change_case:nnn { upper } {#1} {#2} }
+\cs_new:Npn \text_titlecase:nn #1#2
+  { \@@_change_case:nnn { title } {#1} {#2} }
+\cs_new:Npn \text_titlecase_first:nn #1#2
+  { \@@_change_case:nnn { titleonly } {#1} {#2} }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_change_case:nnn, \@@_change_case_aux:nnn}
+% \begin{macro}[EXP]{\@@_change_case_loop:nnw}
+% \begin{macro}[EXP]{\@@_change_case_break:w}
+% \begin{macro}[EXP]
 %   {
 %     \@@_change_case_group_lower:nnn     ,
 %     \@@_change_case_group_upper:nnn     ,
 %     \@@_change_case_group_title:nnn     ,
 %     \@@_change_case_group_titleonly:nnn
 %   }
-% \begin{macro}[rEXP]{\@@_change_case_space:nnw}
-% \begin{macro}[rEXP]{\@@_change_case_N_type:nnN, \@@_change_case_N_type_aux:nnN}
-% \begin{macro}[rEXP]{\@@_change_case_N_type:nnnN}
-% \begin{macro}[rEXP]{\@@_change_case_math_search:nnNNN}
-% \begin{macro}[rEXP]{\@@_change_case_math_loop:nnNw}
-% \begin{macro}[rEXP]{\@@_change_case_math_N_type:nnNN}
-% \begin{macro}[rEXP]{\@@_change_case_math_group:nnNn}
-% \begin{macro}[rEXP]{\@@_change_case_math_space:nnNw}
-% \begin{macro}[rEXP]{\@@_change_case_cs_check:nnN}
-% \begin{macro}[rEXP]{\@@_change_case_exclude:nnN}
-% \begin{macro}[rEXP]{\@@_change_case_exclude:nnnN}
-% \begin{macro}[rEXP]{\@@_change_case_exclude:nnNN}
-% \begin{macro}[rEXP]{\@@_change_case_exclude:nnNn}
-% \begin{macro}[rEXP]
+% \begin{macro}[EXP]{\@@_change_case_space:nnw}
+% \begin{macro}[EXP]{\@@_change_case_N_type:nnN, \@@_change_case_N_type_aux:nnN}
+% \begin{macro}[EXP]{\@@_change_case_N_type:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_math_search:nnNNN}
+% \begin{macro}[EXP]{\@@_change_case_math_loop:nnNw}
+% \begin{macro}[EXP]{\@@_change_case_math_N_type:nnNN}
+% \begin{macro}[EXP]{\@@_change_case_math_group:nnNn}
+% \begin{macro}[EXP]{\@@_change_case_math_space:nnNw}
+% \begin{macro}[EXP]{\@@_change_case_cs_check:nnN}
+% \begin{macro}[EXP]{\@@_change_case_exclude:nnN}
+% \begin{macro}[EXP]{\@@_change_case_exclude:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_exclude:nnNN}
+% \begin{macro}[EXP]{\@@_change_case_exclude:nnNn}
+% \begin{macro}[EXP]
 %   {
 %     \@@_change_case_letterlike_lower:nnN ,
 %     \@@_change_case_letterlike_upper:nnN ,
 %     \@@_change_case_letterlike_title:nnN ,
 %     \@@_change_case_letterlike_titleonly:nnN
 %   }
-% \begin{macro}[rEXP]{\@@_change_case_letterlike:nnnnN}
-% \begin{macro}[rEXP]
+% \begin{macro}[EXP]{\@@_change_case_letterlike:nnnnN}
+% \begin{macro}[EXP]
 %   {\@@_change_case_char_lower:nnN, \@@_change_case_char_upper:nnN}
-% \begin{macro}[rEXP]{\@@_change_case_lower_sigma:nnnN}
-% \begin{macro}[rEXP]{\@@_change_case_lower_sigma:nnNw}
-% \begin{macro}[rEXP]{\@@_change_case_lower_sigma:NnnN}
-% \begin{macro}[rEXP]
+% \begin{macro}[EXP]{\@@_change_case_lower_sigma:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_lower_sigma:nnNw}
+% \begin{macro}[EXP]{\@@_change_case_lower_sigma:NnnN}
+% \begin{macro}[EXP]
 %   {\@@_change_case_char_title:nnN, \@@_change_case_char_titleonly:nnN}
-% \begin{macro}[rEXP]
+% \begin{macro}[EXP]
 %   {\@@_change_case_char_title:nN, \@@_change_case_char_titleonly:nN}
-% \begin{macro}[rEXP]{\@@_change_case_char_title:nnnN}
-% \begin{macro}[rEXP]{\@@_change_case_char_char:nnnN}
-% \begin{macro}[rEXP]{\@@_change_case_char_UTFviii:nnnNN}
-% \begin{macro}[rEXP]{\@@_change_case_char_UTFviii:nnnNNN}
-% \begin{macro}[rEXP]{\@@_change_case_char_UTFviii:nnnNNNNN}
-% \begin{macro}[rEXP]{\@@_change_case_char_UTFviii:nnnn}
-% \begin{macro}[rEXP]
+% \begin{macro}[EXP]{\@@_change_case_char_title:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_char_char:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnNN}
+% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnNNN}
+% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnNNNNN}
+% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnn}
+% \begin{macro}[EXP]
 %   {
 %   \@@_change_case_char_next_lower:nn     ,
 %   \@@_change_case_char_next_upper:nn     ,
 %   \@@_change_case_char_next_title:nn     ,
 %   \@@_change_case_char_next_titleonly:nn
 %   }
-% \begin{macro}[rEXP]{\@@_change_case_char_next_end:nn}
+% \begin{macro}[EXP]{\@@_change_case_char_next_end:nn}
 %   As for the expansion code, the business end of case changing is the
 %   handling of \texttt{N}-type tokens. First, we expand the input fully
 %   (so the loops here don't need to worry about awkward look-aheads and the
 %   like). Then we split into the different paths.
+%
+%   The code here needs to be \texttt{f}-type expandable to deal with the
+%   situation where case changing is applied in running text. There, we
+%   might have case changing as a document command and the text containing
+%   other non-expandable document commands.
+%   \begin{verbatim}
+%     \cs_set_eq:NN \MakeLowercase \text_lowercase
+%     ...
+%     \MakeLowercase{\enquote*{A} text}
+%   \end{verbatim}
+%   If we use an \texttt{e}-type expansion and wrap each token in
+%   \cs{exp_not:n}, that would explode: the document command grabs
+%   \cs{exp_not:n} as an argument, and things go badly wrong. So we have to
+%   wrap the entire result in exactly one \cs{exp_not:n}, or rather in the
+%   kernel version.
 %    \begin{macrocode}
 \cs_new:Npn \@@_change_case:nnn #1#2#3
   {
-    \group_align_safe_begin:
-      \exp_args:Ne \@@_change_case_aux:nnn
-        { \text_expand:n {#3} }
-        {#1} {#2}
-    \group_align_safe_end:
+     \__kernel_exp_not:w \exp_after:wN
+      {
+        \exp:w
+        \exp_args:Ne \@@_change_case_aux:nnn
+          { \text_expand:n {#3} }
+          {#1} {#2}
+      }
   }
 \cs_new:Npn \@@_change_case_aux:nnn #1#2#3
   {
+    \group_align_safe_begin:
     \@@_change_case_loop:nnw {#2} {#3} #1
       \q_recursion_tail \q_recursion_stop
+    \@@_change_case_result:n { }
+  }
+%    \end{macrocode}
+%   As for expansion, collect up the tokens for future use.
+%    \begin{macrocode}
+\cs_new:Npn \@@_change_case_store:n #1
+  { \@@_change_case_store:nw {#1} }
+\cs_generate_variant:Nn \@@_change_case_store:n { o , e , V , v }
+\cs_new:Npn \@@_change_case_store:nw #1#2 \@@_change_case_result:n #3
+  { #2 \@@_change_case_result:n { #3 #1 } }
+\cs_new:Npn \@@_change_case_end:w #1 \@@_change_case_result:n #2
+  {
+    \group_align_safe_end:
+    \exp_end:
+    #2
   }
+%    \end{macrocode}
+%   The main loop is the standard \texttt{tl action} type.
+%    \begin{macrocode}
 \cs_new:Npn \@@_change_case_loop:nnw #1#2#3 \q_recursion_stop
   {
     \tl_if_head_is_N_type:nTF {#3}
@@ -964,7 +1048,7 @@
       }
     {#1} {#2} #3 \q_recursion_stop
   }
-\cs_new:Npn \@@_change_case_end:w #1 \q_recursion_tail \q_recursion_stop
+\cs_new:Npn \@@_change_case_break:w #1 \q_recursion_tail \q_recursion_stop
   { \exp_not:n {#1} }
 %    \end{macrocode}
 %   For a group, we \emph{could} worry about whether this contains a character
@@ -977,36 +1061,48 @@
 %    \begin{macrocode}
 \cs_new:Npn \@@_change_case_group_lower:nnn #1#2#3
   {
-    {
-      \@@_change_case_loop:nnw {#1} {#2} #3
-        \q_recursion_tail \q_recursion_stop
-    }
+    \@@_change_case_store:o
+      {
+        \exp_after:wN
+          {
+            \exp:w
+            \@@_change_case_aux:nnn {#3} {#1} {#2}
+          }
+      }
     \@@_change_case_loop:nnw {#1} {#2}
   }
 \cs_new_eq:NN \@@_change_case_group_upper:nnn
   \@@_change_case_group_lower:nnn
 \cs_new:Npn \@@_change_case_group_title:nnn #1#2#3
   {
-    {
-      \@@_change_case_loop:nnw {#1} {#2} #3
-        \q_recursion_tail \q_recursion_stop
-    }
+    \@@_change_case_store:o
+      {
+        \exp_after:wN
+          {
+            \exp:w
+            \@@_change_case_aux:nnn {#3} {#1} {#2}
+          }
+      }
     \@@_change_case_loop:nnw { lower } {#2}
   }
 \cs_new:Npn \@@_change_case_group_titleonly:nnn #1#2#3
   {
-    {
-      \@@_change_case_loop:nnw {#1} {#2} #3
-        \q_recursion_tail \q_recursion_stop
-    }
-    \@@_change_case_end:w
+    \@@_change_case_store:o
+      {
+        \exp_after:wN
+          {
+            \exp:w
+            \@@_change_case_aux:nnn {#3} {#1} {#2}
+          }
+      }
+    \@@_change_case_break:w
   }
 \use:x
   {
     \cs_new:Npn \exp_not:N \@@_change_case_space:nnw ##1##2 \c_space_tl
   }
   {
-    \c_space_tl
+    \@@_change_case_store:n { ~ }
     \@@_change_case_loop:nnw {#1} {#2}
   }
 %    \end{macrocode}
@@ -1020,7 +1116,8 @@
 %    \begin{macrocode}
 \cs_new:Npn \@@_change_case_N_type:nnN #1#2#3
   {
-    \quark_if_recursion_tail_stop:N #3
+    \quark_if_recursion_tail_stop_do:Nn #3
+      { \@@_change_case_end:w }
     \@@_change_case_N_type_aux:nnN {#1} {#2} #3
   }
 \cs_new:Npn \@@_change_case_N_type_aux:nnN #1#2#3
@@ -1042,7 +1139,7 @@
       {
         \use_i_delimit_by_q_recursion_stop:nw
            {
-             \exp_not:n {#3}
+             \@@_change_case_store:n {#3}
              \@@_change_case_math_loop:nnNw {#1} {#2} #5
            }
       }
@@ -1061,15 +1158,16 @@
   }
 \cs_new:Npn \@@_change_case_math_N_type:nnNN #1#2#3#4
   {
-    \quark_if_recursion_tail_stop:N #4
-    \exp_not:n {#4}
+    \quark_if_recursion_tail_stop_do:Nn #4
+      { \@@_change_case_end:w }
+    \@@_change_case_store:n {#4}
     \token_if_eq_meaning:NNTF #4 #3
       { \@@_change_case_loop:nnw {#1} {#2} }
       { \@@_change_case_math_loop:nnNw {#1} {#2} #3 }
   }
 \cs_new:Npn \@@_change_case_math_group:nnNn #1#2#3#4
   {
-    { \exp_not:n {#4} }
+    \@@_change_case_store:n { {#4} }
     \@@_change_case_math_loop:nnNw {#1} {#2} #3
   }
 \use:x
@@ -1078,7 +1176,7 @@
       \c_space_tl
   }
   {
-    \c_space_tl
+    \@@_change_case_store:n { ~ }
     \@@_change_case_math_loop:nnNw {#1} {#2} #3
   }
 %    \end{macrocode}
@@ -1127,7 +1225,7 @@
   }
 \cs_new:Npn \@@_change_case_exclude:nnNn #1#2#3#4
   {
-    \exp_not:n { #3 {#4} }
+    \@@_change_case_store:n { #3 {#4} }
     \@@_change_case_loop:nnw {#1} {#2}
   }
 %    \end{macrocode}
@@ -1149,12 +1247,12 @@
   {
     \cs_if_exist:cTF { c_@@_ #1 case_ \token_to_str:N #5 _tl }
       {
-        \exp_not:v
+        \@@_change_case_store:v
           { c_@@_ #1 case_ \token_to_str:N #5 _tl }
          \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#4}
       }
       {
-        \exp_not:n {#5}
+        \@@_change_case_store:n {#5}
         \cs_if_exist:cTF
           {
             c_@@_
@@ -1210,16 +1308,20 @@
         \tl_if_head_is_N_type:nTF {#4}
           { \@@_change_case_lower_sigma:NnnN #3 }
           {
-            \char_generate:nn { "03C2 } { \@@_char_catcode:N #3 }
+            \@@_change_case_store:e
+              { \char_generate:nn { "03C2 } { \@@_char_catcode:N #3 } }
             \@@_change_case_loop:nnw
           }
             {#1} {#2} #4 \q_recursion_stop
       }
     \cs_new:Npn \@@_change_case_lower_sigma:NnnN #1#2#3#4
       {
-        \token_if_letter:NTF #4
-          { \char_generate:nn { "03C3 } { \@@_char_catcode:N #1 } }
-          { \char_generate:nn { "03C2 } { \@@_char_catcode:N #1 } }
+        \@@_change_case_store:e
+          {
+            \token_if_letter:NTF #4
+              { \char_generate:nn { "03C3 } { \@@_char_catcode:N #1 } }
+              { \char_generate:nn { "03C2 } { \@@_char_catcode:N #1 } }
+          }
         \@@_change_case_loop:nnw {#2} {#3} #4
       }
   }
@@ -1276,7 +1378,8 @@
   {
     \cs_new:Npn \@@_change_case_char:nnnN #1#2#3#4
       {
-        \use:c { char_ #1 case :N } #4
+        \@@_change_case_store:e
+          { \use:c { char_ #1 case :N } #4 }
         \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3}
       }
   }
@@ -1295,7 +1398,7 @@
                 {#1} {#2} {#3} #4
           }
           {
-            \use:c { char_ #1 case :N } #4
+            \@@_change_case_store:e{ \use:c { char_ #1 case :N } #4 }
             \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3}
           }
        }
@@ -1309,10 +1412,10 @@
       {
         \cs_if_exist:cTF { c_@@_ #1 case_ \tl_to_str:n {#4} _tl }
           {
-            \exp_not:v
+            \@@_change_case_store:v
               { c_@@_ #1 case_ \tl_to_str:n {#4} _tl }
           }
-          { \exp_not:n {#4} }
+          { \@@_change_case_store:n {#4} }
         \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3}
       }
   }
@@ -1325,7 +1428,7 @@
 \cs_new_eq:NN \@@_change_case_char_next_titleonly:nn
   \@@_change_case_char_next_lower:nn
 \cs_new:Npn \@@_change_case_char_next_end:nn #1#2
-  { \@@_change_case_end:w }
+  { \@@_change_case_break:w }
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
@@ -1361,7 +1464,7 @@
 % \end{macro}
 % \end{macro}
 %
-% \begin{macro}{\@@_change_case_upper_de-alt:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_upper_de-alt:nnnN}
 %   A simple alternative version for German.
 %    \begin{macrocode}
 \bool_lazy_or:nnT
@@ -1372,7 +1475,8 @@
       {
         \int_compare:nNnTF { `#4 } = { "00DF }
           {
-            \char_generate:nn { "1E9E } { \@@_char_catcode:N #4 }
+            \@@_change_case_store:e
+              { \char_generate:nn { "1E9E } { \@@_char_catcode:N #4 } }
             \use:c { @@_change_case_char_next_ #2 :nn }
               {#2} {#3}
           }
@@ -1382,11 +1486,11 @@
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}[rEXP]{\@@_change_case_upper_el:nnnN}
-% \begin{macro}[rEXP]{\@@_change_case_upper_el:nnnn}
-% \begin{macro}[rEXP]{\@@_change_case_upper_el_aux:nnnN}
-% \begin{macro}[rEXP]{\@@_change_case_upper_el_loop:nnw}
-% \begin{macro}[rEXP]{\@@_change_case_upper_el:nnN}
+% \begin{macro}[EXP]{\@@_change_case_upper_el:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_upper_el:nnnn}
+% \begin{macro}[EXP]{\@@_change_case_upper_el_aux:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_upper_el_loop:nnw}
+% \begin{macro}[EXP]{\@@_change_case_upper_el:nnN}
 % \begin{macro}[EXP]{\@@_change_case_if_greek:nTF}
 %   For Greek uppercasing, we need to know if characters \emph{in the Greek
 %   range} have accents. That means doing a \textsc{nfd} conversion first, then
@@ -1434,7 +1538,7 @@
           {
             \int_compare:nNnTF { `#3 } = { "0308 }
               {
-                \exp_not:n {#3}
+                \@@_change_case_store:n {#3}
                 \@@_change_case_upper_el_loop:nnw {#1} {#2}
               }
               {
@@ -1456,8 +1560,11 @@
                   {
                     \int_compare:nNnTF { `#3 } = { "0344 }
                       {
-                        \char_generate:nn { "0308 }
-                          { \@@_char_catcode:N #3 }
+                        \@@_change_case_store:e
+                          {
+                            \char_generate:nn { "0308 }
+                              { \@@_char_catcode:N #3 }
+                          }
                         \@@_change_case_upper_el_loop:nnw {#1} {#2}
                       }
                       {
@@ -1497,7 +1604,7 @@
 % \end{macro}
 % \end{macro}
 % \end{macro}
-% \begin{macro}[rEXP]{\@@_change_case_title_el:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_title_el:nnnN}
 %   Titlecasing retains accents, but to prevent the uppercasing code
 %   from kicking in, there has to be an explicit function here.
 %    \begin{macrocode}
@@ -1511,7 +1618,7 @@
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}[rEXP]
+% \begin{macro}[EXP]
 %   {
 %     \@@_change_cases_lower_lt:nnnN      ,
 %     \@@_change_cases_lower_lt_auxi:nnnN ,
@@ -1562,9 +1669,12 @@
               {#2} {#3} #4
           }
           {
-            \char_generate:nn { "0069 } { \@@_char_catcode:N #4 }
-            \char_generate:nn { "0307 } { \@@_char_catcode:N #4 }
-            \char_generate:nn {#1} { \@@_char_catcode:N #4 }
+            \@@_change_case_store:e
+              {
+                \char_generate:nn { "0069 } { \@@_char_catcode:N #4 }
+                \char_generate:nn { "0307 } { \@@_char_catcode:N #4 }
+                \char_generate:nn {#1} { \@@_char_catcode:N #4 }
+              }
             \@@_change_case_loop:nnw {#2} {#3}
           }
       }
@@ -1601,7 +1711,10 @@
                 { \int_compare_p:nNn { `#3 } = { "0303 } }
               }
           }
-          { \char_generate:nn { "0307 } { \@@_char_catcode:N #3 } }
+          {
+            \@@_change_case_store:e
+              { \char_generate:nn { "0307 } { \@@_char_catcode:N #3 } }
+          }
         \@@_change_case_loop:nnw {#1} {#2} #3
       }
   }
@@ -1609,7 +1722,7 @@
 % \end{macro}
 % \end{macro}
 % \end{macro}
-% \begin{macro}[rEXP]
+% \begin{macro}[EXP]
 %   {
 %     \@@_change_cases_upper_lt:nnnN     ,
 %     \@@_change_cases_upper_lt_aux:nnnN
@@ -1641,7 +1754,8 @@
        \tl_if_blank:nTF {#1}
          { \@@_change_case_char:nnnN { upper } {#2} {#3} #4 }
          {
-           \char_generate:nn {#1} { \@@_char_catcode:N #4 }
+           \@@_change_case_store:e
+             { \char_generate:nn {#1} { \@@_char_catcode:N #4 } }
            \@@_change_case_upper_lt:nnw {#2} {#3}
          }
      }
@@ -1666,9 +1780,9 @@
 % \end{macro}
 % \end{macro}
 %
-% \begin{macro}[rEXP]{\@@_change_case_title_nl:nnnN}
-% \begin{macro}[rEXP]{\@@_change_case_title_nl:nnw}
-% \begin{macro}[rEXP]{\@@_change_case_title_nl:nnN}
+% \begin{macro}[EXP]{\@@_change_case_title_nl:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_title_nl:nnw}
+% \begin{macro}[EXP]{\@@_change_case_title_nl:nnN}
 %   For Dutch, there is a single look-ahead test for \texttt{ij} when
 %   title casing. If the appropriate letters are found, produce \texttt{IJ}
 %   and gobble the \texttt{j}/\texttt{J}.
@@ -1701,7 +1815,8 @@
           { \int_compare_p:nNn { `#3 } = { "006A } }
       }
       {
-        \char_generate:nn { "004A } { \@@_char_catcode:N #3 }
+        \@@_change_case_store:e
+          { \char_generate:nn { "004A } { \@@_char_catcode:N #3 } }
         \use:c { @@_change_case_char_next_ #1 :nn } {#1} {#2}
       }
       { \use:c { @@_change_case_char_next_ #1 :nn } {#1} {#2} #3 }
@@ -1711,10 +1826,10 @@
 % \end{macro}
 % \end{macro}
 %
-% \begin{macro}[rEXP]{\@@_change_case_lower_tr:nnnN}
-% \begin{macro}[rEXP]{\@@_change_case_lower_tr:nnNw}
-% \begin{macro}[rEXP]{\@@_change_case_lower_tr:nnN}
-% \begin{macro}[rEXP]{\@@_change_case_lower_tr:nnnNN}
+% \begin{macro}[EXP]{\@@_change_case_lower_tr:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_lower_tr:nnNw}
+% \begin{macro}[EXP]{\@@_change_case_lower_tr:nnN}
+% \begin{macro}[EXP]{\@@_change_case_lower_tr:nnnNN}
 %   The Turkic languages need special treatment for dotted-i and dotless-i.
 %   The lower casing rule can be expressed in terms of searching first for
 %   either a dotless-I or a dotted-I. In the latter case the mapping is
@@ -1731,7 +1846,8 @@
           {
             \int_compare:nNnTF { `#4 } = { "0130 }
               {
-                \char_generate:nn { "0069 } { \@@_char_catcode:N #4 }
+                \@@_change_case_store:e
+                  { \char_generate:nn { "0069 } { \@@_char_catcode:N #4 } }
                 \@@_change_case_loop:nnw {#1} {#3}
               }
               { \@@_change_case_lower_sigma:nnnN {#1} {#2} {#3} #4 }
@@ -1748,7 +1864,8 @@
         \tl_if_head_is_N_type:nTF {#4}
           { \@@_change_case_lower_tr:nnN }
           {
-            \char_generate:nn { "0131 } { \@@_char_catcode:N #3 }
+            \@@_change_case_store:e
+              { \char_generate:nn { "0131 } { \@@_char_catcode:N #3 } }
             \@@_change_case_loop:nnw
           }
             {#1} {#2} #4 \q_recursion_stop
@@ -1759,11 +1876,13 @@
           { \token_if_cs_p:N #3 }
           { ! \int_compare_p:nNn { `#3 } = { "0307 } }
           {
-            \char_generate:nn { "0131 } { \@@_char_catcode:N #3 }
+            \@@_change_case_store:e 
+              { \char_generate:nn { "0131 } { \@@_char_catcode:N #3 } }
             \@@_change_case_loop:nnw {#1} {#2} #3
           }
           {
-            \char_generate:nn { "0069 } { \@@_char_catcode:N #3 }
+            \@@_change_case_store:e
+              { \char_generate:nn { "0069 } { \@@_char_catcode:N #3 } }
             \@@_change_case_loop:nnw {#1} {#2}
           }
       }
@@ -1783,7 +1902,7 @@
       {
         \int_compare:nNnTF { `#4 } = { "0049 }
           {
-            \exp_not:V \c_@@_dotless_i_tl
+            \@@_change_case_store:V \c_@@_dotless_i_tl
             \@@_change_case_loop:nnw {#1} {#3}
           }
           {
@@ -1796,7 +1915,11 @@
       {
         \int_compare:nNnTF { `#5 } = { "00B0 }
           {
-            \char_generate:nn { "0069 } { \char_value_catcode:n { "0069 } }
+            \@@_change_case_store:e
+              {
+                \char_generate:nn { "0069 }
+                  { \char_value_catcode:n { "0069 } }
+              }
             \@@_change_case_loop:nnw {#1} {#3}
           }
           { \@@_change_case_char:nnnN {#1} {#2} {#3} #4#5 }
@@ -1807,7 +1930,7 @@
 % \end{macro}
 % \end{macro}
 % \end{macro}
-% \begin{macro}[rEXP]{\@@_change_case_upper_tr:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_upper_tr:nnnN}
 %   Uppercasing is easier: just one exception with no context.
 %    \begin{macrocode}
 \cs_new:Npx \@@_change_case_upper_tr:nnnN #1#2#3#4
@@ -1822,7 +1945,7 @@
               { \exp_not:N \@@_char_catcode:N #4 }
           }
           {
-            \exp_not:N \exp_not:V
+            \exp_not:N \@@_change_case_store:V
             \exp_not:N \c_@@_dotted_I_tl
           }
         \exp_not:N \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3}
@@ -1832,7 +1955,8 @@
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}{\@@_change_case_lower_az:nnnN, \@@_change_case_upper_az:nnnN}
+% \begin{macro}[EXP]
+%   {\@@_change_case_lower_az:nnnN, \@@_change_case_upper_az:nnnN}
 %   Straight copies.
 %    \begin{macrocode}
 \cs_new_eq:NN \@@_change_case_lower_az:nnnN
@@ -2119,7 +2243,9 @@
 % \begin{macro}[rEXP]{\@@_purify_replace:n}
 % \begin{macro}[rEXP]{\@@_purify_expand:N, \@@_purify_protect:N}
 %   As in the other parts of the module, we start off with a standard
-%   \enquote{action} loop, with expansion applied up-front.
+%   \enquote{action} loop, with expansion applied up-front. Here, as there
+%   will be no text commands left in the output, there is no concern about
+%   using \cs{exp_not:n} and \texttt{e}-type expansion.
 %    \begin{macrocode}
 \cs_new:Npn \text_purify:n #1
   {





More information about the latex3-commits mailing list