diff options
author | Norbert Preining <norbert@preining.info> | 2022-01-13 03:01:10 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2022-01-13 03:01:10 +0000 |
commit | ee3f9ca89b21a2174f7e427d9d48247b1912e130 (patch) | |
tree | f5d80e1343bcf96a0e16a7d4412526b8182b7525 /macros/latex/contrib/l3kernel/l3regex.dtx | |
parent | 1f69e7b43c51a713b615b5ce4ed8adbcdaf9080e (diff) |
CTAN sync 202201130301
Diffstat (limited to 'macros/latex/contrib/l3kernel/l3regex.dtx')
-rw-r--r-- | macros/latex/contrib/l3kernel/l3regex.dtx | 479 |
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~ |