From 472e2dc119ba3eca6a8672e7606df6a7dc847a19 Mon Sep 17 00:00:00 2001
From: Karl Berry <karl@freefriends.org>
Date: Mon, 28 Sep 2015 21:17:44 +0000
Subject: l3 (28sep15)

git-svn-id: svn://tug.org/texlive/trunk@38490 c570f23f-e606-0410-a88d-b1316a301751
---
 Master/texmf-dist/source/latex/l3kernel/l3str.dtx | 1070 +++++++++++++++++++--
 1 file changed, 977 insertions(+), 93 deletions(-)

(limited to 'Master/texmf-dist/source/latex/l3kernel/l3str.dtx')

diff --git a/Master/texmf-dist/source/latex/l3kernel/l3str.dtx b/Master/texmf-dist/source/latex/l3kernel/l3str.dtx
index 63129bb4fcb..34d57cf2ff7 100644
--- a/Master/texmf-dist/source/latex/l3kernel/l3str.dtx
+++ b/Master/texmf-dist/source/latex/l3kernel/l3str.dtx
@@ -1,6 +1,6 @@
 % \iffalse meta-comment
 %
-%% File: l3str.dtx Copyright (C) 2014-2015 The LaTeX3 Project
+%% File: l3str.dtx Copyright (C) 2011-2015 The LaTeX3 Project
 %%
 %% It may be distributed and/or modified under the conditions of the
 %% LaTeX Project Public License (LPPL), either version 1.3c of this
@@ -37,7 +37,7 @@
 \documentclass[full]{l3doc}
 %</driver>
 %<*driver|package>
-\GetIdInfo$Id: l3str.dtx 5983 2015-09-10 18:57:56Z joseph $
+\GetIdInfo$Id: l3str.dtx 6021 2015-09-20 15:10:27Z joseph $
   {L3 Strings}
 %</driver|package>
 %<*driver>
@@ -79,33 +79,163 @@
 % space characters which have category code $10$ (\enquote{space}). Thus
 % at a technical level, a \TeX{} string is a token list with the appropriate
 % category codes. In this documentation, these will simply be referred to as
-% strings: note that they can be stored in token lists as normal.
+% strings.
 %
-% The functions documented here take literal token lists,
-% convert to strings and then carry out manipulations. Thus they may
-% informally be described as \enquote{ignoring} category code. Note that
-% the functions \cs{cs_to_str:N}, \cs{tl_to_str:n}, \cs{tl_to_str:N} and
+% String variables are simply specialised token lists, but by convention
+% should be named with the suffix \texttt{\ldots{}str}.  Such variables
+% should contain characters with category code $12$ (other), except
+% spaces, which have category code $10$ (blank space).  All the
+% functions in this module which accept a token list argument first
+% convert it to a string using \cs{tl_to_str:n} for internal processing,
+% and will not treat a token list or the corresponding string
+% representation differently.
+%
+% Note that as string variables are a special case of token list variables
+% the coverage of \cs{str_\ldots{}:N} functions is somewhat smaller than
+% \cs{tl_\ldots{}:N}.
+%
+% The functions \cs{cs_to_str:N}, \cs{tl_to_str:n}, \cs{tl_to_str:N} and
 % \cs{token_to_str:N} (and variants) will generate strings from the appropriate
 % input: these are documented in \pkg{l3basics}, \pkg{l3tl} and \pkg{l3token},
 % respectively.
 %
-% \section{The first character from a string}
+% Most expandable functions in this module come in three flavours:
+% \begin{itemize}
+%   \item \cs{str_...:N}, which expect a token list or string
+%     variable as their argument;
+%   \item \cs{str_...:n}, taking any token list (or string) as an
+%     argument;
+%   \item \cs{str_..._ignore_spaces:n}, which ignores any space
+%     encountered during the operation: these functions are typically
+%     faster than those which take care of escaping spaces
+%     appropriately.
+% \end{itemize}
+%
+% \section{Building strings}
 %
-% \begin{function}[added = 2011-08-10, EXP]{\str_head:n, \str_tail:n}
+% \begin{function}[added = 2015-09-18]{\str_new:N, \str_new:c}
 %   \begin{syntax}
-%     \cs{str_head:n} \Arg{token list}
-%     \cs{str_tail:n} \Arg{token list}
+%     \cs{str_new:N} \meta{str~var}
+%   \end{syntax}
+%   Creates a new \meta{str~var} or raises an error if the name is
+%   already taken.  The declaration is global.  The \meta{str~var} will
+%   initially be empty.
+% \end{function}
+%
+% \begin{function}[added = 2015-09-18]
+%   {\str_const:Nn, \str_const:Nx, \str_const:cn, \str_const:cx}
+%   \begin{syntax}
+%     \cs{str_const:Nn} \meta{str~var} \Arg{token list}
+%   \end{syntax}
+%   Creates a new constant \meta{str~var} or raises an error if the name
+%   is already taken.  The value of the \meta{str~var} will be set
+%   globally to the \meta{token list}, converted to a string.
+% \end{function}
+%
+% \begin{function}[added = 2015-09-18]
+%   {\str_clear:N, \str_clear:c, \str_gclear:N, \str_gclear:c}
+%   \begin{syntax}
+%     \cs{str_clear:N} \meta{str~var}
+%   \end{syntax}
+%   Clears the content of the \meta{str~var}.
+% \end{function}
+%
+% \begin{function}[added = 2015-09-18]{\str_clear_new:N, \str_clear_new:c}
+%   \begin{syntax}
+%     \cs{str_clear_new:N} \meta{str~var}
+%   \end{syntax}
+%   Ensures that the \meta{str~var} exists globally by applying
+%   \cs{str_new:N} if necessary, then applies \cs{str_(g)clear:N} to leave
+%   the \meta{str~var} empty.
+% \end{function}
+%
+% \begin{function}[added = 2015-09-18]
+%   {
+%     \str_set_eq:NN,  \str_set_eq:cN,  \str_set_eq:Nc,  \str_set_eq:cc,
+%     \str_gset_eq:NN, \str_gset_eq:cN, \str_gset_eq:Nc, \str_gset_eq:cc
+%   }
+%   \begin{syntax}
+%     \cs{str_set_eq:NN} \meta{str~var_1} \meta{str~var_2}
 %   \end{syntax}
-%   Converts the \meta{token list} into a string, as described for
-%   \cs{tl_to_str:n}. The \cs{str_head:n} function then leaves
-%   the first character of this string in the input stream.
-%   The \cs{str_tail:n} function leaves all characters except
-%   the first in the input stream. The first character may be
-%   a space. If the \meta{token list} argument is entirely empty,
-%   nothing is left in the input stream.
+%   Sets the content of \meta{str~var_1} equal to that of
+%   \meta{str~var_2}.
 % \end{function}
 %
-% \subsection{Tests on strings}
+% \section{Adding data to string variables}
+%
+% \begin{function}[added = 2015-09-18]
+%   {
+%     \str_set:Nn,  \str_set:Nx,  \str_set:cn,  \str_set:cx,
+%     \str_gset:Nn, \str_gset:Nx, \str_gset:cn, \str_gset:cx
+%   }
+%   \begin{syntax}
+%     \cs{str_set:Nn} \meta{str var} \Arg{token list}
+%   \end{syntax}
+%   Converts the \meta{token list} to a \meta{string}, and stores the
+%   result in \meta{str var}.
+% \end{function}
+%
+% \begin{function}[added = 2015-09-18]
+%   {
+%     \str_put_left:Nn,  \str_put_left:Nx,
+%     \str_put_left:cn,  \str_put_left:cx,
+%     \str_gput_left:Nn, \str_gput_left:Nx,
+%     \str_gput_left:cn, \str_gput_left:cx
+%   }
+%   \begin{syntax}
+%     \cs{str_put_left:Nn} \meta{str var} \Arg{token list}
+%   \end{syntax}
+%   Converts the \meta{token list} to a \meta{string}, and prepends the
+%   result to \meta{str var}.  The current contents of the \meta{str
+%     var} are not automatically converted to a string.
+% \end{function}
+%
+% \begin{function}[added = 2015-09-18]
+%   {
+%     \str_put_right:Nn,  \str_put_right:Nx,
+%     \str_put_right:cn,  \str_put_right:cx,
+%     \str_gput_right:Nn, \str_gput_right:Nx,
+%     \str_gput_right:cn, \str_gput_right:cx
+%   }
+%   \begin{syntax}
+%     \cs{str_put_right:Nn} \meta{str var} \Arg{token list}
+%   \end{syntax}
+%   Converts the \meta{token list} to a \meta{string}, and appends the
+%   result to \meta{str var}.  The current contents of the \meta{str
+%     var} are not automatically converted to a string.
+% \end{function}
+%
+% \subsection{String conditionals}
+%
+% \begin{function}[EXP, pTF, added = 2015-09-18]
+%   {\str_if_exist:N, \str_if_exist:c}
+%   \begin{syntax}
+%     \cs{str_if_exist_p:N} \meta{str~var}
+%     \cs{str_if_exist:NTF} \meta{str~var} \Arg{true code} \Arg{false code}
+%   \end{syntax}
+%   Tests whether the \meta{str~var} is currently defined.  This does not
+%   check that the \meta{str~var} really is a string.
+% \end{function}
+%
+% \begin{function}[EXP,pTF, added = 2015-09-18]
+%   {\str_if_empty:N, \str_if_empty:c}
+%   \begin{syntax}
+%     \cs{sr_if_empty_p:N} \meta{str~var}
+%     \cs{str_if_empty:NTF} \meta{str~var} \Arg{true code} \Arg{false code}
+%   \end{syntax}
+%   Tests if the \meta{string variable} is entirely empty
+%   (\emph{i.e.}~contains no characters at all).
+% \end{function}
+%
+% \begin{function}[EXP,pTF, added = 2015-09-18]
+%   {\str_if_eq:NN, \str_if_eq:Nc, \str_if_eq:cN, \str_if_eq:cc}
+%   \begin{syntax}
+%     \cs{str_if_eq_p:NN} \meta{str~var_1} \meta{str~var_2}
+%     \cs{str_if_eq:NNTF} \meta{str~var_1} \meta{str~var_2} \Arg{true code} \Arg{false code}
+%   \end{syntax}
+%   Compares the content of two \meta{str variables} and
+%   is logically \texttt{true} if the two contain the same characters.
+% \end{function}
 %
 % \begin{function}[EXP,pTF]
 %   {
@@ -189,6 +319,116 @@
 %   not be used within this string.
 % \end{function}
 %
+% \section{Working with the content of strings}
+%
+% \begin{function}[EXP, added = 2015-09-18]{\str_use:N, \str_use:c}
+%   \begin{syntax}
+%     \cs{str_use:N} \meta{str~var}
+%   \end{syntax}
+%   Recovers the content of a \meta{str~var} and places it
+%   directly in the input stream. An error will be raised if the variable
+%   does not exist or if it is invalid. Note that it is possible to use
+%   a \meta{str} directly without an accessor function.
+% \end{function}
+%
+% \begin{function}[EXP, added = 2015-09-18]
+%   {\str_count:N, \str_count:c, \str_count:n, \str_count_ignore_spaces:n}
+%   \begin{syntax}
+%     \cs{str_count:n} \Arg{token list}
+%   \end{syntax}
+%   Leaves in the input stream the number of characters in the string
+%   representation of \meta{token list}, as an integer denotation.  The
+%   functions differ in their treatment of spaces.  In the case of
+%   \cs{str_count:N} and \cs{str_count:n}, all characters including
+%   spaces are counted.  The \cs{str_count_ignore_spaces:n} function
+%   leaves the number of non-space characters in the input stream.
+% \end{function}
+%
+% \begin{function}[EXP, added = 2015-09-18]
+%   {\str_count_spaces:N, \str_count_spaces:c, \str_count_spaces:n}
+%   \begin{syntax}
+%     \cs{str_count_spaces:n} \Arg{token list}
+%   \end{syntax}
+%   Leaves in the input stream the number of space characters in the
+%   string representation of \meta{token list}, as an integer
+%   denotation. Of course, this function has no \texttt{_ignore_spaces}
+%   variant.
+% \end{function}
+%
+% \begin{function}[EXP, added = 2015-09-18]
+%   {\str_head:N, \str_head:c, \str_head:n, \str_head_ignore_spaces:n}
+%   \begin{syntax}
+%     \cs{str_head:n} \Arg{token list}
+%   \end{syntax}
+%   Converts the \meta{token list} into a \meta{string}.  The first
+%   character in the \meta{string} is then left in the input stream,
+%   with category code \enquote{other}.  The functions differ if the
+%   first character is a space: \cs{str_head:N} and \cs{str_head:n}
+%   return a space token with category code~$10$ (blank space), while
+%   the \cs{str_head_ignore_spaces:n} function ignores this space
+%   character and leaves the first non-space character in the input
+%   stream.  If the \meta{string} is empty (or only contains spaces in
+%   the case of the \texttt{_ignore_spaces} function), then nothing is
+%   left on the input stream.
+% \end{function}
+%
+% \begin{function}[EXP, added = 2015-09-18]
+%   {\str_tail:N, \str_tail:c, \str_tail:n, \str_tail_ignore_spaces:n}
+%   \begin{syntax}
+%     \cs{str_tail:n} \Arg{token list}
+%   \end{syntax}
+%   Converts the \meta{token list} to a \meta{string}, removes the first
+%   character, and leaves the remaining characters (if any) in the input
+%   stream, with category codes $12$ and $10$ (for spaces).  The
+%   functions differ in the case where the first character is a space:
+%   \cs{str_tail:N} and \cs{str_tail:n} will trim only that space, while
+%   \cs{str_tail_ignore_spaces:n} removes the first non-space character
+%   and any space before it.  If the \meta{token list} is empty (or
+%   blank in the case of the \texttt{_ignore_spaces} variant), then
+%   nothing is left on the input stream.
+% \end{function}
+%
+% \begin{function}[EXP, added = 2015-09-18]
+%   {\str_item:Nn, \str_item:nn, \str_item_ignore_spaces:nn}
+%   \begin{syntax}
+%     \cs{str_item:nn} \Arg{token list} \Arg{integer expression}
+%   \end{syntax}
+%   Converts the \meta{token list} to a \meta{string}, and leaves in the
+%   input stream the character in position \meta{integer expression} of
+%   the \meta{string}, starting at $1$ for the first (left-most)
+%   character.  In the case of \cs{str_item:Nn} and \cs{str_item:nn},
+%   all characters including spaces are taken into account.  The
+%   \cs{str_item_ignore_spaces:nn} function skips spaces when counting
+%   characters.  If the \meta{integer expression} is negative,
+%   characters are counted from the end of the \meta{string}. Hence,
+%   $-1$ is the right-most character, \emph{etc.}
+% \end{function}
+%
+% \begin{function}[EXP, added = 2015-09-18]
+%   {
+%     \str_range:Nnn, \str_range:cnn, \str_range:nnn,
+%     \str_range_ignore_spaces:nnn
+%   }
+%   \begin{syntax}
+%     \cs{str_range:nnn} \Arg{token list} \Arg{start index} \Arg{end index}
+%   \end{syntax}
+%   Converts the \meta{token list} to a \meta{string}, and leaves in the
+%   input stream the characters from the \meta{start index} to the
+%   \meta{end index} inclusive.  Positive \meta{indices} are counted
+%   from the start of the string, $1$~being the first character, and
+%   negative \meta{indices} are counted from the end of the string,
+%   $-1$~being the last character.  If either of \meta{start index} or
+%   \meta{end index} is~$0$, the result is empty.  For instance,
+%   \begin{verbatim}
+%     \iow_term:x { \str_range:nnn { abcdef } { 2 } { 5 } }
+%     \iow_term:x { \str_range:nnn { abcdef } { -4 } { -1 } }
+%     \iow_term:x { \str_range:nnn { abcdef } { -2 } { -1 } }
+%     \iow_term:x { \str_range:nnn { abcdef } { 0 } { -1 } }
+%   \end{verbatim}
+%   will print \texttt{bcd}, \texttt{cdef}, \texttt{ef}, and an empty
+%   line to the terminal.
+% \end{function}
+%
 % \section{String manipulation}
 %
 % \begin{function}[rEXP, added = 2015-03-01]
@@ -294,6 +534,53 @@
 %   \end{texnote}
 % \end{function}
 %
+% \section{Viewing strings}
+%
+% \begin{function}[added = 2015-09-18]
+%   {\str_show:N, \str_show:c, \str_show:n}
+%   \begin{syntax}
+%     \cs{str_show:N} \meta{str~var}
+%   \end{syntax}
+%   Displays the content of the \meta{str~var} on the terminal.
+% \end{function}
+%
+% \section{Constant token lists}
+%
+% \begin{variable}[added = 2016-09-19]
+%   {
+%     \c_ampersand_str,
+%     \c_atsign_str,
+%     \c_backslash_str,
+%     \c_left_brace_str,
+%     \c_right_brace_str,
+%     \c_circumflex_str,
+%     \c_colon_str,
+%     \c_dollar_str,
+%     \c_hash_str,
+%     \c_percent_str,
+%     \c_tilde_str,
+%     \c_underscore_str
+%   }
+%   Constant strings, containing a single character token, with category
+%   code $12$.
+% \end{variable}
+%
+% \section{Scratch strings}
+%
+% \begin{variable}{\l_tmpa_str, \l_tmpb_str}
+%   Scratch strings for local assignment. These are never used by
+%   the kernel code, and so are safe for use with any \LaTeX3-defined
+%   function. However, they may be overwritten by other non-kernel
+%   code and so should only be used for short-term storage.
+% \end{variable}
+%
+% \begin{variable}{\g_tmpa_str, \g_tmpb_str}
+%   Scratch strings for global assignment. These are never used by
+%   the kernel code, and so are safe for use with any \LaTeX3-defined
+%   function. However, they may be overwritten by other non-kernel
+%   code and so should only be used for short-term storage.
+% \end{variable}
+%
 % \subsection{Internal string functions}
 %
 % \begin{function}[EXP]{\__str_if_eq_x:nn}
@@ -317,6 +604,38 @@
 %   of \cs{str_if_eq_x:nn(TF)} coded for speed.
 % \end{function}
 %
+% \begin{function}[EXP]{\__str_to_other:n}
+%   \begin{syntax}
+%     \cs{__str_to_other:n} \Arg{token list}
+%   \end{syntax}
+%   Converts the \meta{token list} to a \meta{other string}, where
+%   spaces have category code \enquote{other}.  This function can be
+%   \texttt{f}-expanded without fear of losing a leading space, since
+%   spaces do not have category code $10$ in its result.  It takes a
+%   time quadratic in the character count of the string.
+% \end{function}
+%
+% \begin{function}[EXP]{\__str_count:n}
+%   \begin{syntax}
+%     \cs{__str_count:n} \Arg{other string}
+%   \end{syntax}
+%   This function expects an argument that is entirely made of
+%   characters with category \enquote{other}, as produced by
+%   \cs{__str_to_other:n}.  It leaves in the input stream the number of
+%   character tokens in the \meta{other string}, faster than the
+%   analogous \cs{str_count:n} function.
+% \end{function}
+%
+% \begin{function}[EXP]{\__str_range:nnn}
+%   \begin{syntax}
+%     \cs{__str_range:nnn} \Arg{other string} \Arg{start index} \Arg{end index}
+%   \end{syntax}
+%   Identical to \cs{str_range:nnn} except that the first argument is
+%   expected to be entirely made of characters with category
+%   \enquote{other}, as produced by \cs{__str_to_other:n}, and the
+%   result is also an \meta{other string}.
+% \end{function}
+%
 % \end{documentation}
 %
 % \begin{implementation}
@@ -331,54 +650,113 @@
 %<@@=str>
 %    \end{macrocode}
 %
-% \begin{macro}{\str_head:n, \str_tail:n}
-% \begin{macro}[aux]{\__str_head:w}
-% \begin{macro}[aux]{\__str_tail:w}
-%   After \cs{tl_to_str:n}, we have a list of character tokens,
-%   all with category code 12, except the space, which has category
-%   code 10. Directly using \cs{tl_head:w} would thus lose leading spaces.
-%   Instead, we take an argument delimited by an explicit space, and
-%   then only use \cs{tl_head:w}. If the string started with a
-%   space, then the argument of \cs{__str_head:w} is empty, and
-%   the function correctly returns a space character. Otherwise,
-%   it returns the first token of |#1|, which is the first token
-%   of the string. If the string is empty, we return an empty result.
-%
-%   To remove the first character of \cs{tl_to_str:n} |{#1}|,
-%   we test it using \cs{if_charcode:w} \cs{scan_stop:},
-%   always \texttt{false} for characters. If the argument was non-empty,
-%   then \cs{__str_tail:w} returns everything until the first
-%   \texttt{X} (with category code letter, no risk of confusing
-%   with the user input). If the argument was empty, the first
-%   \texttt{X} is taken by \cs{if_charcode:w}, and nothing
-%   is returned. We use \texttt{X} as a \meta{marker}, rather than
-%   a quark because the test \cs{if_charcode:w} \cs{scan_stop:}
-%   \meta{marker} has to be \texttt{false}.
+% \subsection{Creating and setting string variables}
+%
+% \begin{macro}
+%   {
+%     \str_new:N, \str_new:c,
+%     \str_use:N, \str_use:c,
+%     \str_clear:N, \str_clear:c,
+%     \str_gclear:N,\str_gclear:c,
+%     \str_clear_new:N, \str_clear_new:c,
+%     \str_gclear_new:N, \str_gclear_new:c
+%   }
+% \begin{macro}
+%   {
+%     \str_set_eq:NN,  \str_set_eq:cN,  \str_set_eq:Nc,  \str_set_eq:cc,
+%     \str_gset_eq:NN, \str_gset_eq:cN, \str_gset_eq:Nc, \str_gset_eq:cc
+%   }
+%   A string is simply a token list. The full mapping system isn't set up
+%   yet so do things by hand.
 %    \begin{macrocode}
-\cs_new:Npn \str_head:n #1
-  {
-    \exp_after:wN \__str_head:w
-    \tl_to_str:n {#1}
-    { { } } ~ \q_stop
-  }
-\cs_new:Npn \__str_head:w #1 ~ %
-  { \tl_head:w #1 { ~ } }
-\cs_new:Npn \str_tail:n #1
-  {
-    \exp_after:wN \__str_tail:w
-    \reverse_if:N \if_charcode:w
-        \scan_stop: \tl_to_str:n {#1} X X \q_stop
-  }
-\cs_new:Npn \__str_tail:w #1 X #2 \q_stop { \fi: #1 }
+\group_begin:
+  \cs_set_protected:Npn \@@_tmp:n #1
+    {
+      \tl_if_blank:nF {#1}
+        {
+          \cs_new_eq:cc { str_ #1 :N } { tl_ #1 :N }
+          \exp_args:Nc \cs_generate_variant:Nn { str_ #1 :N } { c }
+          \@@_tmp:n
+        }
+    }
+  \@@_tmp:n
+    { new }
+    { use }
+    { clear }
+    { gclear }
+    { clear_new }
+    { gclear_new }
+    { }
+\group_end:
+\cs_new_eq:NN \str_set_eq:NN \tl_set_eq:NN
+\cs_new_eq:NN \str_gset_eq:NN \tl_gset_eq:NN
+\cs_generate_variant:Nn \str_set_eq:NN  { c , Nc , cc }
+\cs_generate_variant:Nn \str_gset_eq:NN { c , Nc , cc }
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
+%
+% \begin{macro}
+%   {
+%     \str_set:Nn, \str_set:Nx,
+%     \str_set:cn, \str_set:cx,
+%     \str_gset:Nn, \str_gset:Nx,
+%     \str_gset:cn, \str_gset:cx,
+%     \str_const:Nn, \str_const:Nx,
+%     \str_const:cn, \str_const:cx,
+%     \str_put_left:Nn, \str_put_left:Nx,
+%     \str_put_left:cn, \str_put_left:cx,
+%     \str_gput_left:Nn, \str_gput_left:Nx,
+%     \str_gput_left:cn, \str_gput_left:cx,
+%     \str_put_right:Nn, \str_put_right:Nx,
+%     \str_put_right:cn, \str_put_right:cx,
+%     \str_gput_right:Nn, \str_gput_right:Nx,
+%     \str_gput_right:cn, \str_gput_right:cx,
+%   }
+%   Simply convert the token list inputs to \meta{strings}.
+%    \begin{macrocode}
+\group_begin:
+  \cs_set_protected:Npn \@@_tmp:n #1
+    {
+      \tl_if_blank:nF {#1}
+        {
+          \cs_new_protected:cpx { str_ #1 :Nn } ##1##2
+            { \exp_not:c { tl_ #1 :Nx } ##1 { \exp_not:N \tl_to_str:n {##2} } }
+          \exp_args:Nc \cs_generate_variant:Nn { str_ #1 :Nn } { Nx , cn , cx }
+          \@@_tmp:n
+        }
+    }
+  \@@_tmp:n
+    { set }
+    { gset }
+    { const }
+    { put_left }
+    { gput_left }
+    { put_right }
+    { gput_right }
+    { }
+\group_end:
+%    \end{macrocode}
 % \end{macro}
 %
 % \subsection{String comparisons}
 %
-% \begin{macro}[int, EXP]{\__str_if_eq_x:nn}
-% \begin{macro}[aux, EXP]{\__str_escape_x:n}
+% \begin{macro}[pTF, EXP]
+%   {
+%     \str_if_empty:N, \str_if_empty:c,
+%     \str_if_exist:N, \str_if_exist:c
+%   }
+%   More copy-paste!
+%    \begin{macrocode}
+\prg_new_eq_conditional:NNn \str_if_exist:N \tl_if_exist:N { p , T , F , TF }
+\prg_new_eq_conditional:NNn \str_if_exist:c \tl_if_exist:c { p , T , F , TF }
+\prg_new_eq_conditional:NNn \str_if_empty:N \tl_if_empty:N { p , T , F , TF }
+\prg_new_eq_conditional:NNn \str_if_empty:c \tl_if_empty:c { p , T , F , TF }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int, EXP]{\@@_if_eq_x:nn}
+% \begin{macro}[aux, EXP]{\@@_escape_x:n}
 %   String comparisons rely on the primitive \cs{(pdf)strcmp} if available:
 %   \LuaTeX{} does not have it, so emulation is required. As the net result
 %   is that we do not \emph{always} use the primitive, the correct approach
@@ -392,21 +770,21 @@
 %   otherwise will fail as \cs{luatex_luaescapestring:D} does not double
 %   such tokens.
 %    \begin{macrocode}
-\cs_new:Npn \__str_if_eq_x:nn #1#2 { \pdftex_strcmp:D {#1} {#2} }
+\cs_new:Npn \@@_if_eq_x:nn #1#2 { \pdftex_strcmp:D {#1} {#2} }
 \cs_if_exist:NT \luatex_luatexversion:D
    {
-     \cs_set:Npn \__str_if_eq_x:nn #1#2
+     \cs_set:Npn \@@_if_eq_x:nn #1#2
        {
           \luatex_directlua:D
             {
               l3kernel.strcmp
                 (
-                  " \__str_escape_x:n {#1} " ,
-                  " \__str_escape_x:n {#2} "
+                  " \@@_escape_x:n {#1} " ,
+                  " \@@_escape_x:n {#2} "
                 )
             }
        }
-     \cs_new:Npn \__str_escape_x:n #1
+     \cs_new:Npn \@@_escape_x:n #1
        {
          \luatex_luaescapestring:D
            {
@@ -418,16 +796,16 @@
 % \end{macro}
 % \end{macro}
 %
-% \begin{macro}[int, EXP]{\__str_if_eq_x_return:nn}
+% \begin{macro}[int, EXP]{\@@_if_eq_x_return:nn}
 %   It turns out that we often need to compare a token list
 %   with the result of applying some function to it, and
 %   return with \cs{prg_return_true/false:}. This test is
 %   similar to \cs{str_if_eq:nnTF} (see \pkg{l3str}),
 %   but is hard-coded for speed.
 %    \begin{macrocode}
-\cs_new:Npn \__str_if_eq_x_return:nn #1 #2
+\cs_new:Npn \@@_if_eq_x_return:nn #1 #2
   {
-    \if_int_compare:w \__str_if_eq_x:nn {#1} {#2} = \c_zero
+    \if_int_compare:w \@@_if_eq_x:nn {#1} {#2} = \c_zero
       \prg_return_true:
     \else:
       \prg_return_false:
@@ -450,7 +828,7 @@
 \prg_new_conditional:Npnn \str_if_eq:nn #1#2 { p , T , F , TF }
   {
     \if_int_compare:w
-      \__str_if_eq_x:nn { \exp_not:n {#1} } { \exp_not:n {#2} }
+      \@@_if_eq_x:nn { \exp_not:n {#1} } { \exp_not:n {#2} }
       = \c_zero
       \prg_return_true: \else: \prg_return_false: \fi:
   }
@@ -464,13 +842,27 @@
 \cs_generate_variant:Nn \str_if_eq:nnTF { nV , no , VV }
 \prg_new_conditional:Npnn \str_if_eq_x:nn #1#2 { p , T , F , TF }
   {
-    \if_int_compare:w \__str_if_eq_x:nn {#1} {#2} = \c_zero
+    \if_int_compare:w \@@_if_eq_x:nn {#1} {#2} = \c_zero
       \prg_return_true: \else: \prg_return_false: \fi:
   }
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}[int, EXP]{\@@_if_eq_x_return:nn}
+% \begin{macro}[EXP, pTF]
+%   {\str_if_eq:NN, \str_if_eq:Nc, \str_if_eq:cN, \str_if_eq:cc}
+%   Note that \cs{str_if_eq:NN} is different from
+%   \cs{tl_if_eq:NN} because it needs to ignore category codes.
+%    \begin{macrocode}
+\prg_new_conditional:Npnn \str_if_eq:NN #1#2 { p , TF , T , F }
+  {
+    \if_int_compare:w \@@_if_eq_x:nn { \tl_to_str:N #1 } { \tl_to_str:N #2 }
+      = \c_zero \prg_return_true: \else: \prg_return_false: \fi:
+  }
+\cs_generate_variant:Nn \str_if_eq:NNT  { c , Nc , cc }
+\cs_generate_variant:Nn \str_if_eq:NNF  { c , Nc , cc }
+\cs_generate_variant:Nn \str_if_eq:NNTF { c , Nc , cc }
+\cs_generate_variant:Nn \str_if_eq_p:NN { c , Nc , cc }
+%    \end{macrocode}
 % \end{macro}
 %
 % \begin{macro}[EXP]
@@ -550,6 +942,444 @@
 % \end{macro}
 % \end{macro}
 %
+% \subsection{Accessing specific characters in a string}
+%
+% \begin{macro}[EXP, int]{\@@_to_other:n}
+% \begin{macro}[EXP, aux]{\@@_to_other_loop:w, \@@_to_other_end:w}
+%   First apply \cs{tl_to_str:n}, then replace all spaces by
+%   \enquote{other} spaces, $8$ at a time, storing the converted part of
+%   the string between the \cs{q_mark} and \cs{q_stop} markers.  The end
+%   is detected when \cs{@@_to_other_loop:w} finds one of the trailing
+%   |A|, distinguished from any contents of the initial token list by
+%   their category.  Then \cs{@@_to_other_end:w} is called, and finds
+%   the result between \cs{q_mark} and the first |A| (well, there is
+%   also the need to remove a space).
+%    \begin{macrocode}
+\cs_new:Npn \@@_to_other:n #1
+  {
+    \exp_after:wN \@@_to_other_loop:w
+      \tl_to_str:n {#1} ~ A ~ A ~ A ~ A ~ A ~ A ~ A ~ A ~ \q_mark \q_stop
+  }
+\group_begin:
+\tex_lccode:D `\* = `\  %
+\tex_lccode:D `\A = `\A 
+\tex_lowercase:D
+  {
+    \group_end:
+    \cs_new:Npn \@@_to_other_loop:w
+      #1 ~ #2 ~ #3 ~ #4 ~ #5 ~ #6 ~ #7 ~ #8 ~ #9 \q_stop
+      {
+        \if_meaning:w A #8
+          \@@_to_other_end:w
+        \fi:
+        \@@_to_other_loop:w
+        #9 #1 * #2 * #3 * #4 * #5 * #6 * #7 * #8 * \q_stop
+      }
+    \cs_new:Npn \@@_to_other_end:w \fi: #1 \q_mark #2 * A #3 \q_stop
+      { \fi: #2 }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]
+%   {\str_item:Nn, \str_item:cn, \str_item:nn, \str_item_ignore_spaces:nn}
+% \begin{macro}[EXP, aux]{\@@_item:nn, \@@_item:w}
+%   The \cs{str_item:nn} hands its argument with spaces escaped to
+%   \cs{@@_item:nn}, and makes sure to turn the result back into
+%   a proper string (with category code~$10$ spaces) eventually.  The
+%   \cs{str_item_ignore_spaces:nn} function does not escape spaces,
+%   which are thus ignored by \cs{@@_item:nn} since
+%   everything else is done with undelimited arguments.
+%   Evaluate the \meta{index} argument~|#2| and count characters in
+%   the string, passing those two numbers to \cs{@@_item:w} for
+%   further analysis.  If the \meta{index} is negative, shift it by
+%   the \meta{count} to know the how many character to discard, and if
+%   that is still negative give an empty result.  If the \meta{index}
+%   is larger than the \meta{count}, give an empty result, and
+%   otherwise discard $\meta{index}-1$ characters before returning the
+%   following one.  The shift by $-1$ is obtained by inserting an empty
+%   brace group before the string in that case: that brace group also
+%   covers the case where the \meta{index} is zero.
+%    \begin{macrocode}
+\cs_new_nopar:Npn \str_item:Nn { \exp_args:No \str_item:nn }
+\cs_generate_variant:Nn \str_item:Nn { c }
+\cs_new:Npn \str_item:nn #1#2
+  {
+    \exp_args:Nf \tl_to_str:n
+      {
+        \exp_args:Nf \@@_item:nn
+          { \@@_to_other:n {#1} } {#2}
+      }
+  }
+\cs_new:Npn \str_item_ignore_spaces:nn #1
+  { \exp_args:No \@@_item:nn { \tl_to_str:n {#1} } }
+\cs_new:Npn \@@_item:nn #1#2
+  {
+    \exp_after:wN \@@_item:w
+    \int_use:N \__int_eval:w #2 \exp_after:wN ;
+    \__int_value:w \@@_count:n {#1} ;
+    #1 \q_stop
+  }
+\cs_new:Npn \@@_item:w #1; #2;
+  {
+    \int_compare:nNnTF {#1} < \c_zero
+      {
+        \int_compare:nNnTF {#1} < {-#2}
+          { \use_none_delimit_by_q_stop:w }
+          {
+            \exp_after:wN \use_i_delimit_by_q_stop:nw
+            \exp:w \exp_after:wN \@@_skip_exp_end:w
+              \int_use:N \__int_eval:w #1 + #2 ;
+          }
+      }
+      {
+        \int_compare:nNnTF {#1} > {#2}
+          { \use_none_delimit_by_q_stop:w }
+          {
+            \exp_after:wN \use_i_delimit_by_q_stop:nw
+            \exp:w \@@_skip_exp_end:w #1 ; { }
+          }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP, aux]{\@@_skip_exp_end:w}
+% \begin{macro}[EXP, aux]
+%   {\@@_skip_loop:wNNNNNNNN, \@@_skip_end:w, \@@_skip_end:NNNNNNNN}
+%   Removes |max(#1,0)| characters from the input stream, and then
+%   leaves \cs{exp_end:}.  This should be expanded using
+%   \cs{exp:w}.  We remove characters $8$ at a time until
+%   there are at most $8$ to remove.  Then we do a dirty trick: the
+%   \cs{if_case:w} construction leaves between $0$ and $8$ times the
+%   \cs{or:} control sequence, and those \cs{or:} become arguments of
+%   \cs{@@_skip_end:NNNNNNNN}.  If the number of characters to remove
+%   is $6$, say, then there are two \cs{or:} left, and the $8$ arguments
+%   of \cs{@@_skip_end:NNNNNNNN} are the two \cs{or:}, and $6$
+%   characters from the input stream, exactly what we wanted to
+%   remove. Then close the \cs{if_case:w} conditional with \cs{fi:}, and
+%   stop the initial expansion with \cs{exp_end:} (see places where
+%   \cs{@@_skip_exp_end:w} is called).
+%    \begin{macrocode}
+\cs_new:Npn \@@_skip_exp_end:w #1;
+  {
+    \if_int_compare:w #1 > \c_eight
+      \exp_after:wN \@@_skip_loop:wNNNNNNNN
+    \else:
+      \exp_after:wN \@@_skip_end:w
+      \int_use:N \__int_eval:w
+    \fi:
+    #1 ;
+  }
+\cs_new:Npn \@@_skip_loop:wNNNNNNNN #1; #2#3#4#5#6#7#8#9
+  { \exp_after:wN \@@_skip_exp_end:w \int_use:N \__int_eval:w #1 - \c_eight ; }
+\cs_new:Npn \@@_skip_end:w #1 ;
+  {
+    \exp_after:wN \@@_skip_end:NNNNNNNN
+    \if_case:w #1 \exp_stop_f: \or: \or: \or: \or: \or: \or: \or: \or:
+  }
+\cs_new:Npn \@@_skip_end:NNNNNNNN #1#2#3#4#5#6#7#8 { \fi: \exp_end: }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]
+%   {\str_range:Nnn, \str_range:nnn, \str_range_ignore_spaces:nnn}
+% \begin{macro}[EXP, int]{\@@_range:nnn}
+% \begin{macro}[EXP, aux]{\@@_range:w, \@@_range:nnw}
+%   Sanitize the string.  Then evaluate the arguments.  At this stage we
+%   also decrement the \meta{start index}, since our goal is to know how
+%   many characters should be removed.  Then limit the range to be
+%   non-negative and at most the length of the string (this avoids
+%   needing to check for the end of the string when grabbing
+%   characters), shifting negative numbers by the appropriate amount.
+%   Afterwards, skip characters, then keep some more, and finally drop
+%   the end of the string.
+%    \begin{macrocode}
+\cs_new_nopar:Npn \str_range:Nnn { \exp_args:No \str_range:nnn }
+\cs_generate_variant:Nn \str_range:Nnn { c }
+\cs_new:Npn \str_range:nnn #1#2#3
+  {
+    \exp_args:Nf \tl_to_str:n
+      {
+        \exp_args:Nf \@@_range:nnn
+          { \@@_to_other:n {#1} } {#2} {#3}
+      }
+  }
+\cs_new:Npn \str_range_ignore_spaces:nnn #1
+  { \exp_args:No \@@_range:nnn { \tl_to_str:n {#1} } }
+\cs_new:Npn \@@_range:nnn #1#2#3
+  {
+    \exp_after:wN \@@_range:w
+    \__int_value:w \@@_count:n {#1} \exp_after:wN ;
+    \int_use:N \__int_eval:w #2 - \c_one \exp_after:wN ;
+    \int_use:N \__int_eval:w #3 ;
+    #1 \q_stop
+  }
+\cs_new:Npn \@@_range:w #1; #2; #3;
+  {
+    \exp_args:Nf \@@_range:nnw
+      { \@@_range_normalize:nn {#2} {#1} }
+      { \@@_range_normalize:nn {#3} {#1} }
+  }
+\cs_new:Npn \@@_range:nnw #1#2
+  {
+    \exp_after:wN \@@_collect_delimit_by_q_stop:w
+    \int_use:N \__int_eval:w #2 - #1 \exp_after:wN ;
+    \exp:w \@@_skip_exp_end:w #1 ;
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \begin{macro}[EXP, aux]{\@@_range_normalize:nn}
+%   This function converts an \meta{index} argument into an explicit
+%   position in the string (a result of $0$ denoting \enquote{out of
+%     bounds}).  Expects two explicit integer arguments: the
+%   \meta{index} |#1| and the string count~|#2|.  If |#1| is negative,
+%   replace it by $|#1| + |#2| + 1$, then limit to the range $[0,
+%   |#2|]$.
+%    \begin{macrocode}
+\cs_new:Npn \@@_range_normalize:nn #1#2
+  {
+    \int_eval:n
+      {
+        \if_int_compare:w #1 < \c_zero
+          \if_int_compare:w #1 < -#2 \exp_stop_f:
+            \c_zero
+          \else:
+            #1 + #2 + \c_one
+          \fi:
+        \else:
+          \if_int_compare:w #1 < #2 \exp_stop_f:
+            #1
+          \else:
+            #2
+          \fi:
+        \fi:
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \begin{macro}[EXP, aux]{\@@_collect_delimit_by_q_stop:w}
+% \begin{macro}[EXP, aux]
+%   {
+%     \@@_collect_loop:wn, \@@_collect_loop:wnNNNNNNN,
+%     \@@_collect_end:wn, \@@_collect_end:nnnnnnnnw
+%   }
+%   Collects |max(#1,0)| characters, and removes everything else until
+%   \cs{q_stop}. This is somewhat similar to \cs{@@_skip_exp_end:w}, but
+%   accepts integer expression arguments.  This time we can only grab
+%   $7$ characters at a time.  At the end, we use an \cs{if_case:w}
+%   trick again, so that the $8$ first arguments of
+%   \cs{@@_collect_end:nnnnnnnnw} are some \cs{or:}, followed by an
+%   \cs{fi:}, followed by |#1| characters from the input stream. Simply
+%   leaving this in the input stream will close the conditional properly
+%   and the \cs{or:} disappear.
+%    \begin{macrocode}
+\cs_new:Npn \@@_collect_delimit_by_q_stop:w #1;
+  { \@@_collect_loop:wn #1 ; { } }
+\cs_new:Npn \@@_collect_loop:wn #1 ;
+  {
+    \if_int_compare:w #1 > \c_seven
+      \exp_after:wN \@@_collect_loop:wnNNNNNNN
+    \else:
+      \exp_after:wN \@@_collect_end:wn
+    \fi:
+    #1 ;
+  }
+\cs_new:Npn \@@_collect_loop:wnNNNNNNN #1; #2 #3#4#5#6#7#8#9
+  {
+    \exp_after:wN \@@_collect_loop:wn
+    \int_use:N \__int_eval:w #1 - \c_seven ;
+    { #2 #3#4#5#6#7#8#9 }
+  }
+\cs_new:Npn \@@_collect_end:wn #1 ;
+  {
+    \exp_after:wN \@@_collect_end:nnnnnnnnw
+    \if_case:w \if_int_compare:w #1 > \c_zero #1 \else: 0 \fi: \exp_stop_f:
+    \or: \or: \or: \or: \or: \or: \fi:
+  }
+\cs_new:Npn \@@_collect_end:nnnnnnnnw #1#2#3#4#5#6#7#8 #9 \q_stop
+  { #1#2#3#4#5#6#7#8 }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Counting characters}
+%
+% \begin{macro}[EXP]
+%   {\str_count_spaces:N, \str_count_spaces:c, \str_count_spaces:n}
+% \begin{macro}[EXP, aux]{\@@_count_spaces_loop:w}
+%   To speed up this function, we grab and discard $9$ space-delimited
+%   arguments in each iteration of the loop.  The loop stops when the
+%   last argument is one of the trailing |X|\meta{number}, and that
+%   \meta{number} is added to the sum of $9$ that precedes, to adjust
+%   the result.
+%    \begin{macrocode}
+\cs_new_nopar:Npn \str_count_spaces:N
+  { \exp_args:No \str_count_spaces:n }
+\cs_generate_variant:Nn \str_count_spaces:N { c }
+\cs_new:Npn \str_count_spaces:n #1
+  {
+    \int_eval:n
+      {
+        \exp_after:wN \@@_count_spaces_loop:w
+        \tl_to_str:n {#1} ~
+        X 7 ~ X 6 ~ X 5 ~ X 4 ~ X 3 ~ X 2 ~ X 1 ~ X 0 ~ X -1 ~
+        \q_stop
+      }
+  }
+\cs_new:Npn \@@_count_spaces_loop:w #1~#2~#3~#4~#5~#6~#7~#8~#9~
+  {
+    \if_meaning:w X #9
+      \use_i_delimit_by_q_stop:nw
+    \fi:
+    \c_nine + \@@_count_spaces_loop:w
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]
+%   {\str_count:N, \str_count:c, \str_count:n, \str_count_ignore_spaces:n}
+% \begin{macro}[EXP, int]{\@@_count:n}
+% \begin{macro}[EXP, aux]{\@@_count_aux:n, \@@_count_loop:NNNNNNNNN}
+%   To count characters in a string we could first escape all spaces
+%   using \cs{@@_to_other:n}, then pass the result to \cs{tl_count:n}.
+%   However, the escaping step would be quadratic in the number of
+%   characters in the string, and we can do better.  Namely, sum the
+%   number of spaces (\cs{str_count_spaces:n}) and the result of
+%   \cs{tl_count:n}, which ignores spaces.  Since strings tend to be
+%   longer than token lists, we use specialized functions to count
+%   characters ignoring spaces.  Namely, loop, grabbing $9$ non-space
+%   characters at each step, and end as soon as we reach one of the $9$
+%   trailing items.  The internal function \cs{@@_count:n}, used in
+%   \cs{str_item:nn} and \cs{str_range:nnn}, is similar to
+%   \cs{str_count_ignore_spaces:n} but expects its argument to already
+%   be a string or a string with spaces escaped.
+%    \begin{macrocode}
+\cs_new_nopar:Npn \str_count:N { \exp_args:No \str_count:n }
+\cs_generate_variant:Nn \str_count:N { c }
+\cs_new:Npn \str_count:n #1
+  {
+    \@@_count_aux:n
+      {
+        \str_count_spaces:n {#1}
+        + \exp_after:wN \@@_count_loop:NNNNNNNNN \tl_to_str:n {#1}
+      }
+  }
+\cs_new:Npn \@@_count:n #1
+  {
+    \@@_count_aux:n
+      { \@@_count_loop:NNNNNNNNN #1 }
+  }
+\cs_new:Npn \str_count_ignore_spaces:n #1
+  {
+    \@@_count_aux:n
+      { \exp_after:wN \@@_count_loop:NNNNNNNNN \tl_to_str:n {#1} }
+  }
+\cs_new:Npn \@@_count_aux:n #1
+  {
+    \int_eval:n
+      {
+        #1
+        { X \c_eight } { X \c_seven } { X \c_six   }
+        { X \c_five  } { X \c_four  } { X \c_three }
+        { X \c_two   } { X \c_one   } { X \c_zero  }
+        \q_stop
+      }
+  }
+\cs_new:Npn \@@_count_loop:NNNNNNNNN #1#2#3#4#5#6#7#8#9
+  {
+    \if_meaning:w X #9
+      \exp_after:wN \use_none_delimit_by_q_stop:w
+    \fi:
+    \c_nine + \@@_count_loop:NNNNNNNNN
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{The first character in a string}
+%
+% \begin{macro}[EXP]
+%   {\str_head:N, \str_head:c, \str_head:n, \str_head_ignore_spaces:n}
+% \begin{macro}[EXP, aux]{\@@_head:w}
+%   The \texttt{_ignore_spaces} variant applies \cs{tl_to_str:n} then
+%   grabs the first item, thus skipping spaces.
+%   As usual, \cs{str_head:N} expands its argument and
+%   hands it to \cs{str_head:n}.  To circumvent the fact that \TeX{}
+%   skips spaces when grabbing undelimited macro parameters,
+%   \cs{@@_head:w} takes an argument delimited by a space. If |#1|
+%   starts with a non-space character, \cs{use_i_delimit_by_q_stop:nw}
+%   leaves that in the input stream. On the other hand, if |#1| starts
+%   with a space, the \cs{@@_head:w} takes an empty argument, and the
+%   single (initially braced) space in the definition of \cs{@@_head:w}
+%   makes its way to the output. Finally, for an empty argument, the
+%   (braced) empty brace group in the definition of \cs{str_head:n}
+%   gives an empty result after passing through
+%   \cs{use_i_delimit_by_q_stop:nw}.
+%    \begin{macrocode}
+\cs_new_nopar:Npn \str_head:N { \exp_args:No \str_head:n }
+\cs_generate_variant:Nn \str_head:N { c }
+\cs_set:Npn \str_head:n #1
+  {
+    \exp_after:wN \@@_head:w
+    \tl_to_str:n {#1}
+    { { } } ~ \q_stop
+  }
+\cs_set:Npn \@@_head:w #1 ~ %
+  { \use_i_delimit_by_q_stop:nw #1 { ~ } }
+\cs_new:Npn \str_head_ignore_spaces:n #1
+  {
+    \exp_after:wN \use_i_delimit_by_q_stop:nw
+    \tl_to_str:n {#1} { } \q_stop
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]
+%   {\str_tail:N, \str_tail:c, \str_tail:n, \str_tail_ignore_spaces:n}
+% \begin{macro}[EXP, aux]{\@@_tail_auxi:w, \@@_tail_auxii:w}
+%   Getting the tail is a little bit more convoluted than the head of a
+%   string.  We hit the front of the string with \cs{reverse_if:N}
+%   \cs{if_charcode:w} \cs{scan_stop:}.  This removes the first
+%   character, and necessarily makes the test true, since the character
+%   cannot match \cs{scan_stop:}. The auxiliary function then inserts
+%   the required \cs{fi:} to close the conditional, and leaves the tail
+%   of the string in the input stream.  The details are such that an
+%   empty string has an empty tail (this requires in particular that the
+%   end-marker |X| be unexpandable and not a control sequence).  The
+%   \texttt{_ignore_spaces} is rather simpler: after converting the
+%   input to a string, \cs{@@_tail_auxii:w} removes one undelimited
+%   argument and leaves everything else until an end-marker \cs{q_mark}.
+%   One can check that an empty (or blank) string yields an empty
+%   tail.
+%    \begin{macrocode}
+\cs_new_nopar:Npn \str_tail:N { \exp_args:No \str_tail:n }
+\cs_generate_variant:Nn \str_tail:N { c }
+\cs_set:Npn \str_tail:n #1
+  {
+    \exp_after:wN \@@_tail_auxi:w
+    \reverse_if:N \if_charcode:w
+        \scan_stop: \tl_to_str:n {#1} X X \q_stop
+  }
+\cs_set:Npn \@@_tail_auxi:w #1 X #2 \q_stop { \fi: #1 }
+\cs_new:Npn \str_tail_ignore_spaces:n #1
+  {
+    \exp_after:wN \@@_tail_auxii:w
+    \tl_to_str:n {#1} \q_mark \q_mark \q_stop
+  }
+\cs_new:Npn \@@_tail_auxii:w #1 #2 \q_mark #3 \q_stop { #2 }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
 % \subsection{String manipulation}
 %
 % \begin{macro}[EXP]
@@ -558,12 +1388,12 @@
 %     \str_lower_case:n, \str_lower_case:f,
 %     \str_upper_case:n, \str_upper_case:f
 %   }
-% \begin{macro}[aux, EXP]{\__str_change_case:nn}
-% \begin{macro}[aux, EXP]{\__str_change_case_aux:nn}
-% \begin{macro}[aux, EXP]{\__str_change_case_loop:nw}
-% \begin{macro}[aux, EXP]{\__str_change_case_space:n}
-% \begin{macro}[aux, EXP]{\__str_change_case_char:nN}
-% \begin{macro}[aux, EXP]{\__str_change_case_char:NNNNNNNNn}
+% \begin{macro}[aux, EXP]{\@@_change_case:nn}
+% \begin{macro}[aux, EXP]{\@@_change_case_aux:nn}
+% \begin{macro}[aux, EXP]{\@@_change_case_loop:nw}
+% \begin{macro}[aux, EXP]{\@@_change_case_space:n}
+% \begin{macro}[aux, EXP]{\@@_change_case_char:nN}
+% \begin{macro}[aux, EXP]{\@@_change_case_char:NNNNNNNNn}
 %   Case changing for programmatic reasons is done by first detokenizing
 %   input then doing a simple loop that only has to worry about spaces
 %   and everything else. The output is detokenized to allow data sharing
@@ -571,45 +1401,45 @@
 %   data files based on the character code of the current character is
 %   shared with the text case changer too.
 %    \begin{macrocode}
-\cs_new:Npn \str_fold_case:n #1 { \__str_change_case:nn {#1} { fold } }
-\cs_new:Npn \str_lower_case:n #1 { \__str_change_case:nn {#1} { lower } }
-\cs_new:Npn \str_upper_case:n #1 { \__str_change_case:nn {#1} { upper } }
+\cs_new:Npn \str_fold_case:n #1 { \@@_change_case:nn {#1} { fold } }
+\cs_new:Npn \str_lower_case:n #1 { \@@_change_case:nn {#1} { lower } }
+\cs_new:Npn \str_upper_case:n #1 { \@@_change_case:nn {#1} { upper } }
 \cs_generate_variant:Nn \str_fold_case:n  { V }
 \cs_generate_variant:Nn \str_lower_case:n { f }
 \cs_generate_variant:Nn \str_upper_case:n { f }
-\cs_new:Npn \__str_change_case:nn #1
+\cs_new:Npn \@@_change_case:nn #1
   {
-    \exp_after:wN \__str_change_case_aux:nn \exp_after:wN
+    \exp_after:wN \@@_change_case_aux:nn \exp_after:wN
       { \tl_to_str:n {#1} }
   }
-\cs_new:Npn \__str_change_case_aux:nn #1#2
+\cs_new:Npn \@@_change_case_aux:nn #1#2
   {
-    \__str_change_case_loop:nw {#2} #1 \q_recursion_tail \q_recursion_stop
+    \@@_change_case_loop:nw {#2} #1 \q_recursion_tail \q_recursion_stop
   }
-\cs_new:Npn \__str_change_case_loop:nw #1#2 \q_recursion_stop
+\cs_new:Npn \@@_change_case_loop:nw #1#2 \q_recursion_stop
   {
     \tl_if_head_is_space:nTF {#2}
-      { \__str_change_case_space:n }
-      { \__str_change_case_char:nN }
+      { \@@_change_case_space:n }
+      { \@@_change_case_char:nN }
     {#1} #2 \q_recursion_stop
   }
 \use:x
-  { \cs_new:Npn \exp_not:N \__str_change_case_space:n ##1 \c_space_tl }
+  { \cs_new:Npn \exp_not:N \@@_change_case_space:n ##1 \c_space_tl }
   {
     \c_space_tl
-    \__str_change_case_loop:nw {#1}
+    \@@_change_case_loop:nw {#1}
   }
-\cs_new:Npn \__str_change_case_char:nN #1#2
+\cs_new:Npn \@@_change_case_char:nN #1#2
   {
     \quark_if_recursion_tail_stop:N #2
     \exp_args:Nf \tl_to_str:n
       {
-        \exp_after:wN \__str_change_case_char:NNNNNNNNn
+        \exp_after:wN \@@_change_case_char:NNNNNNNNn
           \int_use:N \__int_eval:w 1000000 + `#2 \__int_eval_end: #2 {#1}
       }
-    \__str_change_case_loop:nw {#1}
+    \@@_change_case_loop:nw {#1}
   }
-\cs_new:Npn \__str_change_case_char:NNNNNNNNn #1#2#3#4#5#6#7#8#9
+\cs_new:Npn \@@_change_case_char:NNNNNNNNn #1#2#3#4#5#6#7#8#9
   {
     \str_case:nvF #8
       { c__unicode_ #9 _ #6 _X_ #7 _tl }
@@ -624,10 +1454,64 @@
 % \end{macro}
 % \end{macro}
 %
+% \begin{variable}
+%   {
+%     \c_ampersand_str,
+%     \c_atsign_str,
+%     \c_backslash_str,
+%     \c_left_brace_str,
+%     \c_right_brace_str,
+%     \c_circumflex_str,
+%     \c_colon_str,
+%     \c_dollar_str,
+%     \c_hash_str,
+%     \c_percent_str,
+%     \c_tilde_str,
+%     \c_underscore_str
+%   }
+%   For all of those strings, use \cs{cs_to_str:N} to get characters with
+%   the correct category code without worries
+%    \begin{macrocode}
+\str_const:Nx \c_ampersand_str   { \cs_to_str:N \& }
+\str_const:Nx \c_atsign_str      { \cs_to_str:N \@ }
+\str_const:Nx \c_backslash_str   { \cs_to_str:N \\ }
+\str_const:Nx \c_left_brace_str  { \cs_to_str:N \{ }
+\str_const:Nx \c_right_brace_str { \cs_to_str:N \} }
+\str_const:Nx \c_circumflex_str  { \cs_to_str:N \^ }
+\str_const:Nx \c_colon_str       { \cs_to_str:N \: }
+\str_const:Nx \c_dollar_str      { \cs_to_str:N \$ }
+\str_const:Nx \c_hash_str        { \cs_to_str:N \# }
+\str_const:Nx \c_percent_str     { \cs_to_str:N \% }
+\str_const:Nx \c_tilde_str       { \cs_to_str:N \~ }
+\str_const:Nx \c_underscore_str  { \cs_to_str:N \_ }
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_tmpa_str, \l_tmpb_str, \g_tmpa_str, \g_tmpb_str}
+%   Scratch strings.
+%    \begin{macrocode}
+\str_new:N \l_tmpa_str
+\str_new:N \l_tmpb_str
+\str_new:N \g_tmpa_str
+\str_new:N \g_tmpb_str
+%    \end{macrocode}
+% \end{variable}
+%
+% \subsection{Viewing strings}
+%
+% \begin{macro}{\str_show:n, \str_show:N, \str_show:c}
+%   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_generate_variant:Nn \str_show:N { c }
+%    \end{macrocode}
+% \end{macro}
+%
 %    \begin{macrocode}
 %</initex|package>
 %    \end{macrocode}
 %
 % \end{implementation}
 %
-% \PrintIndex
+% \PrintIndex
\ No newline at end of file
-- 
cgit v1.2.3

