summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/l3kernel/l3regex.dtx
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2022-01-13 03:01:10 +0000
committerNorbert Preining <norbert@preining.info>2022-01-13 03:01:10 +0000
commitee3f9ca89b21a2174f7e427d9d48247b1912e130 (patch)
treef5d80e1343bcf96a0e16a7d4412526b8182b7525 /macros/latex/contrib/l3kernel/l3regex.dtx
parent1f69e7b43c51a713b615b5ce4ed8adbcdaf9080e (diff)
CTAN sync 202201130301
Diffstat (limited to 'macros/latex/contrib/l3kernel/l3regex.dtx')
-rw-r--r--macros/latex/contrib/l3kernel/l3regex.dtx479
1 files changed, 432 insertions, 47 deletions
diff --git a/macros/latex/contrib/l3kernel/l3regex.dtx b/macros/latex/contrib/l3kernel/l3regex.dtx
index dd95825c87..cab4d738bf 100644
--- a/macros/latex/contrib/l3kernel/l3regex.dtx
+++ b/macros/latex/contrib/l3kernel/l3regex.dtx
@@ -2,7 +2,7 @@
%
%% File: l3regex.dtx
%
-% Copyright (C) 2011-2021 The LaTeX Project
+% Copyright (C) 2011-2022 The LaTeX Project
%
% It may be distributed and/or modified under the conditions of the
% LaTeX Project Public License (LPPL), either version 1.3c of this
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2021-11-22}
+% \date{Released 2022-01-12}
%
% \maketitle
%
@@ -99,7 +99,7 @@
%
% \section{Syntax of regular expressions}
%
-% \subsection{Regex examples}
+% \subsection{Regular expression examples}
%
% We start with a few examples, and encourage the reader to apply
% \cs{regex_show:n} to these regular expressions.
@@ -620,6 +620,38 @@
% results in \cs[no-index]{l_foo_int} taking the value $5$.
% \end{function}
%
+% \begin{function}[noTF, added = 2022-01-10]{\regex_match_case:nn}
+% \begin{syntax}
+% \cs{regex_match_case:nnTF}
+% ~~|{| \\
+% ~~~~\Arg{regex_1} \Arg{code case_1} \\
+% ~~~~\Arg{regex_2} \Arg{code case_2} \\
+% ~~~~\ldots \\
+% ~~~~\Arg{regex_n} \Arg{code case_n} \\
+% ~~|}| \Arg{token list}
+% ~~\Arg{true code} \Arg{false code}
+% \end{syntax}
+% Determines which of the \meta{regular expressions} matches at the
+% earliest point in the \meta{token list}, and leaves the
+% corresponding \meta{code_i} followed by the \meta{true code} in the
+% input stream. If several \meta{regex} match starting at the same
+% point, then the first one in the list is selected and the others are
+% discarded. If none of the \meta{regex} match, the \meta{false code}
+% is left in the input stream. Each \meta{regex} can either be given
+% as a regex variable or as an explicit regular expression.
+%
+% In detail, for each starting position in the \meta{token list}, each
+% of the \meta{regex} is searched in turn. If one of them matches
+% then the corresponding \meta{code} is used and everything else is
+% discarded, while if none of the \meta{regex} match at a given
+% position then the next starting position is attempted. If none of
+% the \meta{regex} match anywhere in the \meta{token list} then
+% nothing is left in the input stream. Note that this differs from
+% nested \cs{regex_match:nnTF} statements since all \meta{regex} are
+% attempted at each position rather than attempting to match
+% \meta{regex_1} at every position before moving on to \meta{regex_2}.
+% \end{function}
+%
% \section{Submatch extraction}
%
% \begin{function}[noTF, added = 2017-05-26]
@@ -734,6 +766,74 @@
% locally to \meta{tl~var}.
% \end{function}
%
+% \begin{function}[noTF, added = 2022-01-10]{\regex_replace_case_once:nN}
+% \begin{syntax}
+% \cs{regex_replace_case_once:nNTF}
+% ~~|{| \\
+% ~~~~\Arg{regex_1} \Arg{replacement_1} \\
+% ~~~~\Arg{regex_2} \Arg{replacement_2} \\
+% ~~~~\ldots \\
+% ~~~~\Arg{regex_n} \Arg{replacement_n} \\
+% ~~|}| \meta{tl~var}
+% ~~\Arg{true code} \Arg{false code}
+% \end{syntax}
+% Replaces the earliest match of the regular expression
+% "(?|"\meta{regex_1}"|"\dots"|"\meta{regex_n}")" in the \meta{token
+% list variable} by the \meta{replacement} corresponding to which
+% \meta{regex_i} matched, then leaves the \meta{true code} in the
+% input stream. If none of the \meta{regex} match, then the
+% \meta{tl~var} is not modified, and the \meta{false code} is left in
+% the input stream. Each \meta{regex} can either be given as a regex
+% variable or as an explicit regular expression.
+%
+% In detail, for each starting position in the \meta{token list}, each
+% of the \meta{regex} is searched in turn. If one of them matches
+% then it is replaced by the corresponding \meta{replacement} as
+% described for \cs{regex_replace_once:nnN}. This is equivalent to
+% checking with \cs{regex_match_case:nn} which \meta{regex} matches,
+% then performing the replacement with \cs{regex_replace_once:nnN}.
+% \end{function}
+%
+% \begin{function}[noTF, added = 2022-01-10]{\regex_replace_case_all:nN}
+% \begin{syntax}
+% \cs{regex_replace_case_all:nNTF}
+% ~~|{| \\
+% ~~~~\Arg{regex_1} \Arg{replacement_1} \\
+% ~~~~\Arg{regex_2} \Arg{replacement_2} \\
+% ~~~~\ldots \\
+% ~~~~\Arg{regex_n} \Arg{replacement_n} \\
+% ~~|}| \meta{tl~var}
+% ~~\Arg{true code} \Arg{false code}
+% \end{syntax}
+% Replaces all occurrences of all \meta{regex} in the \meta{token
+% list} by the corresponding \meta{replacement}. Every match is
+% treated independently, and matches cannot overlap. The result is
+% assigned locally to \meta{tl~var}, and the \meta{true code} or
+% \meta{false code} is left in the input stream depending on whether
+% any replacement was made or not.
+%
+% In detail, for each starting position in the \meta{token list}, each
+% of the \meta{regex} is searched in turn. If one of them matches
+% then it is replaced by the corresponding \meta{replacement}, and the
+% search resumes at the position that follows this match (and
+% replacement). For instance
+% \begin{verbatim}
+% \tl_set:Nn \l_tmpa_tl { Hello,~world! }
+% \regex_replace_case_all:nN
+% {
+% { [A-Za-z]+ } { ``\0'' }
+% { \b } { --- }
+% { . } { [\0] }
+% } \l_tmpa_tl
+% \end{verbatim}
+% results in \cs{l_tmpa_tl} having the contents
+% \verb*|``Hello''---[,][ ]``world''---[!]|. Note in particular that
+% the word-boundary assertion |\b| did not match at the start of words
+% because the case |[A-Za-z]+| matched at these positions. To change
+% this, one could simply swap the order of the two cases in the
+% argument of \cs{regex_replace_case_all:nN}.
+% \end{function}
+%
% \section{Scratch regular expressions}
%
% \begin{variable}[added = 2017-12-11]{\l_tmpa_regex, \l_tmpb_regex}
@@ -786,7 +886,7 @@
% \texttt{curr_state} and \texttt{curr_submatches}.
% \item If possible, when a state is reused by the same thread, kill
% other subthreads.
-% \item Use an array rather than \cs[no-index]{l__regex_balance_tl}
+% \item Use an array rather than \cs[no-index]{g__regex_balance_tl}
% to build the function \cs[no-index]{__regex_replacement_balance_one_match:n}.
% \item Reduce the number of epsilon-transitions in alternatives.
% \item Optimize simple strings: use less states (|abcade| should give
@@ -1100,6 +1200,26 @@
% \end{macrocode}
% \end{macro}
%
+% \begin{macro}{\@@_tl_odd_items:n, \@@_tl_even_items:n, \@@_tl_even_items_loop:nn}
+% Map through a token list one pair at a time, leaving the
+% odd-numbered or even-numbered items (the first item is
+% numbered~$1$).
+% \begin{macrocode}
+\cs_new:Npn \@@_tl_odd_items:n #1 { \@@_tl_even_items:n { ? #1 } }
+\cs_new:Npn \@@_tl_even_items:n #1
+ {
+ \@@_tl_even_items_loop:nn #1 \q_@@_nil \q_@@_nil
+ \prg_break_point:
+ }
+\cs_new:Npn \@@_tl_even_items_loop:nn #1#2
+ {
+ \@@_use_none_delimit_by_q_nil:w #2 \prg_break: \q_@@_nil
+ { \exp_not:n {#2} }
+ \@@_tl_even_items_loop:nn
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \subsubsection{Constants and variables}
%
% \begin{macro}{\@@_tmp:w}
@@ -1186,9 +1306,17 @@
% \end{macrocode}
% \end{variable}
%
+% \begin{variable}{\q_@@_nil}
+% Internal quarks.
+% \begin{macrocode}
+\quark_new:N \q_@@_nil
+% \end{macrocode}
+% \end{variable}
+%
% \begin{macro}[EXP]{
% \@@_use_none_delimit_by_q_recursion_stop:w,
-% \@@_use_i_delimit_by_q_recursion_stop:nw
+% \@@_use_i_delimit_by_q_recursion_stop:nw,
+% \@@_use_none_delimit_by_q_nil:w,
% }
% Functions to gobble up to a quark.
% \begin{macrocode}
@@ -1196,16 +1324,10 @@
#1 \q_@@_recursion_stop { }
\cs_new:Npn \@@_use_i_delimit_by_q_recursion_stop:nw
#1 #2 \q_@@_recursion_stop {#1}
+\cs_new:Npn \@@_use_none_delimit_by_q_nil:w #1 \q_@@_nil { }
% \end{macrocode}
% \end{macro}
%
-% \begin{variable}{\q_@@_nil}
-% Internal quarks.
-% \begin{macrocode}
-\quark_new:N \q_@@_nil
-% \end{macrocode}
-% \end{variable}
-%
% \begin{macro}[pTF]{\@@_quark_if_nil:n}
% Branching quark conditional.
% \begin{macrocode}
@@ -2343,6 +2465,29 @@
% \end{macrocode}
% \end{macro}
%
+% \begin{macro}{\@@_compile_use:n}
+% Use a regex, regardless of whether it is given as a string (in which
+% case we need to compile) or as a regex variable. This is used for
+% \cs{regex_match_case:nn} and related functions to allow a mixture of
+% explicit regex and regex variables.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_compile_use:n #1
+ {
+ \tl_if_single_token:nT {#1}
+ {
+ \exp_after:wN \@@_compile_use_aux:w
+ \token_to_meaning:N #1 ~ \q_@@_nil
+ }
+ \@@_compile:n {#1} \l_@@_internal_regex
+ }
+\cs_new_protected:Npn \@@_compile_use_aux:w #1 ~ #2 \q_@@_nil
+ {
+ \str_if_eq:nnT { #1 ~ } { macro:->\@@_branch:n }
+ { \use_ii:nnn }
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \begin{macro}{\@@_compile_escaped:N, \@@_compile_special:N}
% If the special character or escaped alphanumeric has a particular
% meaning in regexes, the corresponding function is used. Otherwise,
@@ -4154,6 +4299,80 @@
% \end{macrocode}
% \end{macro}
%
+% \begin{variable}{\g_@@_case_int}
+% Case number that was successfully matched in
+% \cs{regex_match_case:nn} and related functions.
+% \begin{macrocode}
+\int_new:N \g_@@_case_int
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_case_max_group_int}
+% The largest group number appearing in any of the \meta{regex} in the
+% argument of \cs{regex_match_case:nn} and related functions.
+% \begin{macrocode}
+\int_new:N \l_@@_case_max_group_int
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_case_build:n, \@@_case_build:x, \@@_case_build_aux:Nn, \@@_case_build_loop:n}
+% See \cs{@@_build:n}, but with a loop.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_case_build:n #1
+ {
+ \@@_case_build_aux:Nn \c_true_bool {#1}
+ \int_gzero:N \g_@@_case_int
+ }
+\cs_generate_variant:Nn \@@_case_build:n { x }
+\cs_new_protected:Npn \@@_case_build_aux:Nn #1#2
+ {
+ \@@_standard_escapechar:
+ \int_set_eq:NN \l_@@_max_state_int \l_@@_min_state_int
+ \@@_build_new_state:
+ \@@_build_new_state:
+ \@@_toks_put_right:Nn \l_@@_left_state_int
+ { \@@_action_start_wildcard:N #1 }
+ %
+ \@@_build_new_state:
+ \@@_toks_put_left:Nx \l_@@_left_state_int
+ { \@@_action_submatch:nN { 0 } < }
+ \@@_push_lr_states:
+ \int_zero:N \l_@@_case_max_group_int
+ \int_gzero:N \g_@@_case_int
+ \tl_map_inline:nn {#2}
+ {
+ \int_gincr:N \g_@@_case_int
+ \@@_case_build_loop:n {##1}
+ }
+ \int_set_eq:NN \l_@@_capturing_group_int \l_@@_case_max_group_int
+ \@@_pop_lr_states:
+ }
+\cs_new_protected:Npn \@@_case_build_loop:n #1
+ {
+ \int_set:Nn \l_@@_capturing_group_int { 1 }
+ \@@_compile_use:n {#1}
+ \int_set:Nn \l_@@_case_max_group_int
+ {
+ \int_max:nn { \l_@@_case_max_group_int }
+ { \l_@@_capturing_group_int }
+ }
+ \seq_pop:NN \l_@@_right_state_seq \l_@@_internal_a_tl
+ \int_set:Nn \l_@@_right_state_int \l_@@_internal_a_tl
+ \@@_toks_put_left:Nx \l_@@_right_state_int
+ {
+ \@@_action_submatch:nN { 0 } >
+ \int_gset:Nn \g_@@_case_int
+ { \int_use:N \g_@@_case_int }
+ \@@_action_success:
+ }
+ \@@_toks_clear:N \l_@@_max_state_int
+ \seq_push:No \l_@@_right_state_seq
+ { \int_use:N \l_@@_max_state_int }
+ \int_incr:N \l_@@_max_state_int
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \begin{macro}{\@@_build_for_cs:n}
% The matching code relies on some global intarray variables, but only
% uses a range of their entries. Specifically,
@@ -4495,7 +4714,7 @@
% the group, and leaves \texttt{internal_a} pointing to the left end
% of the last repetition. We only record the submatch information at
% the last repetition. Finally, add a state at the end (the transition
-% to it has been taken care of by the replicating auxiliary.
+% to it has been taken care of by the replicating auxiliary).
% \begin{macrocode}
\cs_new_protected:Npn \@@_group_repeat:nn #1#2
{
@@ -5425,12 +5644,12 @@
% \end{macrocode}
% \end{variable}
%
-% \begin{variable}{\l_@@_balance_tl}
+% \begin{variable}{\g_@@_balance_tl}
% This token list holds the replacement text for
% \cs{@@_replacement_balance_one_match:n} while it is being built
% incrementally.
% \begin{macrocode}
-\tl_new:N \l_@@_balance_tl
+\tl_new:N \g_@@_balance_tl
% \end{macrocode}
% \end{variable}
%
@@ -5573,11 +5792,11 @@
%
% \subsubsection{Framework}
%
-% \begin{macro}{\@@_replacement:n}
-% \begin{macro}{\@@_replacement_aux:n}
+% \begin{macro}{\@@_replacement:n, \@@_replacement:x}
+% \begin{macro}{\@@_replacement_apply:Nn, \@@_replacement_set:n}
% The replacement text is built incrementally. We keep track in
% \cs{l_@@_balance_int} of the balance of explicit begin- and
-% end-group tokens and we store in \cs{l_@@_balance_tl} some
+% end-group tokens and we store in \cs{g_@@_balance_tl} some
% code to compute the brace balance from submatches (see its
% description). Detect unescaped right braces, and escaped characters,
% with trailing \cs{prg_do_nothing:} because some of the later
@@ -5585,12 +5804,14 @@
% parsed, make sure that there is no open csname. Finally, define the
% \texttt{balance_one_match} and \texttt{do_one_match} functions.
% \begin{macrocode}
-\cs_new_protected:Npn \@@_replacement:n #1
+\cs_new_protected:Npn \@@_replacement:n
+ { \@@_replacement_apply:Nn \@@_replacement_set:n }
+\cs_new_protected:Npn \@@_replacement_apply:Nn #1#2
{
\group_begin:
\tl_build_begin:N \l_@@_build_tl
\int_zero:N \l_@@_balance_int
- \tl_clear:N \l_@@_balance_tl
+ \tl_gclear:N \g_@@_balance_tl
\@@_escape_use:nnnn
{
\if_charcode:w \c_right_brace_str ##1
@@ -5606,7 +5827,7 @@
}
{ \@@_replacement_escaped:N ##1 }
{ \@@_replacement_normal:n ##1 }
- {#1}
+ {#2}
\prg_do_nothing: \prg_do_nothing:
\if_int_compare:w \l_@@_replacement_csnames_int > \c_zero_int
\msg_error:nnx { regex } { replacement-missing-rbrace }
@@ -5620,18 +5841,15 @@
{ \seq_count:N \l_@@_replacement_category_seq }
\seq_clear:N \l_@@_replacement_category_seq
}
- \cs_gset:Npx \@@_replacement_balance_one_match:n ##1
- {
- + \int_use:N \l_@@_balance_int
- \l_@@_balance_tl
- - \@@_submatch_balance:n {##1}
- }
+ \tl_gput_right:Nx \g_@@_balance_tl
+ { + \int_use:N \l_@@_balance_int }
\tl_build_end:N \l_@@_build_tl
\exp_args:NNo
\group_end:
- \@@_replacement_aux:n \l_@@_build_tl
+ #1 \l_@@_build_tl
}
-\cs_new_protected:Npn \@@_replacement_aux:n #1
+\cs_generate_variant:Nn \@@_replacement:n { x }
+\cs_new_protected:Npn \@@_replacement_set:n #1
{
\cs_set:Npn \@@_replacement_do_one_match:n ##1
{
@@ -5646,11 +5864,47 @@
}
#1
}
+ \exp_args:Nno \use:n
+ { \cs_gset:Npn \@@_replacement_balance_one_match:n ##1 }
+ {
+ \g_@@_balance_tl
+ - \@@_submatch_balance:n {##1}
+ }
}
% \end{macrocode}
% \end{macro}
% \end{macro}
%
+% \begin{macro}{\@@_case_replacement:n, \@@_case_replacement:x}
+% \begin{macrocode}
+\tl_new:N \g_@@_case_replacement_tl
+\tl_new:N \g_@@_case_balance_tl
+\cs_new_protected:Npn \@@_case_replacement:n #1
+ {
+ \tl_gset:Nn \g_@@_case_balance_tl
+ {
+ \if_case:w
+ \__kernel_intarray_item:Nn
+ \g_@@_submatch_case_intarray {##1}
+ }
+ \tl_gset_eq:NN \g_@@_case_replacement_tl \g_@@_case_balance_tl
+ \tl_map_tokens:nn {#1}
+ { \@@_replacement_apply:Nn \@@_case_replacement_aux:n }
+ \tl_gset:No \g_@@_balance_tl
+ { \g_@@_case_balance_tl \fi: }
+ \exp_args:No \@@_replacement_set:n
+ { \g_@@_case_replacement_tl \fi: }
+ }
+\cs_generate_variant:Nn \@@_case_replacement:n { x }
+\cs_new_protected:Npn \@@_case_replacement_aux:n #1
+ {
+ \tl_gput_right:Nn \g_@@_case_replacement_tl { \or: #1 }
+ \tl_gput_right:No \g_@@_case_balance_tl
+ { \exp_after:wN \or: \g_@@_balance_tl }
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \begin{macro}{\@@_replacement_put:n}
% This gets redefined for \cs{peek_regex_replace_once:nnTF}.
% \begin{macrocode}
@@ -5751,7 +6005,7 @@
% construction, it must be taken into account in the brace balance.
% Later on, |##1| will be replaced by a pointer to the $0$-th submatch for a
% given match. There is an \cs{exp_not:N} here as at the point-of-use
-% of \cs{l_@@_balance_tl} there is an \texttt{x}-type expansion which is needed
+% of \cs{g_@@_balance_tl} there is an \texttt{x}-type expansion which is needed
% to get |##1| in correctly.
% \begin{macrocode}
\cs_new_protected:Npn \@@_replacement_put_submatch:n #1
@@ -5768,11 +6022,8 @@
\tl_build_put_right:Nn \l_@@_build_tl
{ \@@_query_submatch:n { \int_eval:n { #1 + ##1 } } }
\if_int_compare:w \l_@@_replacement_csnames_int = \c_zero_int
- \tl_put_right:Nn \l_@@_balance_tl
- {
- + \@@_submatch_balance:n
- { \exp_not:N \int_eval:n { #1 + ##1 } }
- }
+ \tl_gput_right:Nn \g_@@_balance_tl
+ { + \@@_submatch_balance:n { \int_eval:n { #1 + ##1 } } }
\fi:
}
% \end{macrocode}
@@ -6269,6 +6520,29 @@
% \end{macrocode}
% \end{macro}
%
+% \begin{macro}[noTF]{\regex_match_case:nn}
+% The auxiliary errors if |#1| has an odd number of items, and
+% otherwise it sets \cs{g_@@_case_int} according to which case was
+% found (zero if not found). The \texttt{true} branch leaves the
+% corresponding code in the input stream.
+% \begin{macrocode}
+\cs_new_protected:Npn \regex_match_case:nnTF #1#2#3
+ {
+ \@@_match_case:nnTF {#1} {#2}
+ {
+ \tl_item:nn {#1} { 2 * \g_@@_case_int }
+ #3
+ }
+ }
+\cs_new_protected:Npn \regex_match_case:nn #1#2
+ { \regex_match_case:nnTF {#1} {#2} { } { } }
+\cs_new_protected:Npn \regex_match_case:nnT #1#2#3
+ { \regex_match_case:nnTF {#1} {#2} {#3} { } }
+\cs_new_protected:Npn \regex_match_case:nnF #1#2
+ { \regex_match_case:nnTF {#1} {#2} { } }
+% \end{macrocode}
+% \end{macro}
+%
% \begin{macro}[noTF]
% {
% \regex_extract_once:nnN, \regex_extract_once:NnN,
@@ -6306,6 +6580,72 @@
% \end{macrocode}
% \end{macro}
%
+% \begin{macro}[noTF]{\regex_replace_case_once:nN}
+% If the input is bad (odd number of items) then take the false
+% branch. Otherwise, use the same auxiliary as
+% \cs{regex_replace_once:nnN}, but with more complicated code to build
+% the automaton, and to find what replacement text to use. The
+% \cs{tl_item:nn} is only expanded once we know the value of
+% \cs{g_@@_case_int}, namely which case matched.
+% \begin{macrocode}
+\cs_new_protected:Npn \regex_replace_case_once:nNTF #1#2
+ {
+ \int_if_odd:nTF { \tl_count:n {#1} }
+ {
+ \msg_error:nnxxxx { regex } { case-odd }
+ { \token_to_str:N \regex_replace_case_once:nN(TF) } { code }
+ { \tl_count:n {#1} } { \tl_to_str:n {#1} }
+ \use_ii:nn
+ }
+ {
+ \@@_replace_once_aux:nnN
+ { \@@_case_build:x { \@@_tl_odd_items:n {#1} } }
+ { \@@_replacement:x { \tl_item:nn {#1} { 2 * \g_@@_case_int } } }
+ #2
+ \bool_if:NTF \g_@@_success_bool
+ }
+ }
+\cs_new_protected:Npn \regex_replace_case_once:nN #1#2
+ { \regex_replace_case_once:nNTF {#1} {#2} { } { } }
+\cs_new_protected:Npn \regex_replace_case_once:nNT #1#2#3
+ { \regex_replace_case_once:nNTF {#1} {#2} {#3} { } }
+\cs_new_protected:Npn \regex_replace_case_once:nNF #1#2
+ { \regex_replace_case_once:nNTF {#1} {#2} { } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[noTF]{\regex_case_replace_all:nN}
+% If the input is bad (odd number of items) then take the false
+% branch. Otherwise, use the same auxiliary as
+% \cs{regex_replace_all:nnN}, but with more complicated code to build
+% the automaton, and to find what replacement text to use.
+% \begin{macrocode}
+\cs_new_protected:Npn \regex_replace_case_all:nNTF #1#2
+ {
+ \int_if_odd:nTF { \tl_count:n {#1} }
+ {
+ \msg_error:nnxxxx { regex } { case-odd }
+ { \token_to_str:N \regex_replace_case_all:nN(TF) } { code }
+ { \tl_count:n {#1} } { \tl_to_str:n {#1} }
+ \use_ii:nn
+ }
+ {
+ \@@_replace_all_aux:nnN
+ { \@@_case_build:x { \@@_tl_odd_items:n {#1} } }
+ { \@@_case_replacement:x { \@@_tl_even_items:n {#1} } }
+ #2
+ \bool_if:NTF \g_@@_success_bool
+ }
+ }
+\cs_new_protected:Npn \regex_replace_case_all:nN #1#2
+ { \regex_replace_case_all:nNTF {#1} {#2} { } { } }
+\cs_new_protected:Npn \regex_replace_case_all:nNT #1#2#3
+ { \regex_replace_case_all:nNTF {#1} {#2} {#3} { } }
+\cs_new_protected:Npn \regex_replace_case_all:nNF #1#2
+ { \regex_replace_case_all:nNTF {#1} {#2} { } }
+% \end{macrocode}
+% \end{macro}
+%
% \subsubsection{Variables and helpers for user functions}
%
% \begin{variable}{\l_@@_match_count_int}
@@ -6343,12 +6683,14 @@
% \end{macrocode}
% \end{variable}
%
-% \begin{variable}{\g_@@_submatch_prev_intarray, \g_@@_submatch_begin_intarray, \g_@@_submatch_end_intarray}
-% Hold the place where the match attempt begun and the end-points of each submatch.
+% \begin{variable}{\g_@@_submatch_prev_intarray, \g_@@_submatch_begin_intarray, \g_@@_submatch_end_intarray, \g_@@_submatch_case_intarray}
+% Hold the place where the match attempt begun, the end-points of each
+% submatch, and which regex case the match corresponds to, respectively.
% \begin{macrocode}
\intarray_new:Nn \g_@@_submatch_prev_intarray { 65536 }
\intarray_new:Nn \g_@@_submatch_begin_intarray { 65536 }
\intarray_new:Nn \g_@@_submatch_end_intarray { 65536 }
+\intarray_new:Nn \g_@@_submatch_case_intarray { 65536 }
% \end{macrocode}
% \end{variable}
%
@@ -6433,6 +6775,34 @@
% \end{macrocode}
% \end{macro}
%
+% \begin{macro}{\@@_match_case:nnTF}
+% \begin{macro}[EXP]{\@@_match_case_aux:nn}
+% The code would get badly messed up if the number of items in |#1|
+% were not even, so we catch this case, then follow the same code as
+% \cs{regex_match:nnTF} but using \cs{@@_case_build:n} and without
+% returning a result.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_match_case:nnTF #1#2
+ {
+ \int_if_odd:nTF { \tl_count:n {#1} }
+ {
+ \msg_error:nnxxxx { regex } { case-odd }
+ { \token_to_str:N \regex_match_case:nn(TF) } { code }
+ { \tl_count:n {#1} } { \tl_to_str:n {#1} }
+ \use_ii:nn
+ }
+ {
+ \@@_if_match:nn
+ { \@@_case_build:x { \@@_tl_odd_items:n {#1} } }
+ {#2}
+ \bool_if:NTF \g_@@_success_bool
+ }
+ }
+\cs_new:Npn \@@_match_case_aux:nn #1#2 { \exp_not:n { {#1} } }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
% \begin{macro}{\@@_count:nnN}
% Again, we don't care about submatches. Instead of aborting after the
% first \enquote{longest match} is found, we search for multiple
@@ -6739,10 +7109,14 @@
{
\__kernel_intarray_gset:Nnn \g_@@_submatch_prev_intarray
{ \l_@@_submatch_int } { 0 }
+ \__kernel_intarray_gset:Nnn \g_@@_submatch_case_intarray
+ { \l_@@_submatch_int } { 0 }
\int_incr:N \l_@@_submatch_int
}
\__kernel_intarray_gset:Nnn \g_@@_submatch_prev_intarray
{ \l_@@_zeroth_submatch_int } { \l_@@_start_pos_int }
+ \__kernel_intarray_gset:Nnn \g_@@_submatch_case_intarray
+ { \l_@@_zeroth_submatch_int } { \g_@@_case_int }
\int_zero:N \l_@@_internal_a_int
\exp_after:wN \@@_extract_aux:w \l_@@_success_submatches_tl
\prg_break_point: \@@_use_none_delimit_by_q_recursion_stop:w ,
@@ -6767,7 +7141,7 @@
%
% \subsubsection{Replacement}
%
-% \begin{macro}{\@@_replace_once:nnN}
+% \begin{macro}{\@@_replace_once:nnN, \@@_replace_once_aux:nnN}
% Build the \textsc{nfa} and the replacement functions, then find a
% single match. If the match failed, simply exit the
% group. Otherwise, we do the replacement. Extract submatches. Compute
@@ -6781,18 +7155,19 @@
% \texttt{x}-expansion, and checks that braces are balanced in the
% final result.
% \begin{macrocode}
-\cs_new_protected:Npn \@@_replace_once:nnN #1#2#3
+\cs_new_protected:Npn \@@_replace_once:nnN #1#2
+ { \@@_replace_once_aux:nnN {#1} { \@@_replacement:n {#2} } }
+\cs_new_protected:Npn \@@_replace_once_aux:nnN #1#2#3
{
\group_begin:
\@@_single_match:
#1
\exp_args:No \@@_match:n {#3}
- \if_meaning:w \c_false_bool \g_@@_success_bool
- \group_end:
- \else:
+ \bool_if:NTF \g_@@_success_bool
+ {
\@@_extract:
\exp_args:No \@@_query_set:n {#3}
- \@@_replacement:n {#2}
+ #2
\int_set:Nn \l_@@_balance_int
{
\@@_replacement_balance_one_match:n
@@ -6810,7 +7185,8 @@
{ \l_@@_max_pos_int }
}
\@@_group_end_replace:N #3
- \fi:
+ }
+ { \group_end: }
}
% \end{macrocode}
% \end{macro}
@@ -6827,14 +7203,16 @@
% match. Join together the replacement texts for each match (including
% the part of the query before the match), and the end of the query.
% \begin{macrocode}
-\cs_new_protected:Npn \@@_replace_all:nnN #1#2#3
+\cs_new_protected:Npn \@@_replace_all:nnN #1#2
+ { \@@_replace_all_aux:nnN {#1} { \@@_replacement:n {#2} } }
+\cs_new_protected:Npn \@@_replace_all_aux:nnN #1#2#3
{
\group_begin:
\@@_multi_match:n { \@@_extract: }
#1
\exp_args:No \@@_match:n {#3}
\exp_args:No \@@_query_set:n {#3}
- \@@_replacement:n {#2}
+ #2
\int_set:Nn \l_@@_balance_int
{
0
@@ -7617,8 +7995,15 @@
{ The~values~given~in~a~quantifier~must~be~in~order. }
% \end{macrocode}
%
-% Used when showing a regex.
+% Used in user commands, and when showing a regex.
% \begin{macrocode}
+\msg_new:nnnn { regex } { case-odd }
+ { #1~with~odd~number~of~items }
+ {
+ There~must~be~a~#2~part~for~each~regex:~
+ found~odd~number~of~items~(#3)~in\\
+ \iow_indent:n {#4}
+ }
\msg_new:nnn { regex } { show }
{
>~Compiled~regex~