From 472e2dc119ba3eca6a8672e7606df6a7dc847a19 Mon Sep 17 00:00:00 2001 From: Karl Berry 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|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> @@ -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} % % \end{macrocode} % % \end{implementation} % -% \PrintIndex +% \PrintIndex \ No newline at end of file -- cgit v1.2.3