summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/source/latex/l3experimental/l3str/l3regex.dtx
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2011-12-29 23:03:28 +0000
committerKarl Berry <karl@freefriends.org>2011-12-29 23:03:28 +0000
commit0bc9393ae50667d7ff769a76321a9548a70351fc (patch)
tree6da7cc8ebef423a4653c82a19be6d4e1c6652705 /Master/texmf-dist/source/latex/l3experimental/l3str/l3regex.dtx
parent56ecd0d3d0de54cebdeb29cc8a056001ee93b02e (diff)
l3experimental 3109 (29dec11)
git-svn-id: svn://tug.org/texlive/trunk@24970 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/source/latex/l3experimental/l3str/l3regex.dtx')
-rw-r--r--Master/texmf-dist/source/latex/l3experimental/l3str/l3regex.dtx2487
1 files changed, 1556 insertions, 931 deletions
diff --git a/Master/texmf-dist/source/latex/l3experimental/l3str/l3regex.dtx b/Master/texmf-dist/source/latex/l3experimental/l3str/l3regex.dtx
index 94bd054f0ce..9c276b6922d 100644
--- a/Master/texmf-dist/source/latex/l3experimental/l3str/l3regex.dtx
+++ b/Master/texmf-dist/source/latex/l3experimental/l3str/l3regex.dtx
@@ -35,7 +35,7 @@
%
%<*driver|package>
\RequirePackage{expl3}
-\GetIdInfo$Id: l3regex.dtx 3017 2011-11-28 00:10:02Z bruno $
+\GetIdInfo$Id: l3regex.dtx 3073 2011-12-21 01:53:06Z bruno $
{L3 Experimental Regular Expressions}
%</driver|package>
%<*driver>
@@ -74,36 +74,53 @@
% \section{\pkg{l3regex} documentation}
%
% The \pkg{l3regex} package provides regular expression testing,
-% extraction of submatches, splitting, and replacement, all acting on strings
-% of characters. The syntax of regular expressions is mostly a subset
-% of the PCRE syntax (and very close to POSIX). For performance
-% reasons, only a limited set of features are implemented. Notably,
-% back-references are not supported.
+% extraction of submatches, splitting, and replacement, all acting
+% on token lists. The syntax of regular expressions is mostly a subset
+% of the PCRE syntax (and very close to POSIX), with some additions
+% due to the fact that we act on lists of tokens rather than characters.
+% For performance reasons, only a limited set of features are implemented.
+% Notably, back-references are not supported.
%
% Let us give a few examples. After
% \begin{verbatim}
-% \str_set:Nn \l_my_str { That~cat. }
-% \regex_replace_once:nnN { at } { is } \l_my_str
+% \tl_set:Nn \l_my_tl { That~cat. }
+% \regex_replace_once:nnN { at } { is } \l_my_tl
% \end{verbatim}
-% the string variable \cs{l_my_str} holds the text
+% the token list variable \cs{l_my_tl} holds the text
% \enquote{\texttt{This cat.}}, where the first
% occurrence of \enquote{\texttt{at}} was replaced
% by \enquote{\texttt{is}}. A more complicated example is
% a pattern to add a comma at the end of each word:
% \begin{verbatim}
-% \regex_replace_all:nnN { \w+ } { \0 , } \l_my_str
+% \regex_replace_all:nnN { \w+ } { \0 , } \l_my_tl
% \end{verbatim}
% The |\w| sequence represents any \enquote{word} character,
% and |+| indicates that the |\w| sequence should be repeated
% as many times as possible (at least once), hence matching a word in the
-% input string. In the replacement text, |\0| denotes the full match
+% input token list. In the replacement text, |\0| denotes the full match
% (here, a word).
%
+% If a regular expression is to be used several times,
+% it can be compiled once, and stored in a token list
+% variable using \cs{regex_const:Nn}. For example,
+% \begin{verbatim}
+% \regex_const:Nn \l_foo_regex_tl { \c{begin} \cB (\CB*?) \cE }
+% \end{verbatim}
+% stores in \cs{l_foo_regex_tl} a regular expression which matches
+% the starting marker for an environment: \cs{begin}, followed
+% by a begin-group token (|\cB|), then tokens which are not
+% begin-group characters (|\CB|), as few as possible (|*?|),
+% followed by an end-group token (|\cE|). As explained in the
+% next section, the parentheses \enquote{capture} the result of
+% |\CB*?|, giving us access to the name of the environment
+% when doing replacements.
+%
% \subsection{Syntax of regular expressions}
%
-% Most characters match exactly themselves. Some characters are
+% Most characters match exactly themselves,
+% with an arbitrary category code. Some characters are
% special and must be escaped with a backslash (\emph{e.g.}, |\*|
-% matches an explicit star character). Some escape sequences of
+% matches a star character). Some escape sequences of
% the form backslash--letter also have a special meaning
% (for instance |\d| matches any digit). As a rule,
% \begin{itemize}
@@ -113,7 +130,7 @@
% |\A|, |\B|, \ldots{} have special meanings;
% \item non-alphanumeric printable ascii characters can (and should)
% always be escaped: many of them have special meanings (\emph{e.g.},
-% |(|, |)|, |?|, |.|);
+% use |\(|, |\)|, |\?|, |\.|);
% \item spaces should always be escaped (even in character
% classes);
% \item any other character may be escaped or not, without any
@@ -121,12 +138,11 @@
% \end{itemize}
% Note that these rules play nicely with the fact that many
% non-alphanumeric characters are difficult to input into \TeX{}
-% under normal category codes. For instance, |\$\%\^\\abc\#|
-% matches the literal string |$%^\abc#|.
-% \begin{texnote}
-% When converting the regular expression to a string,
-% the value of the escape character is set to be a backslash.
-% \end{texnote}
+% under normal category codes. For instance, |\\abc\%|
+% matches the characters |\abc%| (with arbitrary category codes),
+% but does not match the control sequence |\abc| followed by a
+% percent character. Matching control sequences can be done
+% using the |\c|\Arg{regex} syntax (see below).
%
% Any special character which appears at a place where its special
% behaviour cannot apply matches itself instead (for instance,
@@ -146,7 +162,7 @@
%
% Character types.
% \begin{l3regex-syntax}
-% \item[.] A single period matches any character, including newlines.
+% \item[.] A single period matches any token.
% \item[\\d] Any decimal digit.
% \item[\\h] Any horizontal space character,
% equivalent to |[\ \^^I]|: space and tab.
@@ -157,22 +173,27 @@
% but not a space, for compatibility with Perl.
% \item[\\w] Any word character, \emph{i.e.},
% alpha-numerics and underscore, equivalent to |[A-Za-z0-9\_]|.
-% \item[\\D] Any character not matched by |\d|.
-% \item[\\H] Any character not matched by |\h|.
-% \item[\\N] Any character other than the |\n| character (hex 0A).
-% \item[\\S] Any character not matched by |\s|.
-% \item[\\V] Any character not matched by |\v|.
-% \item[\\W] Any character not matched by |\w|.
+% \item[\\D] Any token not matched by |\d|.
+% \item[\\H] Any token not matched by |\h|.
+% \item[\\N] Any token other than the |\n| character (hex 0A).
+% \item[\\S] Any token not matched by |\s|.
+% \item[\\V] Any token not matched by |\v|.
+% \item[\\W] Any token not matched by |\w|.
% \end{l3regex-syntax}
+% Of those, |.|, |\D|, |\H|, |\N|, |\S|, |\V|, and |\W| will match arbitrary
+% control sequences.
%
% Character classes match exactly one character in the subject string.
% \begin{l3regex-syntax}
% \item[{[\ldots{}]}] Positive character class.
-% Matches any of the specified characters.
+% Matches any of the specified tokens.
% \item[{[\char`\^\ldots{}]}] Negative character class.
-% Matches any character other than the specified characters.
+% Matches any token other than the specified characters.
% \item[{[x-y]}] Range (can be used with escaped characters).
% \end{l3regex-syntax}
+% For instance, |[a-oq-z\cC]| matches any lowercase latin letter
+% except |p|, as well as control sequences (see below for a description
+% of |\c|).
%
% Quantifiers (repetition).
% \begin{l3regex-syntax}
@@ -191,12 +212,15 @@
%
% Anchors and simple assertions.
% \begin{l3regex-syntax}
-% \item[\\b] Word boundary.
-% \item[\\B] Not a word boundary.
+% \item[\\b] Word boundary: either the previous token is matched by
+% |\w| and the next by |\W|, or the opposite. For this purpose,
+% the ends of the token list are considered as |\W|.
+% \item[\\B] Not a word boundary: between two |\w| tokens
+% or two |\W| tokens (including the boundary).
% \item[\char`^ \textrm{or} \\A]
-% Start of the subject string.
+% Start of the subject token list.
% \item[\char`$\textrm{,} \\Z \textrm{or} \\z]
-% End of the subject string.
+% End of the subject token list.
% \item[\\G] Start of the current match. This is only different from |^|
% in the case of multiple matches: for instance
% |\regex_count:nnN { \G a } { aaba } \l_tmpa_int| yields $2$, but
@@ -216,6 +240,30 @@
% group number.
% \end{l3regex-syntax}
%
+% Each catcode is represented by a single uppercase letter:
+% |C| for control sequences;
+% |B| for begin-group tokens;
+% |E| for end-group tokens;
+% |M| for math shift;
+% |T| for alignment tab tokens;
+% |P| for macro parameter tokens;
+% |U| for superscript tokens (up);
+% |D| for subscript tokens (down);
+% |S| for spaces;
+% |L| for letters;
+% |O| for others; and
+% |A| for active characters.
+% \begin{l3regex-syntax}
+% \item[\\cX] Any character token with catcode |X|,
+% where |X| can be any of |CBEMTPUDSLOA|.
+% \item[\\CX] Any token with catcode different from |X|,
+% where |X| can be any of |CBEMTPUDSLOA|.
+% \item[\\c\Arg{regex}] A control sequence whose csname matches
+% the \meta{regex}, anchored at the beginning and end,
+% so that |\c{begin}| matches exactly \cs{begin}, and nothing
+% else.
+% \end{l3regex-syntax}
+%
% Options can be set with |(?|\meta{option}|)| and
% unset with |(?-|\meta{option}|)|. Options are local
% to the group in which they are set, and revert to their
@@ -234,7 +282,7 @@
% In character classes, only |^|, |-|, |]|, |\| and spaces are special,
% and should be escaped. Other non-alphanumeric characters can
% still be escaped without harm. The escape sequences |\d|,
-% |\D|, |\w|, |\W| are also supported in character classes.
+% |\D|, etc. are also supported in character classes.
% If the first character is |^|, then the meaning of the character
% class is inverted. Ranges of characters can be expressed using
% |-|, for instance, |[\D 0-5]| is equivalent to |[^6-9]|.
@@ -256,10 +304,24 @@
%
% For instance,
% \begin{verbatim}
-% \str_set:Nn \l_my_str { Hello,~world! }
-% \regex_replace_all:nnN { ([er]?l|o) . } { \(\0\-\-\1\) } \l_my_str
+% \tl_set:Nn \l_my_tl { Hello,~world! }
+% \regex_replace_all:nnN { ([er]?l|o) . } { \(\0\-\-\1\) } \l_my_tl
% \end{verbatim}
-% results in \cs{l_my_str} holding |H(ell--el)(o,--o) w(or--o)(ld--l)!|
+% results in \cs{l_my_tl} holding |H(ell--el)(o,--o) w(or--o)(ld--l)!|
+%
+% The characters inserted by the replacement have category code $12$
+% (other) by default. The escape sequence |\c| allows to insert characters
+% with arbitrary category codes, as well as control sequences.
+% \begin{l3regex-syntax}
+% \item[\\cXY] Produces the character |Y| (which can be given as
+% an escape sequence such as |\t| for tab) with category code |X|,
+% which must be one of |CBEMTPUDSLOA|.
+% \item[\\c\Arg{text}] Produces the control sequence with csname
+% \meta{text}. The \meta{text} may contain references to the submatches
+% |\0|, |\1| \emph{etc.} As an experimental feature, |\c{...}| can be
+% nested, but the behaviour is not yet fully specified. Use at own risks.
+%^^A todo: decide what the right behaviour should be.
+% \end{l3regex-syntax}
%
% \subsection{Precompiling regular expressions}
%
@@ -285,14 +347,32 @@
% The assignment is local for \cs{regex_set:Nn} and global for
% \cs{regex_gset:Nn}. Use \cs{regex_const:Nn} for precompiled expressions
% which will never change.
-% \begin{texnote}
-% Precompiled regular expressions can be safely written to a file
-% and read when the \LaTeX3 syntax is active (as triggered by
-% \cs{ExplSyntaxOn}).
-% \end{texnote}
% \end{function}
%
-% \subsection{String matching}
+% \begin{function}{\regex_to_str:N}
+% \begin{syntax}
+% \cs{regex_to_str:N} \meta{regex var}
+% \end{syntax}
+% Converts the \meta{regex var}, previously defined using
+% \cs{regex_(g)set:Nn}, to a string, which can be safely
+% read back when the \LaTeX3 syntax is active
+% (as triggered by \cs{ExplSyntaxOn}).
+% Line breaks are inserted at various places, to \emph{try}
+% and keep the line length short.
+% For instance, if \cs{l_my_stream} is an open stream for writing,
+% and \cs{l_regex_tl} is a regex variable, you can save its definition
+% using
+% \begin{verbatim}
+% \iow_now:Nx \l_my_stream
+% {
+% \tl_to_str:n { \tl_set:Nn \l_my_regex_tl } { \iow_newline:
+% \regex_to_str:N \l_my_regex_tl
+% }
+% }
+% \end{verbatim}
+% \end{function}
+%
+% \subsection{Matching}
%
% All regular expression functions are available in both |:n| and |:N|
% variants. The former require a \enquote{standard} regular expression,
@@ -301,10 +381,10 @@
%
% \begin{function}[TF]{\regex_match:nn, \regex_match:Nn}
% \begin{syntax}
-% \cs{regex_match:nnTF} \Arg{regex} \Arg{string} \Arg{true code} \Arg{false code}
+% \cs{regex_match:nnTF} \Arg{regex} \Arg{token list} \Arg{true code} \Arg{false code}
% \end{syntax}
-% Tests whether the \meta{regular expression} matches any substring
-% of \meta{string}. For instance,
+% Tests whether the \meta{regular expression} matches any part
+% of the \meta{token list}. For instance,
% \begin{verbatim}
% \regex_match:nnTF { b [cde]* } { abecdcx } { TRUE } { FALSE }
% \regex_match:nnTF { [b-dq-w] } { example } { TRUE } { FALSE }
@@ -314,15 +394,18 @@
%
% \begin{function}{\regex_count:nnN, \regex_count:NnN}
% \begin{syntax}
-% \cs{regex_count:nnN} \Arg{regex} \Arg{string} \meta{int var}
+% \cs{regex_count:nnN} \Arg{regex} \Arg{token list} \meta{int var}
% \end{syntax}
% Sets \meta{int var} within the current \TeX{} group level
% equal to the number of times
-% \meta{regular expression} appears in \meta{string}.
+% \meta{regular expression} appears in \meta{token list}.
% The search starts by finding the left-most longest match,
% respecting greedy and ungreedy operators. Then the search
% starts again from the character following the last character
-% of the previous match, until reaching the end of the string.
+% of the previous match, until reaching the end of the token list.
+% Infinite loops are prevented in the case where the regular expression
+% can match an empty string: then we count one match between each
+% pair of characters.
% For instance,
% \begin{verbatim}
% \int_new:N \l_foo_int
@@ -335,25 +418,25 @@
%
% \begin{function}[TF]{\regex_extract_once:nnN, \regex_extract_once:NnN}
% \begin{syntax}
-% \cs{regex_extract_once:nnN} \Arg{regex} \Arg{string} \meta{seq~var}
-% \cs{regex_extract_once:nnNTF} \Arg{regex} \Arg{string} \meta{seq~var} \Arg{true code} \Arg{false code}
+% \cs{regex_extract_once:nnN} \Arg{regex} \Arg{token list} \meta{seq~var}
+% \cs{regex_extract_once:nnNTF} \Arg{regex} \Arg{token list} \meta{seq~var} \Arg{true code} \Arg{false code}
% \end{syntax}
% Finds the first match of the \meta{regular expression}
-% in the \meta{string}. If it exists, the match is stored
+% in the \meta{token list}. If it exists, the match is stored
% as the zeroeth item of the \meta{seq~var}, and further
% items are the contents of capturing groups, in the order
% of their opening parenthesis. The \meta{seq~var}
% is assigned locally. If there is no match,
-% the \meta{seq~var} is not altered.
+% the \meta{seq~var} is cleared.
% The testing versions insert the \meta{true code} into the input
% stream if a match was found, and the \meta{false code} otherwise.
% For instance, assume that you type
% \begin{verbatim}
-% \regex_extract_once:nnNTF { ^(La)?TeX(!*)$ } { LaTeX!!! }
-% \l_foo_seq { true } { false }
+% \regex_extract_once:nnNTF { \A(La)?TeX(!*)\Z } { LaTeX!!! } \l_foo_seq
+% { true } { false }
% \end{verbatim}
-% Then the regular expression (anchored at the start with |^| and
-% at the end with |$|) will match the whole string. The first
+% Then the regular expression (anchored at the start with |\A| and
+% at the end with |\Z|) will match the whole token list. The first
% capturing group, |(La)?|, matches |La|, and the second capturing
% group, |(!*)|, matches |!!!|. Thus, |\l_foo_seq| will contain
% the items |{LaTeX!!!}|, |{La}|, and |{!!!}|, and the \texttt{true}
@@ -362,64 +445,79 @@
%
% \begin{function}[TF]{\regex_extract_all:nnN, \regex_extract_all:NnN}
% \begin{syntax}
-% \cs{regex_extract_all:nnNTF} \Arg{regex} \Arg{string} \meta{seq~var} \Arg{true code} \Arg{false code}
+% \cs{regex_extract_all:nnN} \Arg{regex} \Arg{token list} \meta{seq~var}
+% \cs{regex_extract_all:nnNTF} \Arg{regex} \Arg{token list} \meta{seq~var} \Arg{true code} \Arg{false code}
% \end{syntax}
% Finds all matches of the \meta{regular expression}
-% in the \meta{string}, and stores all the submatch information
+% in the \meta{token list}, and stores all the submatch information
% in a single sequence (concatenating the results of
% multiple \cs{regex_extract_once:nnN} calls).
+% The \meta{seq~var} is assigned locally. If there is no match,
+% the \meta{seq~var} is cleared.
+% The testing versions insert the \meta{true code} into the input
+% stream if a match was found, and the \meta{false code} otherwise.
% For instance, assume that you type
% \begin{verbatim}
-% \regex_extract_all:nnN { \w+ } { Hello,~world! } \l_foo_seq
+% \regex_extract_all:nnNTF { \w+ } { Hello,~world! } \l_foo_seq
+% { true } { false }
% \end{verbatim}
% Then the regular expression will match twice, and the resulting
-% sequence contains the two items \texttt{Hello} and \texttt{world}.
+% sequence contains the two items |{Hello}| and |{world}|,
+% and the \texttt{true} branch is left in the input stream.
% \end{function}
%
-% \begin{function}{\regex_split:nnN, \regex_split:NnN}
+% \begin{function}[TF]{\regex_split:nnN, \regex_split:NnN}
% \begin{syntax}
-% \cs{regex_split:nnN} \Arg{regular expression} \meta{string} \meta{seq~var}
+% \cs{regex_split:nnN} \Arg{regular expression} \Arg{token list} \meta{seq~var}
+% \cs{regex_split:nnNTF} \Arg{regular expression} \Arg{token list} \meta{seq~var} \Arg{true code} \Arg{false code}
% \end{syntax}
-% Searches the \meta{string} into a sequence of substrings, delimited by
+% Splits the \meta{token list} into a sequence of parts, delimited by
% matches of the \meta{regular expression}. If the \meta{regular expression}
-% has capturing groups, then the substrings that they match are stored as
+% has capturing groups, then the token lists that they match are stored as
% items of the sequence as well. The assignment to \meta{seq~var} is local.
-% If no match is found the resulting \meta{seq~var} has the \meta{string} as
-% its sole item. If the \meta{regular expression} matches the empty string,
-% then the \meta{string} is split into single character substrings.
+% If no match is found the resulting \meta{seq~var} has the
+% \meta{token list} as its sole item. If the \meta{regular expression}
+% matches the empty token list, then the \meta{token list} is split
+% into single tokens.
+% The testing versions insert the \meta{true code} into the input
+% stream if a match was found, and the \meta{false code} otherwise.
% For example, after
% \begin{verbatim}
% \seq_new:N \l_path_seq
-% \regex_split:nnN { / } { the/path/for/this/file.tex } \l_path_seq
+% \regex_split:nnNTF { / } { the/path/for/this/file.tex } \l_path_seq
+% { true } { false }
% \end{verbatim}
% the sequence |\l_path_seq| contains the items |{the}|, |{path}|,
-% |{for}|, |{this}|, and |{file.tex}|.
+% |{for}|, |{this}|, and |{file.tex}|, and the \texttt{true} branch
+% is left in the input stream.
% \end{function}
%
-% \subsection{String replacement}
+% \subsection{Replacement}
%
% \begin{function}[TF]{\regex_replace_once:nnN,\regex_replace_once:NnN}
% \begin{syntax}
-% \cs{regex_replace_once:nnN} \Arg{regular expression} \Arg{replacement} \meta{str~var}
+% \cs{regex_replace_once:nnN} \Arg{regular expression} \Arg{replacement} \meta{tl~var}
+% \cs{regex_replace_once:nnNTF} \Arg{regular expression} \Arg{replacement} \meta{tl~var} \Arg{true code} \Arg{false code}
% \end{syntax}
-% Searches for the \meta{regular expression} in the \meta{string}
-% and replaces the matching part with the \meta{replacement}. The result
-% is assigned locally to \meta{str~var}. In the \meta{replacement},
+% Searches for the \meta{regular expression} in the \meta{token list}
+% and replaces the first match with the \meta{replacement}. The result
+% is assigned locally to \meta{tl~var}. In the \meta{replacement},
% |\0| represents the full match, |\1| represent the contents
% of the first capturing group, |\2| of the second, \emph{etc.}
% \end{function}
%
-% \begin{function}{\regex_replace_all:nnN, \regex_replace_all:NnN}
+% \begin{function}[TF]{\regex_replace_all:nnN, \regex_replace_all:NnN}
% \begin{syntax}
-% \cs{regex_replace_all:nnN} \Arg{regular expression} \Arg{replacement} \meta{str~var}
+% \cs{regex_replace_all:nnN} \Arg{regular expression} \Arg{replacement} \meta{tl~var}
+% \cs{regex_replace_all:nnNTF} \Arg{regular expression} \Arg{replacement} \meta{tl~var} \Arg{true code} \Arg{false code}
% \end{syntax}
% Replaces all occurrences of the \cs{regular expression}
-% in the \meta{string} by the \meta{replacement}, where
+% in the \meta{token list} by the \meta{replacement}, where
% |\0| represents the full match, |\1|
% represent the contents of the first capturing group,
% |\2| of the second, \emph{etc.} Every match
% is treated independently, and matches cannot overlap.
-% The result is assigned locally to \meta{str~var}.
+% The result is assigned locally to \meta{tl~var}.
% \end{function}
%
% \subsection{Bugs, misfeatures, future work, and other possibilities}
@@ -427,11 +525,27 @@
% The following need to be done now.
% \begin{itemize}
% \item The \texttt{\{\}} quantifiers are only partially implemented.
-% \item Check the catcode of spaces everywhere.
-% \item Newline conventions are not done.
-% In particular, we should have an option for |.| not to match newlines.
-% Also, |\A| should differ from |^|, and |\Z|, |\z| and |$| should
-% differ.
+% \item Clean up the use of messages.
+% \item Add tests for every regex and replacement feature.
+% \item Make sure the documentation is correct, and the code comments
+% are as well.
+% \end{itemize}
+%
+% Code improvements to come.
+% \begin{itemize}
+% \item Test for the maximum register \cs{c_max_register_int}.
+% \item Use \tn{dimen} registers rather than \cs{l_regex_nesting_tl}
+% to build \cs{regex_nesting:n}.
+% \item Reduce the number of epsilon-transitions in alternatives.
+% \item Merge the \texttt{build} and \texttt{class} functions where possible.
+% \item Improve nesting of |\c| in the replacement text.
+% \item Move the \enquote{reconstruction} part of \pkg{l3regex}
+% to \pkg{l3tl-analysis}.
+% \item Optimize regexes for csnames when the regex is a simple string.
+% \item Optimize simple strings: use less states
+% (|abcade| should give two states, for |abc| and |ade|).
+% \item Optimize groups with no alternative.
+% \item Optimize the use of \cs{prg_stepwise_...} functions.
% \end{itemize}
%
% The following features are likely to be implemented at some point
@@ -441,30 +555,33 @@
% \item Regex matching on external files.
% \item Conditional subpatterns with look ahead/behind: \enquote{if
% what follows is [\ldots{}], then [\ldots{}]}.
-% \item |(*..)| and |(?..)| sequences to set some options.
-% \item Reduce the number of epsilon-transitions in alternatives.
-% \item Optimize simple strings: use less states
-% (|abcade| should give two states, for |abc| and |ade|).
-% \item Optimize groups with no alternative.
-% \item Optimize the use of \cs{prg_stepwise_...} functions.
+% \item |(*..)| and |(?..)| sequences to set some options
+% (partially implemented).
% \item |\K| for resetting the beginning of the match.
% \item UTF-8 mode for pdf\TeX{}.
+% \item Newline conventions are not done.
+% In particular, we should have an option for |.| not to match newlines.
+% Also, |\A| should differ from |^|, and |\Z|, |\z| and |$| should
+% differ.
+% \item Unicode properties: |\p{..}| and |\P{..}|;
+% |\X| which should match any \enquote{extended} Unicode sequence.
+% This requires to manipulate a lot of data, hence a lot of optimization
+% ahead.
% \end{itemize}
%
% The following features of PCRE or Perl will probably not be implemented.
% \begin{itemize}
-% \item |\cx|, similar to \TeX{}'s own |\^^x|;
% \item |\ddd|, matching the character with code \texttt{ddd} in octal;
-% \item POSIX character classes |[:alpha:]| \emph{etc.};
-% \item Unicode properties: |\p{..}| and |\P{..}|;
-% \item |\X| which should match any \enquote{extended} Unicode sequence;
-% \item Callout with |(?C...)|;
+% \item POSIX character classes |[:alpha:]| \emph{etc.}, this is redundant;
+% \item Callout with |(?C...)|, we cannot run arbitrary user code during
+% the matching, because the regex code uses registers in an unsafe way;
% \item Conditional subpatterns (other than with a look-ahead
% or look-behind condition): this is non-regular, isn't it?
% \end{itemize}
%
% The following features of PCRE or perl will definitely not be implemented.
% \begin{itemize}
+% \item |\cx|, similar to \TeX{}'s own |\^^x|;
% \item Comments: \TeX{} already has its own system for comments.
% \item Named subpatterns: \TeX{} programmers have lived so far without
% any need for named macro parameters.
@@ -495,7 +612,7 @@
% \begin{macrocode}
\ProvidesExplPackage
{\ExplFileName}{\ExplFileDate}{\ExplFileVersion}{\ExplFileDescription}
-\RequirePackage{l3str}
+\RequirePackage{l3str, l3tl-analysis, l3flag}
% \end{macrocode}
%
% Most regex engines use backtracking. This allows to provide very
@@ -506,8 +623,8 @@
%
% Given a regular expression of $n$ characters, we build a
% non-deterministic finite automaton (NFA) with roughly $n$ states,
-% which accepts precisely those strings matching that regular expression.
-% We then run the string through the NFA, and check the return value.
+% which accepts precisely those token lists matching that regular expression.
+% We then run the token list through the NFA, and check the return value.
%
% The code is structured as follows. Various helper functions are
% introduced in the next subsection, to limit the clutter in later
@@ -520,14 +637,14 @@
% \subsection{Constants and variables}
%
% \begin{macro}{\regex_tmp:w}
-% \begin{variable}{\g_regex_tmpa_tl, \l_regex_tmpa_tl, \l_regex_tmpb_tl}
+% \begin{variable}{\l_regex_tmpa_tl, \l_regex_tmpb_tl, \l_regex_tmpc_tl}
% \begin{variable}{\l_regex_tmpa_int, \l_regex_tmpb_int}
% Temporary variables.
% \begin{macrocode}
\cs_new:Npn \regex_tmp:w { }
-\tl_new:N \g_regex_tmpa_tl
\tl_new:N \l_regex_tmpa_tl
\tl_new:N \l_regex_tmpb_tl
+\tl_new:N \l_regex_tmpc_tl
\int_new:N \l_regex_tmpa_int
\int_new:N \l_regex_tmpb_int
% \end{macrocode}
@@ -535,14 +652,27 @@
% \end{variable}
% \end{macro}
%
+% \begin{variable}{\l_regex_group_begin_flag}
+% \begin{variable}{\l_regex_group_end_flag}
+% Those flags are raised to indicate extra begin-group
+% or end-group tokens when extracting submatches.
+% \begin{macrocode}
+\flag_new:N \l_regex_group_begin_flag
+\flag_new:N \l_regex_group_end_flag
+% \end{macrocode}
+% \end{variable}
+% \end{variable}
+%
% \subsubsection{Variables used while building}
%
% \begin{variable}{\l_regex_max_state_int}
% \begin{variable}{\l_regex_left_state_int, \l_regex_right_state_int}
-% The last state that was allocated is stored in \cs{l_regex_max_state_int},
-% and \cs{l_regex_left/right_state_int} point to both end-points of the
-% last group (which any quantifier would repeat). For simple strings of
-% characters, the left and right pointers only differ by one.
+% The end-point states of the last group (which any quantifier
+% would repeat) are stored as \cs{l_regex_left/right_state_int}.
+% For simple strings of characters, the left and right pointers
+% only differ by one.
+% The last state that was allocated is $\cs{l_regex_max_state_int}-1$,
+% so that \cs{l_regex_max_state_int} always points to a free state.
% \begin{macrocode}
\int_new:N \l_regex_max_state_int
\int_new:N \l_regex_left_state_int
@@ -593,6 +723,16 @@
% \end{variable}
% \end{variable}
%
+% \begin{variable}{\l_regex_build_or_class_tl}
+% The actions of various escape sequences and special characters
+% are different within a class and outside a class. Rather than
+% using a boolean, we store either \texttt{build} or \texttt{class}
+% in this token list.
+% \begin{macrocode}
+\tl_new:N \l_regex_build_or_class_tl
+% \end{macrocode}
+% \end{variable}
+%
% \begin{variable}{\l_regex_one_or_group_tl}
% When looking for quantifiers, this variable holds either
% \enquote{one} or \enquote{group} depending on whether the
@@ -603,6 +743,22 @@
% \end{macrocode}
% \end{variable}
%
+% \begin{variable}{\l_regex_in_csname_bool}
+% This boolean is true when building the regular expression
+% for a given csname.
+% \begin{macrocode}
+\bool_new:N \l_regex_in_csname_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_regex_tmpa_regex_tl}
+% This holds a temporary pre-compiled regular expression
+% when matching a control sequence name.
+% \begin{macrocode}
+\tl_new:N \l_regex_tmpa_regex_tl
+% \end{macrocode}
+% \end{variable}
+%
% \subsubsection{Character classes}
%
% \begin{macro}{\regex_build_tmp_class:n}
@@ -710,19 +866,37 @@
%
% \subsubsection{Variables used when matching}
%
-% \begin{variable}{\l_regex_query_other_str}
-% The string that is being matched.
-% In that string, spaces have category code \enquote{other}.
+% \begin{variable}{\l_regex_nesting_int}
+% This integer is used to keep track of begin and end-group tokens.
+% \begin{macrocode}
+\int_new:N \l_regex_nesting_int
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_regex_min_step_int}
+% \begin{variable}{\l_regex_max_step_int}
+% This integer holds the value of the step corresponding to
+% the left end of the token list, \emph{i.e.}, when the token
+% list is stored in \tn{toks} registers, the \tn{toks} register
+% \cs{l_regex_min_step_int} holds the first token in the token
+% list. In fact, this number is always one more than
+% \cs{l_regex_max_state_int}, but it is more practical to give
+% that a name.
% \begin{macrocode}
-\tl_new:N \l_regex_query_other_str
+\int_new:N \l_regex_min_step_int
+\int_new:N \l_regex_max_step_int
% \end{macrocode}
% \end{variable}
+% \end{variable}
%
% \begin{variable}{\l_regex_current_step_int}
% \begin{variable}{\l_regex_start_step_int}
% \begin{variable}{\l_regex_success_step_int}
-% While reading through the query string, \cs{l_regex_current_step_int}
-% is the position in the string. Each match begins at the position
+% While reading through the query token list,
+% \cs{l_regex_current_step_int} is the position in the
+% token list, starting at \cs{l_regex_min_step_int} for
+% the left-most token.
+% Each match begins at the position
% given by \cs{l_regex_start_step_int}. Whenever an execution thread
% succeeds, the corresponding step is stored into
% \cs{l_regex_success_step_int}, which will be the next starting step
@@ -736,24 +910,26 @@
% \end{variable}
% \end{variable}
%
-% \begin{variable}{\l_regex_unique_step_int}
+% \begin{variable}{\l_regex_unique_id_int}
% In the case of repeated matches, \cs{l_regex_current_step_int}
% is reset to the end-position of the previous match. In contrast,
-% \cs{l_regex_unique_step_int} is simply incremented to provide
+% \cs{l_regex_unique_id_int} is simply incremented to provide
% a unique number for each iteration of the matching loop. This
% is handy to attach each set of submatch information to a given
% iteration (and automatically discard it when it corresponds to
% a past iteration).
% \begin{macrocode}
-\int_new:N \l_regex_unique_step_int
+\int_new:N \l_regex_unique_id_int
% \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_regex_current_char_int}
+% \begin{variable}{\l_regex_current_catcode_int}
+% \begin{variable}{\l_regex_current_token_tl}
% \begin{variable}{\l_regex_last_char_int}
% \begin{variable}{\l_regex_case_changed_char_int}
% The character codes of the character at the current position
-% in the string, and at the previous position, and the current
+% in the token list, and at the previous position, and the current
% character with its case changed (|A-Z|$\leftrightarrow$|a-z|).
% The \cs{l_regex_last_char_int} is used to test for word boundaries
% (|\b| and |\B|). The \cs{l_regex_case_changed_char_int} is
@@ -761,12 +937,16 @@
% is used in the regex.
% \begin{macrocode}
\int_new:N \l_regex_current_char_int
+\int_new:N \l_regex_current_catcode_int
+\tl_new:N \l_regex_current_token_tl
\int_new:N \l_regex_last_char_int
\int_new:N \l_regex_case_changed_char_int
% \end{macrocode}
% \end{variable}
% \end{variable}
% \end{variable}
+% \end{variable}
+% \end{variable}
%
% \begin{variable}{\l_regex_caseless_bool}
% True if caseless matching is used within the regular expression.
@@ -777,7 +957,7 @@
% \end{variable}
%
% \begin{variable}{\l_regex_current_state_int}
-% For every character in the string, each of the active states is
+% For every character in the token list, each of the active states is
% considered in turn.
% The variable \cs{l_regex_current_state_int} holds the state
% of the NFA which is currently considered: transitions are then
@@ -823,26 +1003,38 @@
% \begin{macro}[int]{\l_regex_every_match_tl}
% Every time a match is found, this token list is used.
% For single matching, the token list is set to removing
-% the remainder of the query string. For multiple matching,
+% the remainder of the query token list. For multiple matching,
% the token list is set to repeat the matching.
% \begin{macrocode}
\tl_new:N \l_regex_every_match_tl
% \end{macrocode}
% \end{macro}
%
-% \begin{variable}{\l_regex_success_bool}
-% The boolean \cs{l_regex_success_bool} is true if the current match
-% attempt was successful.
+% \begin{variable}{\g_regex_success_bool}
+% \begin{variable}{\l_regex_saved_success_bool}
+% \begin{variable}{\l_regex_success_match_bool}
+% The boolean \cs{g_regex_success_bool} is true if there was
+% at least one match, and \cs{l_regex_success_match_bool} is
+% true if the current match attempt was successful.
+% The variable \cs{g_regex_success_bool} is the only global
+% variable in this whole module. When nesting \cs{regex}
+% functions internally, the value of \cs{g_regex_success_bool}
+% is saved into \cs{l_regex_saved_success_bool}, which is local,
+% hence not affected by the changes due to inner regex functions.
% \begin{macrocode}
-\bool_new:N \l_regex_success_bool
+\bool_new:N \g_regex_success_bool
+\bool_new:N \l_regex_saved_success_bool
+\bool_new:N \l_regex_success_match_bool
% \end{macrocode}
% \end{variable}
+% \end{variable}
+% \end{variable}
%
% \begin{macro}{\regex_last_match_empty:F}
% \begin{macro}[aux]{\regex_last_match_empty_no:F}
% \begin{macro}[aux]{\regex_last_match_empty_yes:F}
% When doing multiple matches, we need to avoid infinite loops where
-% each iteration matches the same empty string. When we detect such
+% each iteration matches the same empty token list. When we detect such
% a situation, the next match attempt is shifted by one character.
% Namely, an empty match is discarded if it follows an empty match
% at the same position. If the previous match was non-empty,
@@ -876,86 +1068,61 @@
% \end{variable}
% \end{variable}
%
-% \subsubsection{Variables used for user functions}
+% \subsubsection{Variables used when building the replacement}
%
-% \begin{variable}{\g_regex_submatches_seq}
-% This holds temporarily a sequence of submatches,
-% global so that it exits the group.
+% \begin{variable}{\l_regex_replacement_int}
% \begin{macrocode}
-\seq_new:N \g_regex_submatches_seq
+\int_new:N \l_regex_replacement_int
% \end{macrocode}
% \end{variable}
%
-% \begin{variable}{\g_regex_match_count_int}
-% The number of matches found so far is stored
-% in \cs{g_regex_match_count_int}. This is only used
-% in the \cs{regex_count:nnN} functions.
+% \begin{variable}{\l_regex_replacement_csnames_int}
% \begin{macrocode}
-\int_new:N \g_regex_match_count_int
+\int_new:N \l_regex_replacement_csnames_int
% \end{macrocode}
% \end{variable}
%
-% \begin{variable}{\g_regex_extract_all_seq}
-% The \cs{regex_extract_all:nnN} function stores its result
-% in that sequence variable before assigning it to the
-% variable provided by the user.
+% \begin{variable}{\l_regex_submatch_int}
+% \begin{variable}{\l_regex_submatch_start_int}
% \begin{macrocode}
-\seq_new:N \g_regex_extract_all_seq
+\int_new:N \l_regex_submatch_int
+\int_new:N \l_regex_submatch_start_int
% \end{macrocode}
% \end{variable}
-%
-% \begin{variable}{\g_regex_split_seq}
-% The \cs{regex_split:nnN} function stores its result
-% in that sequence variable before assigning it to the
-% variable provided by the user.
-% \begin{macrocode}
-\seq_new:N \g_regex_split_seq
-% \end{macrocode}
% \end{variable}
%
-% \begin{variable}{\l_regex_replacement_tl}
-% \begin{variable}{\g_regex_replaced_str}
-% The replacement code stores a processed version
-% of the user's argument in \cs{l_regex_replacement_tl}.
-% The result of the replacement is stored in \cs{g_regex_replaced_str},
-% global to exit the group.
+% \subsubsection{Variables used for user functions}
+%
+% \begin{variable}{\l_regex_match_count_int}
+% The number of matches found so far is stored
+% in \cs{l_regex_match_count_int}. This is only used
+% in the \cs{regex_count:nnN} functions.
% \begin{macrocode}
-\tl_new:N \l_regex_replacement_tl
-\tl_new:N \g_regex_replaced_str
+\int_new:N \l_regex_match_count_int
% \end{macrocode}
% \end{variable}
-% \end{variable}
%
% \subsection{Helpers}
%
-% \subsubsection{Scan markers}
-%
-% \begin{macro}[int]{\s_regex_stop}
-% Unexpandable markers, which does nothing if it reaches \TeX{}'s stomach.
-% When performing the matching, the \tn{toks} registers hold submatch
-% information, followed by the instruction for a given state of the NFA.
-% The two parts are separated by \cs{s_regex_stop}.
-% See \cs{regex_nfa:Nw} for another use.
-% \begin{macrocode}
-\cs_new_eq:NN \s_regex_stop \scan_stop:
-% \end{macrocode}
-% \end{macro}
-%
% \subsubsection{Toks}
%
+% When performing the matching, the \tn{toks} registers hold submatch
+% information, followed by the instruction for a given state of the NFA.
+% The two parts are separated by \cs{s_stop}.
+%
% \begin{macro}[int]{\regex_toks_put_left:Nx}
% \begin{macro}[int]{\regex_toks_put_right:Nx}
% During the building phase, every \tn{toks} register starts with
-% \cs{s_regex_stop}, and we wish to add \texttt{x}-expanded material
+% \cs{s_stop}, and we wish to add \texttt{x}-expanded material
% to those registers. The expansion is done \enquote{by hand} for
% optimization (these operations are used quite a lot). When adding
% material to the left, we define \cs{regex_tmp:w} to remove the
-% \cs{s_regex_stop} marker and put it back to the left of the new
+% \cs{s_stop} marker and put it back to the left of the new
% material.
% \begin{macrocode}
\cs_new_protected:Npn \regex_toks_put_left:Nx #1#2
{
- \cs_set_nopar:Npx \regex_tmp:w \s_regex_stop { \s_regex_stop #2 }
+ \cs_set_nopar:Npx \regex_tmp:w \s_stop { \s_stop #2 }
\tex_toks:D #1 \exp_after:wN \exp_after:wN \exp_after:wN
{ \exp_after:wN \regex_tmp:w \tex_the:D \tex_toks:D #1 }
}
@@ -969,30 +1136,37 @@
% \end{macro}
% \end{macro}
%
-% \subsubsection{Unsafe substring extraction}
+% \subsubsection{Extracting parts of the query token list}
%
-% \begin{macro}[int]{\regex_query_substr:NN}
-% \begin{macro}[aux]{\regex_query_substr_aux:nnn}
-% Extracting submatches from the query string is done
-% in a way similar to \cs{str_substr:Nnn}, but omitting
-% most checks, hence faster.
+% \begin{macro}[int]{\regex_query_substr:nn}
+% \begin{macro}[int]{\regex_query_submatch:nn}
+% \begin{macro}[int]{\regex_query_submatch:w}
+% The token list is stored in \tn{toks} registers:
+% the first is \cs{l_regex_min_step_int}, the last is (we don't care yet).
% \begin{macrocode}
-\cs_new_nopar:Npn \regex_query_substr:NN #1#2
+\cs_new:Npn \regex_query_substr:nn #1#2
{
- \exp_after:wN \regex_query_substr_aux:NN
- \exp_after:wN #1
- \exp_after:wN #2
- \l_regex_query_other_str
- \q_stop
+ \if_num:w \int_eval:w #1 < \int_eval:w #2 \int_eval_end:
+ \str_aux_toks_range:nn {#1} {#2}
+ \fi:
+ }
+\cs_new:Npn \regex_query_submatch:nn #1#2
+ {
+ \if_num:w #1 < \l_regex_capturing_group_int
+ \exp_after:wN \regex_query_submatch:w
+ \int_use:N \int_eval:w #1 + #2 ;
+ \fi:
}
-\cs_new:Npn \regex_query_substr_aux:NN #1#2
+\cs_new:Npn \regex_query_submatch:w #1 ;
{
- \str_skip_do:nn {#1}
- { \str_collect_do:nn { #2 - #1 } { \use_i_delimit_by_q_stop:nw } }
+ \regex_query_substr:nn
+ { \tex_skip:D #1 }
+ { \etex_gluestretch:D \tex_skip:D #1 }
}
% \end{macrocode}
% \end{macro}
% \end{macro}
+% \end{macro}
%
% \subsubsection{Sequences}
%
@@ -1042,20 +1216,46 @@
% \end{macro}
% \end{macro}
%
+% \begin{macro}[int, EXP]{\regex_seq_item:Nn}
+% \begin{macro}[aux, EXP]{\regex_seq_item_aux:wNn}
+% \begin{macrocode}
+\cs_new:Npn \regex_seq_item:Nn #1#2
+ {
+ \exp_after:wN \regex_seq_item_aux:wNn
+ \int_use:N \int_eval:w #2 \exp_after:wN ;
+ #1 { ? \prg_map_break: } { }
+ \prg_break_point:n { }
+ }
+\cs_new:Npn \regex_seq_item_aux:wNn #1; #2#3
+ {
+ \use_none:n #2
+ \if_num:w #1 = \c_zero
+ \exp_after:wN \prg_map_break:n
+ \else:
+ \exp_after:wN \use_none:n
+ \fi:
+ { \exp_not:n {#3} }
+ \exp_after:wN \regex_seq_item_aux:wNn
+ \int_use:N \int_eval:w #1 - \c_one ;
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
% \subsubsection{Testing characters}
%
% \begin{macro}{\regex_item_dot:T}
% The dot meta-character matches any character,
-% except the end-of-string marker.
+% except the end marker.
% \begin{macrocode}
\cs_new_protected_nopar:Npn \regex_item_dot:T
- { \int_compare:nNnF \l_regex_current_char_int = \c_minus_one }
+ { \int_compare:nNnF \l_regex_current_char_int < \c_minus_one }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\regex_break_point:TF}
% \begin{macro}[int]{\regex_break_true:w,\regex_break_false:w}
-% When testing whether a character of the query string matches
+% When testing whether a character of the query token list matches
% a given character class in the regular expression, we often
% have to test it against several ranges of characters, checking
% if any one of those matches. This is done with a structure like
@@ -1187,6 +1387,56 @@
% \end{macrocode}
% \end{macro}
%
+% \begin{macro}[int]{\regex_item_catcode:n}
+% \begin{macro}[int]{\regex_item_catcode_neq:n}
+% Checking the category code is quite straightforward.
+% \begin{macrocode}
+\cs_new_protected_nopar:Npn \regex_item_catcode:n #1
+ {
+ \if_num:w #1 = \l_regex_current_catcode_int
+ \exp_after:wN \regex_break_true:w
+ \fi:
+ }
+\cs_new_protected_nopar:Npn \regex_item_catcode_neq:n #1
+ {
+ \if_num:w #1 = \l_regex_current_catcode_int
+ \else:
+ \exp_after:wN \regex_break_true:w
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[int]{\regex_item_cs:n}
+% Match a control sequence (the argument is a pre-compiled regex).
+% First test the catcode of the current token to be zero.
+% Then perform the matching test, and break if the csname
+% indeed matches. The three \cs{exp_after:wN} expand the contents
+% of \cs{l_regex_current_token_tl} (of the form \cs{exp_not:n}
+% \Arg{control sequence}) to \meta{control sequence}.
+% \begin{macrocode}
+\cs_new_protected:Npn \regex_item_cs:n #1
+ {
+ \int_compare:nNnT \l_regex_current_catcode_int = \c_zero
+ {
+ \tl_set:Nn \l_regex_tmpa_regex_tl {#1}
+ \bool_set_eq:NN \l_regex_saved_success_bool \g_regex_success_bool
+ \exp_args:NNx \regex_match:NnTF \l_regex_tmpa_regex_tl
+ {
+ \exp_after:wN \exp_after:wN
+ \exp_after:wN \cs_to_str:N \l_regex_current_token_tl
+ }
+ {
+ \bool_gset_eq:NN \g_regex_success_bool \l_regex_saved_success_bool
+ \regex_break_true:w
+ }
+ { \bool_gset_eq:NN \g_regex_success_bool \l_regex_saved_success_bool }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \subsubsection{Grabbing digits}
%
% \begin{macro}[int]{\regex_get_digits:nw}
@@ -1323,19 +1573,15 @@
% \begin{macro}[int]{\regex_build_new_state:}
% Here, we add a new state to the NFA. At the end of the building
% phase, we want every \tn{toks} register to start with
-% \cs{s_regex_stop}, hence initialize the new register appropriately.
+% \cs{s_stop}, hence initialize the new register appropriately.
% Then set \cs{l_regex_left/right_state_int} to their new values.
% \begin{macrocode}
\cs_new_protected_nopar:Npn \regex_build_new_state:
{
- \int_compare:nNnTF \l_regex_max_state_int > { 32766 }
- { \msg_error:nn { regex } { -997 } }
- {
- \int_incr:N \l_regex_max_state_int
- \tex_toks:D \l_regex_max_state_int { \s_regex_stop }
- }
+ \tex_toks:D \l_regex_max_state_int { \s_stop }
\int_set_eq:NN \l_regex_left_state_int \l_regex_right_state_int
\int_set_eq:NN \l_regex_right_state_int \l_regex_max_state_int
+ \int_incr:N \l_regex_max_state_int
}
% \end{macrocode}
% \end{macro}
@@ -1377,7 +1623,9 @@
% First, reset a few variables. Then use the generic framework defined
% in \pkg{l3str} to parse the regular expression once, recognizing
% which characters are raw characters, and which have special meanings.
-% The result is stored in \cs{g_str_result_tl}, and can be run directly.
+% The search is not anchored: to achieve that, we insert state(s)
+% responsible for repeating the match attempt on every token
+% in the token list.
% The trailing \cs{prg_do_nothing:} ensure that the look-ahead done by
% some of the operations is harmless.
% Finally, \cs{regex_build_end:} adds the finishing code
@@ -1385,16 +1633,18 @@
% \begin{macrocode}
\cs_new_protected:Npn \regex_build:n #1
{
- \regex_build_setup:
- \str_aux_escape:NNNn
- \regex_build_i_unescaped:N
- \regex_build_i_escaped:N
- \regex_build_i_raw:N
- { #1 }
+ \bool_set_false:N \l_regex_in_csname_bool
+ \regex_build:w
+ \regex_build_new_state:
+ \regex_toks_put_right:Nx \l_regex_left_state_int
+ { \regex_action_start_wildcard:nn {0} {1} }
\regex_build_open_aux:
- \g_str_result_tl \prg_do_nothing: \prg_do_nothing:
- \regex_seq_push_int:NN \l_regex_capturing_group_seq \c_zero
- \regex_build_close_aux: \regex_build_group_:
+ \str_escape_use:NNNn
+ \regex_build_i_unescaped:N
+ \regex_build_i_escaped:N
+ \regex_build_i_raw:N
+ { #1 }
+ \prg_do_nothing: \prg_do_nothing:
\regex_build_end:
}
% \end{macrocode}
@@ -1403,7 +1653,7 @@
% \begin{macro}[aux]{\regex_build_i_unescaped:N}
% \begin{macro}[aux]{\regex_build_i_escaped:N}
% \begin{macro}[aux]{\regex_build_i_raw:N}
-% The \pkg{l3str} function \cs{str_aux_escape:NNNn} goes through
+% The \pkg{l3str} function \cs{str_escape_use:NNNn} goes through
% the regular expression and finds the |\a|, |\e|, |\f|, |\n|, |\r|,
% |\t|, and |\x| escape sequences, then distinguishes three cases:
% non-escaped characters, escaped characters, and \enquote{raw}
@@ -1432,35 +1682,36 @@
% \end{macro}
% \end{macro}
%
-% \begin{macro}[aux]{\regex_build_default_control:N}
-% If the control character has a particular meaning in regular expressions,
-% the corresponding function is used. Otherwise, it is interpreted as a raw
-% character. The \cs{regex_build_default_raw:N} function is defined later.
+% \begin{macro}[aux]{\regex_build_control:N}
+% If the control character has a particular meaning in regexes,
+% the corresponding function is used. Otherwise, it is interpreted
+% as a raw character. The \cs{regex_build/class_default_raw:N}
+% functions are defined later.
% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_build_default_control:N #1
+\cs_new_protected_nopar:Npn \regex_build_control:N #1
{
- \cs_if_exist_use:cF { regex_build_#1: }
- { \regex_build_default_raw:N #1 }
+ \cs_if_exist_use:cF { regex_ \l_regex_build_or_class_tl _#1: }
+ { \use:c { regex_ \l_regex_build_or_class_tl _default_raw:N } #1 }
}
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}[int]{\regex_build_setup:}
+%^^A todo: update
+% This function is similar to \cs{regex_build_control:N}. If the control
+% character has a meaning in character classes, call the corresponding
+% function, otherwise, treat it as a raw character, with the
+% \cs{regex_class_default_raw:N} function, defined later.
+%
+% \begin{macro}[int]{\regex_build:w}
% Hopefully, we didn't forget to initialize anything here.
-% The search is not anchored: to achieve that, we insert state(s)
-% responsible for repeating the match attempt on every character
-% of the string.
% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_build_setup:
+\cs_new_protected_nopar:Npn \regex_build:w
{
- \cs_set_eq:NN \regex_build_control:N \regex_build_default_control:N
\cs_set_eq:NN \regex_build_raw:N \regex_build_default_raw:N
+ \tl_set:Nn \l_regex_build_or_class_tl { build }
\int_set_eq:NN \l_regex_capturing_group_int \c_zero
\int_zero:N \l_regex_max_state_int
\regex_build_new_state:
- \regex_build_new_state:
- \regex_toks_put_right:Nx \l_regex_left_state_int
- { \regex_action_start_wildcard:nn {0} {1} }
}
% \end{macrocode}
% \end{macro}
@@ -1472,15 +1723,19 @@
% \begin{macrocode}
\cs_new_protected_nopar:Npn \regex_build_end:
{
+ \regex_seq_push_int:NN \l_regex_capturing_group_seq \c_zero
+ \regex_build_close_aux: \regex_build_group_:
\seq_if_empty:NF \l_regex_capturing_group_seq
{
- \msg_error:nn { regex } {22}
+ \msg_kernel_error:nnx { regex } { missing-rparen }
+ { \seq_length:N \l_regex_capturing_group_seq }
\prg_replicate:nn
{ \seq_length:N \l_regex_capturing_group_seq }
{ \regex_build_close_aux: \regex_build_group_: }
}
\regex_toks_put_right:Nx \l_regex_right_state_int
{ \regex_action_success: }
+ \int_incr:N \l_regex_capturing_group_int
}
% \end{macrocode}
% \end{macro}
@@ -1492,14 +1747,14 @@
% \begin{macro}[int]{\regex_build_G:}
% \begin{macro}[aux]{\regex_build_anchor_start:N}
% Anchoring at the start corresponds to checking that the current
-% character is the first in the string. Anchoring to the beginning
+% character is the first in the token list. Anchoring to the beginning
% of the match attempt uses \cs{l_regex_start_step_int} instead of
% \cs{c_zero}.
% \begin{macrocode}
\cs_new_protected_nopar:cpn { regex_build_^: }
- { \regex_build_anchor_start:N \c_zero }
+ { \regex_build_anchor_start:N \l_regex_min_step_int }
\cs_new_protected_nopar:Npn \regex_build_A:
- { \regex_build_anchor_start:N \c_zero }
+ { \regex_build_anchor_start:N \l_regex_min_step_int }
\cs_new_protected_nopar:Npn \regex_build_G:
{ \regex_build_anchor_start:N \l_regex_start_step_int }
\cs_new_protected_nopar:Npn \regex_build_anchor_start:N #1
@@ -1507,7 +1762,7 @@
\regex_build_new_state:
\regex_toks_put_right:Nx \l_regex_left_state_int
{
- \exp_not:N \int_compare:nNnT {#1} = \l_regex_current_step_int
+ \exp_not:N \int_compare:nNnT #1 = \l_regex_current_step_int
{
\regex_action_free:n
{
@@ -1526,7 +1781,7 @@
% \begin{macro}[aux]{\regex_build_Z:}
% \begin{macro}[aux]{\regex_build_z:}
% \begin{macro}[aux]+\regex_build_$:+
-% This matches the end of the string, marked by a character code of $-1$.
+% This matches the end of the token list, marked by a character code of $-2$.
% \begin{macrocode}
\cs_new_protected_nopar:cpn { regex_build_$: } % $
{
@@ -1534,7 +1789,7 @@
\regex_toks_put_right:Nx \l_regex_left_state_int
{
\exp_not:N \int_compare:nNnT
- \c_minus_one = \l_regex_current_char_int
+ \l_regex_current_char_int < \c_minus_one
{
\regex_action_free:n
{
@@ -1555,7 +1810,7 @@
% \begin{macro}[int]{\regex_build_B:}
% \begin{macro}[aux]{\regex_if_word_boundary:TF}
% Contrarily to |^| and |$|, which could be implemented without
-% really knowing what precedes in the string, this requires
+% really knowing what precedes in the token list, this requires
% more information, namely, the knowledge of the last character
% code. Case sensitivity does not change word boundaries.
% \begin{macrocode}
@@ -1597,7 +1852,7 @@
\int_set_eq:NN \l_regex_current_char_int \l_regex_last_char_int
\c_regex_w_tl
\regex_break_point:TF
- { \group_end: \c_regex_W_tl \regex_item_equal:n { -1 } }
+ { \group_end: \c_regex_W_tl \regex_item_equal:n { -2 } }
{ \group_end: \c_regex_w_tl }
\regex_break_point:TF
}
@@ -1608,6 +1863,23 @@
%
% \subsubsection{Normal character, and simple character classes}
%
+% \begin{macro}[int]{\regex_build_one:n, \regex_build_one:x}
+%
+% \begin{macrocode}
+\cs_new_protected:Npn \regex_build_one:n #1
+ { \regex_build_one:x { \exp_not:n {#1} } }
+\cs_new_protected:Npn \regex_build_one:x #1
+ {
+ \cs_set_nopar:Npx \regex_build_tmp_class:n ##1
+ {
+ \exp_not:N \exp_not:n {#1}
+ \regex_break_point:TF { \regex_action_cost:n {##1} } { }
+ }
+ \regex_build_one_quantifier:
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \begin{macro}[aux]{\regex_build_default_raw:N}
% A normal alphanumeric or an escaped non-alphanumeric
% (actually, any unknown combination) will match itself
@@ -1618,20 +1890,13 @@
% yet what this will be before checking for quantifiers.
% \begin{macrocode}
\cs_new_protected_nopar:Npn \regex_build_default_raw:N #1
- {
- \cs_set:Npx \regex_build_tmp_class:n ##1
- {
- \regex_item_equal:n { \int_value:w `#1 ~ }
- \regex_break_point:TF { \regex_action_cost:n { ##1 } } { }
- }
- \regex_build_one_quantifier:
- }
+ { \regex_build_one:x { \regex_item_equal:n { \int_value:w `#1 ~ } } }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}[aux]{\regex_build_.:}
% Similar to \cs{regex_build_default_raw:N} but accepts any character,
-% and refuses $-1$, which marks the end of the string.
+% and refuses $-1$, which marks the end of the token list.
% \begin{macrocode}
\cs_new_protected_nopar:cpn { regex_build_.: }
{
@@ -1642,34 +1907,21 @@
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}[aux]{\regex_build_d:,\regex_build_D}
-% \begin{macro}[aux]{\regex_build_h:,\regex_build_H}
-% \begin{macro}[aux]{\regex_build_s:,\regex_build_S}
-% \begin{macro}[aux]{\regex_build_v:,\regex_build_V}
-% \begin{macro}[aux]{\regex_build_w:,\regex_build_W}
+% \begin{macro}[aux]{\regex_build_d:,\regex_build_D:}
+% \begin{macro}[aux]{\regex_build_h:,\regex_build_H:}
+% \begin{macro}[aux]{\regex_build_s:,\regex_build_S:}
+% \begin{macro}[aux]{\regex_build_v:,\regex_build_V:}
+% \begin{macro}[aux]{\regex_build_w:,\regex_build_W:}
% \begin{macro}[aux]{\regex_build_N:}
-% \begin{macro}[aux]{\regex_build_char_type:N}
% The constants \cs{c_regex_d_tl}, \emph{etc.} hold
% a list of tests which match the corresponding character
% class, and jump to the \cs{regex_break_point:TF} marker.
% As for a normal character, we check for quantifiers.
% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_build_char_type:N #1
- {
- \cs_set:Npn \regex_build_tmp_class:n ##1
- {
- \exp_not:N #1
- \regex_break_point:TF { \regex_action_cost:n {##1} } { }
- }
- \regex_build_one_quantifier:
- }
\tl_map_inline:nn { dDhHsSvVwWN }
{
\cs_new_protected_nopar:cpx { regex_build_#1: }
- {
- \exp_not:N \regex_build_char_type:N
- \exp_not:c { c_regex_#1_tl }
- }
+ { \regex_build_one:n \exp_not:c { c_regex_#1_tl } }
}
% \end{macrocode}
% \end{macro}
@@ -1678,10 +1930,20 @@
% \end{macro}
% \end{macro}
% \end{macro}
-% \end{macro}
%
% \subsubsection{Character classes}
%
+% \begin{macro}[aux]{\regex_class_one:n, \regex_class_one:x}
+% This is the function used to add material
+% to the class.
+% \begin{macrocode}
+\cs_new_protected:Npn \regex_class_one:n
+ { \tl_put_right:Nn \l_regex_class_tl }
+\cs_new_protected:Npn \regex_class_one:x
+ { \tl_put_right:Nx \l_regex_class_tl }
+% \end{macrocode}
+% \end{macro}
+%
% \begin{macro}[aux]{\regex_build_[:}
% This starts a class. The code for the class is collected
% in \cs{l_regex_class_tl}. The first character is special.
@@ -1689,41 +1951,26 @@
\cs_new_protected_nopar:cpn { regex_build_[: }
{
\tl_clear:N \l_regex_class_tl
- \cs_set_eq:NN \regex_build_control:N \regex_class_control:N
- \cs_set_eq:NN \regex_build_raw:N \regex_class_raw:N
+ \cs_set_eq:NN \regex_build_raw:N \regex_class_default_raw:N
+ \tl_set:Nn \l_regex_build_or_class_tl { class }
\regex_class_first:NN
}
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}[aux]{\regex_class_control:N}
-% This function is similar to \cs{regex_build_control:N}. If the control
-% character has a meaning in character classes, call the corresponding
-% function, otherwise, treat it as a raw character, with the
-% \cs{regex_class_raw:N} function, defined later.
-% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_class_control:N #1
- {
- \cs_if_exist_use:cF { regex_class_#1: }
- { \regex_class_raw:N #1 }
- }
-% \end{macrocode}
-% \end{macro}
-%
% \begin{macro}[aux]{\regex_class_]:}
% If \texttt{]} appears as the first item of a class, then
% it doesn't end the class. Otherwise, it's the end,
% act just as for a single character, but with a more
-% complicated test. And restore \cs{regex_build_control:N}
-% and \cs{regex_build_raw:N}.
+% complicated test. And restore \cs{regex_build_raw:N}.
% \begin{macrocode}
\cs_new_protected_nopar:cpn { regex_class_]: }
{
\tl_if_empty:NTF \l_regex_class_tl %[
- { \regex_class_raw:N ] }
+ { \regex_class_default_raw:N ] }
{
- \cs_set_eq:NN \regex_build_control:N \regex_build_default_control:N
\cs_set_eq:NN \regex_build_raw:N \regex_build_default_raw:N
+ \tl_set:Nn \l_regex_build_or_class_tl { build }
\cs_set:Npn \regex_build_tmp_class:n ##1
{
\exp_not:o \l_regex_class_tl
@@ -1753,12 +2000,12 @@
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}[aux]{\regex_class_raw:N}
+% \begin{macro}[aux]{\regex_class_default_raw:N}
% \begin{macro}[aux]{\regex_class_single:N}
% Most characters are treated here. We look ahead for an unescaped dash.
% If there is none, then the character matches itself.
% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_class_raw:N #1#2#3
+\cs_new_protected_nopar:Npn \regex_class_default_raw:N #1#2#3
{
\str_if_eq:nnTF {#2#3} { \regex_build_control:N - }
{ \regex_class_range:Nw #1 }
@@ -1768,10 +2015,7 @@
}
}
\cs_new_protected_nopar:Npn \regex_class_single:N #1
- {
- \tl_put_right:Nx \l_regex_class_tl
- { \regex_item_equal:n { \int_value:w `#1 } }
- }
+ { \regex_class_one:x { \regex_item_equal:n { \int_value:w `#1 } } }
% \end{macrocode}
% \end{macro}
% \end{macro}
@@ -1804,9 +2048,9 @@
\cs_new_protected_nopar:Npn \regex_class_range_put:NN #1#2
{
\if_num:w `#1 > `#2 \exp_stop_f:
- \msg_error:nn { regex } {8}
+ \msg_kernel_error:nnxx { regex } { backwards-range } {#1} {#2}
\else:
- \tl_put_right:Nx \l_regex_class_tl
+ \regex_class_one:x
{
\if_num:w `#1 = `#2 \exp_stop_f:
\regex_item_equal:n
@@ -1834,9 +2078,59 @@
\tl_map_inline:nn { dDhHsSvVwWN }
{
\cs_new_protected_nopar:cpx { regex_class_#1: }
+ { \regex_class_one:n \exp_not:c { c_regex_#1_tl } }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsubsection{Catcodes and csnames}
+%
+% \begin{macro}[aux]{\regex_build_c:}
+% \begin{macro}[aux]{\regex_build_C:}
+% \begin{macro}[aux]{\regex_class_c:}
+% \begin{macro}[aux]{\regex_class_C:}
+% \begin{macro}[aux]{\regex_aux_c:nnNN}
+%
+% \begin{macrocode}
+\cs_new_protected_nopar:Npn \regex_build_c:
+ { \regex_aux_c:NNNN \regex_item_catcode:n \use_i:nn }
+\cs_new_protected_nopar:Npn \regex_build_C:
+ { \regex_aux_c:NNNN \regex_item_catcode_neq:n \use_ii:nn }
+\cs_new_eq:NN \regex_class_c: \regex_build_c:
+\cs_new_eq:NN \regex_class_C: \regex_build_C:
+\cs_new_protected:Npn \regex_aux_c:NNNN #1#2#3#4
+ {
+ \token_if_eq_meaning:NNTF #3 \regex_build_raw:N
+ {
+ \regex_aux_c_test:NTF #4
+ {
+ \use:c { regex_ \l_regex_build_or_class_tl _one:x }
+ { #1 { \int_use:N \l_regex_current_catcode_int } }
+ }
+ {
+ \msg_kernel_error:nnx { regex } { build-c } { #4 }
+ #3 #4
+ }
+ }
{
- \tl_put_right:Nn \exp_not:N \l_regex_class_tl
- { \exp_not:c { c_regex_#1_tl } }
+ \if_charcode:w \c_lbrace_str \exp_not:N #4
+ \exp_after:wN #2
+ \else:
+ \exp_after:wN \use_ii:nn
+ \fi:
+ {
+ \group_begin:
+ \bool_set_true:N \l_regex_in_csname_bool
+ \regex_build:w
+ }
+ {
+ \msg_kernel_error:nnx { regex } { build-c } { #4 }
+ #3 #4
+ }
}
}
% \end{macrocode}
@@ -1846,6 +2140,81 @@
% \end{macro}
% \end{macro}
%
+% The |\c| and |\C| escape sequences are used both in the
+% regex and the replacement texts. They can be followed either
+% by a capital letter representing a category code, or by a left
+% brace (in the case of |\c| only).
+%
+% \begin{macro}{\regex_aux_c_test:NTF}
+% If the first argument is a known catcode identifier
+% (capital letters listed below), then
+% \cs{l_regex_current_catcode_int} is set to the relevant
+% catcode value (between $0$ for cs and $13$ for active chars),
+% and the \texttt{true} branch is taken.
+% Otherwise, the \texttt{false} branch is taken,
+% and the current catcode is set to $-1$.
+% \begin{macrocode}
+\cs_new_protected:Npn \regex_aux_c_test:NTF #1
+ {
+ \int_set:Nn \l_regex_current_catcode_int
+ {
+ \if_case:w \int_eval:w `#1 - `A \int_eval_end:
+ \c_thirteen % Active
+ \or: \c_one % Begin-group
+ \or: \c_zero % Control sequence
+ \or: \c_eight % Down (subscript)
+ \or: \c_two % End-group
+ \else:
+ \if_case:w \int_eval:w `#1 - `L \int_eval_end:
+ \c_eleven % Letter
+ \or: \c_three % Math toggle
+ \or: \c_minus_one
+ \or: \c_twelve % Other
+ \or: \c_six % Parameter
+ \or: \c_minus_one
+ \or: \c_minus_one
+ \or: \c_ten % Space
+ \or: \c_four % Tab (alignment)
+ \or: \c_seven % Up (superscript)
+ \else: \c_minus_one
+ \fi:
+ \fi:
+ }
+ \if_num:w \l_regex_current_catcode_int = \c_minus_one
+ \exp_after:wN \use_ii:nn
+ \else:
+ \exp_after:wN \use_i:nn
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% ^^A{
+% \begin{macro}+\regex_build}:+
+% Non-escaped right braces are only special if they appear
+% when building the regular expression for a csname. Otherwise,
+% replace the brace with an escaped brace.
+% \begin{macrocode}
+\cs_new_protected:cpn { regex_build_ \c_rbrace_str : }
+ {
+ \bool_if:NTF \l_regex_in_csname_bool
+ {
+ \regex_build_Z:
+ \regex_toks_put_right:Nx \l_regex_right_state_int
+ { \regex_action_success: }
+ \int_incr:N \l_regex_capturing_group_int
+ \use:x
+ {
+ \group_end:
+ \exp_not:n { \use:c { regex_ \l_regex_build_or_class_tl _one:n } }
+ { \regex_item_cs:n { \regex_set_aux:N ? } }
+ }
+ }
+ { \exp_after:wN \regex_build_raw:N \c_rbrace_str }
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \subsubsection{Quantifiers}
%
% \begin{macro}[int]{\regex_build_quantifier:w}
@@ -1952,12 +2321,8 @@
{
\tl_if_empty:nTF {#2}
{ \regex_build_quantifier_aux:nnNN { {#1} } { n* } }
- {
- \int_compare:nNnT {#1} > {#2}
- { \msg_error:nn { regex } {4} }
- \regex_build_quantifier_aux:nnNN { {#1} {#2} } { nn }
- }
- }
+ { \regex_build_quantifier_aux:nnNN { {#1} {#2} } { nn } }
+ } %^^A todo: catch the case m>n
{
\regex_build_quantifier_end:n { }
\use:x
@@ -2190,7 +2555,7 @@
\cs_new_protected_nopar:cpn { regex_build_): }
{
\seq_if_empty:NTF \l_regex_capturing_group_seq
- { \msg_error:nn { regex } { 22 } }
+ { \msg_kernel_error:nn { regex } { extra-rparen } }
{
\regex_build_close_aux:
\regex_build_group_quantifier:
@@ -2249,7 +2614,8 @@
{
\cs_if_exist_use:cF { regex_build_special_group_\token_to_str:N #2 : }
{
- \msg_error:nn { regex } { -998 }
+ \msg_kernel_error:nnx { regex } { unsupported }
+ { (? \token_to_str:N #2 } %)
\regex_build_control:N ( % )
\regex_build_raw:N ?
#1 #2
@@ -2433,7 +2799,7 @@
{
\tex_toks:D \l_regex_tmpa_int
{
- \s_regex_stop
+ \s_stop
#1 { \int_eval:n { \l_regex_right_state_int - \l_regex_tmpa_int } }
}
}
@@ -2544,16 +2910,19 @@
\regex_toks_put_right:Nx \l_regex_right_state_int
{
\regex_action_free:n
- { \int_eval:n { \l_regex_max_state_int - \l_regex_left_state_int } }
+ {
+ \int_eval:n %^^A todo: document why that value.
+ { \l_regex_max_state_int - \c_one - \l_regex_left_state_int }
+ }
}
\int_set_eq:NN \l_regex_tmpa_int \l_regex_left_state_int
- \int_set:Nn \l_regex_tmpb_int { \l_regex_max_state_int + \c_one }
+ \int_set_eq:NN \l_regex_tmpb_int \l_regex_max_state_int
\int_set:Nn \l_regex_max_state_int
{
- \l_regex_left_state_int - \c_one
- + #1 * ( \l_regex_max_state_int - \l_regex_left_state_int + \c_one )
+ \l_regex_left_state_int
+ + #1 * ( \l_regex_max_state_int - \l_regex_left_state_int )
}
- \int_until_do:nNnn \l_regex_tmpb_int > \l_regex_max_state_int
+ \int_while_do:nNnn \l_regex_tmpb_int < \l_regex_max_state_int
{
\tex_toks:D \l_regex_tmpb_int = \tex_toks:D \l_regex_tmpa_int
\int_incr:N \l_regex_tmpa_int
@@ -2640,12 +3009,12 @@
%
% The first step in matching a regular expression is to build
% the corresponding NFA and store its states in the \tn{toks}
-% registers. Then loop through the query string one character
+% registers. Then loop through the query token list one character
% (one \enquote{step}) at a time, exploring in parallel every
% possible path through the NFA. We keep track of an array of
% the states currently \enquote{active}. More precisely,
% \tn{skip} registers hold the state numbers to be considered
-% when the next character of the string is read.
+% when the next token is read.
%
% At every step, we unpack that array of active states and
% empty it. Then loop over all active states, and perform the
@@ -2670,7 +3039,7 @@
% the \enquote{best} match. Execution paths through the NFA
% are ordered by precedence: for instance, the regular
% expression \texttt{a?} creates two paths, matching either
-% an empty string or a single \texttt{a}; the path matching
+% an empty token list or a single \texttt{a}; the path matching
% an \texttt{a} has higher precedence. When two paths collide,
% the path with the highest precedence is kept, and the other
% one is discarded. The submatch information for a given path
@@ -2704,6 +3073,7 @@
%
% The current approach means that stretch and shrink components
% of \tn{skip} registers,
+%^^A todo: update: submatches' end-points are muskips.
% as well as all \tn{muskip} registers are unused. It could seem that
% \tn{count} registers are also free for use, but we still want to be
% able to safely use integers, which are implemented as \tn{count}
@@ -2737,21 +3107,20 @@
% but not by \cs{regex_state_use_with_submatches:}.
% A state is free if it is not marker as taken, namely
% if the corresponding \tn{dimen} register is not
-% \cs{l_regex_unique_step_int} in \texttt{sp}.
+% \cs{l_regex_unique_id_int} in \texttt{sp}.
% The primitive conditional is ended before unpacking
% the \tn{toks} register.
% \begin{macrocode}
\cs_new_protected_nopar:Npn \regex_state_use_with_submatches:
{ \regex_state_use_aux:n { } }
\cs_new_protected_nopar:Npn \regex_state_use:
- { \regex_state_use_aux:n { \exp_after:wN \regex_state_use_aux_ii:w } }
-\cs_new_nopar:Npn \regex_state_use_aux_ii:w #1 \s_regex_stop { }
+ { \regex_state_use_aux:n { \exp_after:wN \use_none_delimit_by_s_stop:w } }
\cs_new_protected_nopar:Npn \regex_state_use_aux:n #1
{
\if_num:w \tex_dimen:D \l_regex_current_state_int
- < \l_regex_unique_step_int
+ < \l_regex_unique_id_int
\tex_dimen:D \l_regex_current_state_int
- = \l_regex_unique_step_int sp \scan_stop:
+ = \l_regex_unique_id_int sp \scan_stop:
#1 \tex_the:D \tex_toks:D \exp_after:wN \l_regex_current_state_int
\fi:
\scan_stop:
@@ -2799,26 +3168,26 @@
\tex_the:D \tex_toks:D #1
}
}
-\cs_new_protected:Npn \regex_store_submatches_aux:w #1 \s_regex_stop
+\cs_new_protected:Npn \regex_store_submatches_aux:w #1 \s_stop
{
\regex_store_submatches_aux_ii:Nnnw
#1
\regex_state_submatches:nn \c_minus_one \q_prop
- \s_regex_stop
+ \s_stop
}
\cs_new_protected:Npn \regex_store_submatches_aux_ii:Nnnw
- \regex_state_submatches:nn #1 #2 #3 \s_regex_stop
+ \regex_state_submatches:nn #1 #2 #3 \s_stop
{
\exp_after:wN \c_zero
\exp_after:wN \regex_state_submatches:nn \exp_after:wN
{
\int_value:w \int_eval:w
- \l_regex_unique_step_int + \c_one
+ \l_regex_unique_id_int + \c_one
\exp_after:wN
}
\exp_after:wN { \l_regex_current_submatches_prop }
\regex_state_submatches:nn {#1} {#2}
- \s_regex_stop
+ \s_stop
}
% \end{macrocode}
% \end{macro}
@@ -2832,7 +3201,7 @@
% \begin{macrocode}
\cs_new_protected:Npn \regex_state_submatches:nn #1#2
{
- \if_num:w #1 = \l_regex_unique_step_int
+ \if_num:w #1 = \l_regex_unique_id_int
\tl_set:Nn \l_regex_current_submatches_prop { #2 }
\fi:
}
@@ -2842,7 +3211,7 @@
% \subsubsection{Matching: framework}
%
% \begin{macro}[int]{\regex_match:n}
-% Store the query string in \cs{l_regex_query_other_str}.
+%^^A todo: update doc.
% Then reset a few variables which should be set only once,
% before the first match, even in the case of multiple matches.
% Then run the NFA (\cs{regex_match_once:} matches multiple times
@@ -2850,70 +3219,95 @@
% \begin{macrocode}
\cs_new_protected:Npn \regex_match:n #1
{
- \tl_set:Nx \l_regex_query_other_str { \tl_to_other_str:n {#1} }
+ \tl_set_analysis:Nn \l_regex_tmpa_tl {#1}
+
+ \int_zero:N \l_regex_nesting_int
+ \int_set_eq:NN \l_regex_current_step_int \l_regex_max_state_int
+ \regex_query_set:nnn { } { -1 } { -2 }
+ \int_set_eq:NN \l_regex_min_step_int \l_regex_current_step_int
+ \exp_after:wN \regex_query_set_loop:ww
+ \l_regex_tmpa_tl
+ \q_mark { ? \prg_map_break: } ,
+ \prg_break_point:n { }
+ \int_set_eq:NN \l_regex_max_step_int \l_regex_current_step_int
+ \regex_query_set:nnn { } { -1 } { -2 }
+
\regex_match_initial_setup:
\regex_match_once:
}
+\cs_new:Npn \regex_query_set_loop:ww #1 \q_mark #2#3 ,
+ {
+ \use_none:n #2
+ \regex_query_set:nnn {#1} {"#2} {#3}
+ \if_case:w "#2 \exp_stop_f:
+ \or: \int_incr:N \l_regex_nesting_int
+ \or: \int_decr:N \l_regex_nesting_int
+ \fi:
+ \regex_query_set_loop:ww
+ }
+\cs_new_protected:Npn \regex_query_set:nnn #1#2#3
+ {
+ \tex_muskip:D \l_regex_current_step_int
+ = \etex_gluetomu:D
+ #3 sp
+ plus #2 sp
+ minus \l_regex_nesting_int sp
+ \scan_stop:
+ \tex_toks:D \l_regex_current_step_int {#1}
+ \int_incr:N \l_regex_current_step_int
+ }
+\cs_new_protected_nopar:Npn \regex_query_get:
+ {
+ \tl_set:Nx \l_regex_current_token_tl
+ { \tex_the:D \tex_toks:D \l_regex_current_step_int }
+ \l_regex_current_char_int
+ = \etex_mutoglue:D \tex_muskip:D \l_regex_current_step_int
+ \l_regex_current_catcode_int = \etex_gluestretch:D
+ \etex_mutoglue:D \tex_muskip:D \l_regex_current_step_int
+ }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\regex_match_once:}
-% \begin{macro}[aux]{\regex_match_once_aux:}
-% \begin{macro}[aux]{\regex_match_once_aux_ii:N}
-% After setting up more variables in \cs{regex_match_setup:},
-% skip the \cs{l_regex_start_step_int} first characters of the
-% query string, and loop over it.
+% Set up more variables in \cs{regex_match_setup:}.
% If there was a match, use the token list \cs{l_regex_every_match_tl},
-% which may call \cs{regex_match_once:} to achieve multiple matches.
+% which may call \cs{regex_match_once:} again to achieve multiple matches.
% \begin{macrocode}
\cs_new_protected_nopar:Npn \regex_match_once:
{
\regex_match_setup:
- \exp_after:wN \regex_match_once_aux: \l_regex_query_other_str
- \q_recursion_tail \q_recursion_stop
- \bool_if:NT \l_regex_success_bool { \l_regex_every_match_tl }
- }
-\cs_new_protected_nopar:Npn \regex_match_once_aux:
- {
- \int_compare:nNnTF \l_regex_start_step_int = \c_zero
- {
- \int_set_eq:NN \l_regex_current_char_int \c_minus_one
- \regex_match_loop:N
- }
+ \regex_query_get:
+ \regex_match_loop:
+ \prg_break_point:n { }
+ \bool_if:NT \l_regex_success_match_bool
{
- \str_skip_do:nn
- { \l_regex_start_step_int - \c_one }
- { \regex_match_once_aux_ii:N }
+ \bool_gset_true:N \g_regex_success_bool
+ \l_regex_every_match_tl
}
}
-\cs_new_protected_nopar:Npn \regex_match_once_aux_ii:N #1
- {
- \int_set:Nn \l_regex_current_char_int { `#1 }
- \regex_match_loop:N
- }
% \end{macrocode}
% \end{macro}
-% \end{macro}
-% \end{macro}
%
% \begin{macro}[aux]{\regex_match_initial_setup:}
% This function holds the setup that should be done
-% only once for one given pattern matching on a given
-% string. It is called only once for the whole string.
+% only once for one given pattern matching.
+% It is called only once for the whole token list.
% On the other hand, \cs{regex_match_setup:}
-% is called for every match in the string in case of
-% repeated matches, and \cs{regex_match_loop_setup:N}
-% is called at every step.
+% is called for every match in the token list in case of
+% repeated matches.
% \begin{macrocode}
\cs_new_protected_nopar:Npn \regex_match_initial_setup:
{
- \prg_stepwise_inline:nnnn {1} {1} { \l_regex_max_state_int }
+ \prg_stepwise_inline:nnnn {0} {1} { \l_regex_max_state_int - \c_one }
{ \tex_dimen:D ##1 \c_minus_one sp \scan_stop: }
- \int_set_eq:NN \l_regex_unique_step_int \c_minus_one
- \int_set_eq:NN \l_regex_start_step_int \c_minus_one
- \int_set_eq:NN \l_regex_current_step_int \c_zero
- \int_set_eq:NN \l_regex_success_step_int \c_zero
+ \int_set_eq:NN \l_regex_unique_id_int \c_minus_one
+ \int_set:Nn \l_regex_start_step_int
+ { \l_regex_min_step_int - \c_one }
+ \int_set_eq:NN \l_regex_current_step_int \l_regex_min_step_int
+ \int_set_eq:NN \l_regex_success_step_int \l_regex_min_step_int
+ \int_set_eq:NN \l_regex_submatch_int \l_regex_max_state_int
\bool_set_false:N \l_regex_success_empty_bool
+ \bool_gset_false:N \g_regex_success_bool
}
% \end{macrocode}
% \end{macro}
@@ -2929,47 +3323,61 @@
{ \cs_set_eq:NN \regex_last_match_empty:F \regex_last_match_empty_yes:F }
{ \cs_set_eq:NN \regex_last_match_empty:F \regex_last_match_empty_no:F }
\int_set_eq:NN \l_regex_start_step_int \l_regex_success_step_int
- \int_set_eq:NN \l_regex_current_step_int \l_regex_start_step_int
- \int_decr:N \l_regex_current_step_int
- \bool_set_false:N \l_regex_success_bool
+ \int_set:Nn \l_regex_current_step_int
+ { \l_regex_start_step_int - \c_one }
+ \bool_set_false:N \l_regex_success_match_bool
\int_zero:N \l_regex_max_index_int
- \regex_store_state:n {1}
+ \regex_store_state:n {0} %^^A _state_int!
}
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}[aux]{\regex_match_loop:N}
+% \begin{macro}[aux]{\regex_match_loop:}
% \begin{macro}[aux]{\regex_match_one_index:n}
% \begin{macro}[aux]{\regex_match_one_index_aux:n}
% Setup what needs to be reset at every character,
% then set \cs{l_regex_current_char_int} to the
-% character code of the character that is read
-% (and $-1$ for the end of the string), and loop
+% character code of the token that is read
+% (and $-1$ for the end of the token list), and loop
% over the elements of the \tn{skip} array. Then repeat.
-% There are a couple of tests to stop reading the string
+% There are a couple of tests to stop reading the token list
% when no active state is left, or when the end is reached.
+% At every step in reading the token list, we store the character
+% code of the current character in \cs{l_regex_current_char_int},
+% unless the end was reached: then we store $-1$.
% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_match_loop:N #1
+\cs_new_protected_nopar:Npn \regex_match_loop:
{
- \regex_match_loop_setup:N #1
+ \int_incr:N \l_regex_current_step_int
+ \int_incr:N \l_regex_unique_id_int
+ \bool_set_false:N \l_regex_fresh_thread_bool
+ \int_set_eq:NN \l_regex_last_char_int \l_regex_current_char_int
+ \regex_query_get:
+ \regex_match_loop_case_hook:
\cs_set_nopar:Npx \regex_tmp:w
{
\int_zero:N \l_regex_max_index_int
- \prg_stepwise_function:nnnN
- {1} {1} { \l_regex_max_index_int }
- \regex_match_one_index:n
+ \regex_match_one_index:w 1 ; \prg_break_point:n { }
+ \exp_not:N \prg_break_point:n { }
}
- \regex_tmp:w \prg_break_point:n { }
+ \regex_tmp:w
+ \if_num:w \l_regex_current_char_int < \c_minus_one
+ \exp_after:wN \prg_map_break:
+ \fi:
\if_num:w \l_regex_max_index_int = \c_zero
- \exp_after:wN \use_none_delimit_by_q_recursion_stop:w
+ \exp_after:wN \prg_map_break:
\fi:
- \quark_if_recursion_tail_stop:N #1
- \regex_match_loop:N
+ \regex_match_loop:
}
-\cs_new_nopar:Npn \regex_match_one_index:n #1
+\cs_new_nopar:Npn \regex_match_one_index:w #1;
{
+ \if_num:w #1 > \l_regex_max_index_int
+ \exp_after:wN \prg_map_break:
+ \fi:
\regex_match_one_index_aux:n
{ \int_value:w \tex_skip:D #1 }
+ \exp_after:wN \regex_match_one_index:w
+ \int_use:N \int_eval:w #1 + \c_one ;
}
\cs_new_protected_nopar:Npn \regex_match_one_index_aux:n #1
{
@@ -2982,28 +3390,6 @@
% \end{macro}
% \end{macro}
%
-% \begin{macro}[aux]{\regex_match_loop_setup:N}
-% This is called for every character in the string.
-% At every step in reading the query string, we store the character
-% code of the current character in \cs{l_regex_current_char_int},
-% unless the end was reached: then we store $-1$.
-% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_match_loop_setup:N #1
- {
- \int_incr:N \l_regex_current_step_int
- \int_incr:N \l_regex_unique_step_int
- \bool_set_false:N \l_regex_fresh_thread_bool
- \int_set_eq:NN \l_regex_last_char_int \l_regex_current_char_int
- \if_meaning:w #1 \q_recursion_tail
- \int_set_eq:NN \l_regex_current_char_int \c_minus_one
- \else:
- \int_set:Nn \l_regex_current_char_int {`#1}
- \fi:
- \regex_match_loop_case_hook:
- }
-% \end{macrocode}
-% \end{macro}
-%
% \begin{macro}{\regex_match_loop_case_hook:}
% \begin{macro}{\regex_match_loop_caseless_hook:}
% In the case where the regular expression contains caseless matching,
@@ -3041,7 +3427,7 @@
% The search is made unanchored at the start by putting
% a free transition to the real start of the NFA, and a
% costly transition to the same state, waiting for the
-% next character in the query string. This combination
+% next token in the query. This combination
% could be reused (with some changes). We sometimes need
% to know that the match for a given thread starts at
% this character. For that, we use the boolean
@@ -3063,7 +3449,7 @@
% \begin{macrocode}
\cs_new_protected_nopar:Npn \regex_action_cost:n #1
{
- \exp_args:Nf \regex_store_state:n %^^A optimize!
+ \exp_args:Nf \regex_store_state:n
{ \int_eval:n { \l_regex_current_state_int + #1 } }
}
% \end{macrocode}
@@ -3082,11 +3468,10 @@
{
\regex_last_match_empty:F
{
- \bool_set_true:N \l_regex_success_bool
+ \bool_set_true:N \l_regex_success_match_bool
\bool_set_eq:NN \l_regex_success_empty_bool
\l_regex_fresh_thread_bool
- \int_set_eq:NN \l_regex_success_step_int
- \l_regex_current_step_int
+ \int_set_eq:NN \l_regex_success_step_int \l_regex_current_step_int
\prop_set_eq:NN \l_regex_success_submatches_prop
\l_regex_current_submatches_prop
\prg_map_break:
@@ -3131,46 +3516,252 @@
% \end{macrocode}
% \end{macro}
%
-% \subsection{Submatches, once the correct match is found}
+% \subsection{Replacement}
%
-% \begin{macro}[int]{\regex_extract:}
-% \begin{macro}[aux]{\regex_extract_aux:nTF}
+% \begin{macro}[rEXP]{\regex_submatch_nesting_aux:n}
% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_extract:
+\cs_new_protected:Npn \regex_submatch_nesting_aux:n #1
+ {
+ + \etex_glueshrink:D \etex_mutoglue:D \etex_muexpr:D
+ \tex_muskip:D \etex_gluestretch:D \tex_skip:D #1
+ - \tex_muskip:D \tex_skip:D #1
+ \scan_stop:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{\regex_replacement:n}
+% Our goal here is to analyse the replacement text. First take care
+% of detecting escaped and non-escaped characters using
+% \cs{str_escape_use:NNNn} with three protected arguments.
+% This inserts in the input stream a token list of the form
+% \meta{fn 1} \meta{char 1} \ldots{} \meta{fn $N$} \meta{char $N$},
+% where \meta{fn $i$} is one of the three functions, and \meta{char $i$}
+% a character in the string |#1|.
+% \begin{macrocode}
+\cs_new:Npn \regex_nesting:n #1 { } %^^A move. Rename?
+\tl_new:N \l_regex_nesting_tl
+\cs_new_protected:Npn \regex_replacement:n #1
{
- \seq_gclear:N \g_regex_submatches_seq
- \prg_stepwise_inline:nnnn
- {0} {1} { \l_regex_capturing_group_int }
+ \int_zero:N \l_regex_replacement_int
+ \int_zero:N \l_regex_nesting_int
+ \tl_clear:N \l_regex_nesting_tl
+ \str_escape_use:NNNn
+ \regex_replacement_unescaped:N
+ \regex_replacement_escaped:N
+ \regex_replacement_raw:N
+ {#1}
+ \prg_do_nothing: \prg_do_nothing:
+ \cs_set_nopar:Npx \regex_nesting:n ##1
{
- \regex_extract_aux:nTF { ##1 }
- {
- \seq_gput_right:Nx \g_regex_submatches_seq
- { \regex_query_substr:NN \l_regex_tmpa_tl \l_regex_tmpb_tl }
- }
- { \seq_gput_right:Nn \g_regex_submatches_seq { } }
+ + \int_use:N \l_regex_nesting_int
+ \l_regex_nesting_tl
+ - \regex_submatch_nesting_aux:n {##1}
+ }
+ \use:x
+ {
+ \exp_not:n { \cs_set_nopar:Npn \regex_replacement_tl:n ##1 }
+ { \str_aux_toks_range:nn \c_zero \l_regex_replacement_int }
+ }
+ % ^^A rename! Careful with \cP\#.
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[aux]{\regex_replacement_raw:N}
+% \begin{macrocode}
+\cs_new_protected:Npn \regex_replacement_raw:N #1
+ { \regex_replacement_put:n {#1} }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[aux]{\regex_replacement_put:n}
+% Raw characters are stored in a toks register.
+% \begin{macrocode}
+\cs_new_protected:Npn \regex_replacement_put:n #1
+ {
+ \tex_toks:D \l_regex_replacement_int {#1}
+ \int_incr:N \l_regex_replacement_int
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[aux]{\regex_replacement_escaped:N}
+% \begin{macro}[aux]{\regex_replacement_submatch:w}
+% \begin{macro}[aux]{\regex_replacement_submatch_aux:nN}
+% \begin{macrocode}
+\cs_new_protected:Npn \regex_replacement_unescaped:N #1
+ {
+ \if_charcode:w \c_rbrace_str #1
+ \if_num:w \l_regex_replacement_csnames_int > \c_zero
+ \regex_replacement_put:n \cs_end:
+ \else:
+ \regex_replacement_put:n #1
+ \fi:
+ \else:
+ \regex_replacement_put:n #1
+ \fi:
+ }
+\cs_new_protected:Npn \regex_replacement_escaped:N #1
+ {
+ \if_charcode:w c #1
+ \exp_after:wN \regex_replacement_c:w
+ \else:
+ \if_charcode:w g #1
+ \exp_after:wN \exp_after:wN \exp_after:wN \regex_replacement_g:w
+ \else:
+ \if_num:w \c_one < 1#1 \exp_stop_f:
+ \regex_replacement_put_submatch:n {#1}
+ \else:
+ \regex_replacement_put:n #1
+ \fi:
+ \fi:
+ \fi:
+ }
+\cs_new_protected:Npn \regex_replacement_put_submatch:n #1
+ {
+ \regex_replacement_put:n
+ { \regex_query_submatch:nn {#1} {##1} }
+ \if_num:w \l_regex_replacement_csnames_int = \c_zero
+ \tl_put_right:Nn \l_regex_nesting_tl
+ {
+ \exp_not:N \if_num:w #1 < \l_regex_capturing_group_int
+ \regex_submatch_nesting_aux:n { \int_eval:w #1+##1 \int_eval_end: }
+ \exp_not:N \fi:
+ }
+ \fi:
+ }
+\cs_new_protected:Npn \regex_replacement_error:NNN #1#2#3
+ {
+ \msg_kernel_error:nn { regex } { replacement-#1 }
+ #2 #3
+ }
+\cs_new_protected:Npn \regex_replacement_g:w #1#2
+ {
+ \str_if_eq:xxTF
+ { \exp_not:n { #1#2 } }
+ { \regex_replacement_unescaped:N \c_lbrace_str }
+ {
+ \int_zero:N \l_regex_tmpa_int
+ \regex_replacement_g_digits:NN
}
+ { \regex_replacement_error:NNN g #1 #2 }
}
-\cs_new_protected_nopar:Npn \regex_extract_aux:nTF #1#2#3
+\cs_new_protected:Npn \regex_replacement_g_digits:NN #1#2
{
- \prop_get:NnNTF \l_regex_success_submatches_prop
- { #1 < } \l_regex_tmpa_tl
+ \token_if_eq_meaning:NNTF #1 \regex_replacement_unescaped:N
{
- \prop_get:NnNTF \l_regex_success_submatches_prop
- { #1 > } \l_regex_tmpb_tl
- {#2}
- {#3}
+ \if_num:w \c_one < 1#2 \exp_stop_f:
+ \int_set:Nn \l_regex_tmpa_int { \c_ten * \l_regex_tmpa_int + #2 }
+ \exp_after:wN \use_i:nnn
+ \exp_after:wN \regex_replacement_g_digits:NN
+ \else:
+ \if_charcode:w \c_rbrace_str #2
+ \exp_args:No \regex_replacement_put_submatch:n
+ { \int_use:N \l_regex_tmpa_int }
+ \exp_after:wN \exp_after:wN \exp_after:wN \use_none:nn
+ \else:
+ \exp_after:wN \exp_after:wN
+ \exp_after:wN \regex_replacement_error:NNN
+ \exp_after:wN \exp_after:wN \exp_after:wN g
+ \fi:
+ \fi:
}
- {#3}
+ { \regex_replacement_error:NNN g }
+ #1 #2
+ }
+\cs_new_protected_nopar:Npn \regex_replacement_c:w #1#2
+ {
+ \token_if_eq_meaning:NNTF #1 \regex_replacement_unescaped:N
+ {
+ \cs_if_exist_use:cF { regex_replacement_c_#2:w }
+ { \regex_replacement_error:NNN c #1#2 }
+ }
+ { \regex_replacement_error:NNN c #1#2 }
+ }
+\cs_new_protected_nopar:cpn { regex_replacement_c_ \c_lbrace_str :w }
+ {
+ \int_incr:N \l_regex_replacement_csnames_int
+ \regex_replacement_put:n
+ { \exp_not:n { \exp_after:wN \regex_replacement_exp_not:N \cs:w } }
+ }
+\cs_new_nopar:Npn \regex_replacement_exp_not:N #1 { \exp_not:n {#1} }
+\group_begin:
+
+ \char_set_catcode_math_superscript:N \^^@
+ \cs_new_protected_nopar:Npn \regex_replacement_c_U:w
+ { \regex_replacement_char:nNN { ^^@ } }
+
+ \char_set_catcode_alignment:N \^^@
+ \cs_new_protected_nopar:Npn \regex_replacement_c_T:w
+ { \regex_replacement_char:nNN { ^^@ } }
+
+ \cs_new_protected_nopar:Npn \regex_replacement_c_S:w #1#2
+ {
+ \int_compare:nNnTF { `#2 } = \c_zero
+ { \regex_replacement_error:NNN c #1#2 }
+ {
+ \char_set_lccode:nn {32} { `#2 }
+ \tl_to_lowercase:n { \regex_replacement_put:n {~} }
+ }
+ }
+
+ \char_set_catcode_parameter:N \^^@
+ \cs_new_protected_nopar:Npn \regex_replacement_c_P:w
+ { \regex_replacement_char:nNN { ^^@^^@ } }
+
+ \char_set_catcode_other:N \^^@
+ \cs_new_protected_nopar:Npn \regex_replacement_c_O:w
+ { \regex_replacement_char:nNN { ^^@ } }
+
+ \char_set_catcode_math_toggle:N \^^@
+ \cs_new_protected_nopar:Npn \regex_replacement_c_M:w
+ { \regex_replacement_char:nNN { ^^@ } }
+
+ \char_set_catcode_letter:N \^^@
+ \cs_new_protected_nopar:Npn \regex_replacement_c_L:w
+ { \regex_replacement_char:nNN { ^^@ } }
+
+ \char_set_catcode_group_end:N \^^@
+ \cs_new_protected_nopar:Npn \regex_replacement_c_E:w
+ {
+ \int_decr:N \l_regex_nesting_int
+ \regex_replacement_char:nNN { \if_false: { \fi: ^^@ }
+ }
+
+ \char_set_catcode_math_subscript:N \^^@
+ \cs_new_protected_nopar:Npn \regex_replacement_c_D:w
+ { \regex_replacement_char:nNN { ^^@ } }
+
+ \char_set_catcode_group_begin:N \^^@
+ \cs_new_protected_nopar:Npn \regex_replacement_c_B:w
+ {
+ \int_incr:N \l_regex_nesting_int
+ \regex_replacement_char:nNN { \exp_after:wN ^^@ \if_false: } \fi: }
+ }
+
+ \char_set_catcode_active:N \^^@
+ \cs_new_protected_nopar:Npn \regex_replacement_c_A:w
+ { \regex_replacement_char:nNN { \exp_not:N ^^@ } }
+
+\group_end:
+
+\cs_new_protected_nopar:Npn \regex_replacement_char:nNN #1#2#3
+ {
+ \char_set_lccode:nn \c_zero { `#3 }
+ \tl_to_lowercase:n
+ { \regex_replacement_put:n { \exp_not:n {#1} } }
}
% \end{macrocode}
% \end{macro}
% \end{macro}
+% \end{macro}
%
% \subsection{User commands}
%
% \subsubsection{Precompiled pattern}
%
-% A given pattern is often reused to match many different strings.
+% A given pattern is often reused to match many different query token lists.
% We thus give a means of storing the NFA corresponding to a given
% pattern in a token list variable of the form
% \begin{quote}
@@ -3179,7 +3770,7 @@
% \cs{tex_toks:D} 0 \{ \meta{instruction0} \} \\
% \ldots{} \\
% \cs{tex_toks:D} $n$ \{ \meta{instruction$\sb{n}$} \} \\
-% \cs{s_regex_stop}
+% \cs{s_stop}
% \end{quote}
% where $n$ is the number of states in the NFA,
% and the various \meta{instruction$\sb{i}$} control
@@ -3191,672 +3782,678 @@
% contents of \meta{nfa var} may overwrite data which is used
% elsewhere, unless everything is done carefully in a group.
%
-% \begin{macro}{\regex_set:Nn}
% \begin{macro}{\regex_gset:Nn}
+% \begin{macro}{\regex_const:Nn}
+% \begin{macro}{\regex_set:Nn}
% \begin{macro}[aux]{\regex_set_aux:NNn}
-% \begin{macro}[aux]{\regex_set_aux:n}
-% \begin{macro}[aux]{\regex_nfa:Nw}
-% Within a group, build the NFA corresponding to the given regular
-% expression, with submatch tracking. Then save the contents of all
-% relevant \tn{toks} registers into \cs{g_regex_tmpa_tl}, then
-% transferred to the user's tl variable.
-% The auxiliary \cs{regex_nfa:Nw} is not protected: this ensures that
-% the NFA will properly be replaced by an error message in expansion
-% contexts.
+% The three user functions only differ with which function is used
+% to assign the pre-compiled regular expression to the user's variable.
+% Internally, they all first build the NFA corresponding to the regex,
+% then store the contents of all the necessary \tn{toks} registers
+% in the user's variable.
% \begin{macrocode}
\cs_new_protected_nopar:Npn \regex_set:Nn
- { \regex_set_aux:NNn \tl_set_eq:NN }
+ { \regex_set_aux:NNn \tl_set:Nn }
\cs_new_protected_nopar:Npn \regex_gset:Nn
- { \regex_set_aux:NNn \tl_gset_eq:NN }
+ { \regex_set_aux:NNn \tl_gset:Nn }
+\cs_new_protected_nopar:Npn \regex_const:Nn
+ { \regex_set_aux:NNn \tl_const:Nn }
\cs_new_protected:Npn \regex_set_aux:NNn #1#2#3
{
\group_begin:
\regex_build:n {#3}
- \tl_gset:Nx \g_regex_tmpa_tl
- {
- \exp_not:N \regex_nfa:Nw \exp_not:N #2
- \l_regex_max_state_int
- = \int_use:N \l_regex_max_state_int
- \l_regex_capturing_group_int
- = \int_use:N \l_regex_capturing_group_int
- \token_if_eq_meaning:NNT
- \regex_match_loop_case_hook:
- \regex_match_loop_caseless_hook:
- {
- \cs_set_eq:NN \regex_match_loop_case_hook:
- \regex_match_loop_caseless_hook:
- }
- \prg_stepwise_function:nnnN
- {1} {1} {\l_regex_max_state_int}
- \regex_set_aux:n
- \s_regex_stop
- }
+ \cs_set_nopar:Npx \regex_tmp:w
+ { #1 \exp_not:N #2 { \regex_set_aux:N #2 } }
+ \exp_after:wN
\group_end:
- #1 #2 \g_regex_tmpa_tl
+ \regex_tmp:w
}
-\cs_new_nopar:Npn \regex_set_aux:n #1
- { \tex_toks:D #1 { \tex_the:D \tex_toks:D #1 } }
-\cs_new:Npn \regex_nfa:Nw #1 #2 \s_regex_stop
- { \msg_expandable_kernel_error:nnn { regex } { nfa-misused } {#1} }
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
-% \end{macro}
%
-% \begin{macro}{\regex_const:Nn}
-% The same idea as for setting, but using the new function.
-% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_const:Nn
- { \regex_set_aux:NNn \cs_new_eq:NN }
-% \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}[int,TF]{\regex_check_nfa:N}
-% If a token list variable starts with \cs{regex_nfa:Nw},
-% then it most likely holds the data for a precompiled pattern.
+% \begin{macro}[int, rEXP]{\regex_set_aux:N}
+% \begin{macro}[aux, rEXP]{\regex_set_aux:n}
+% \begin{macro}[aux, EXP]{\regex_nfa:Nw}
+% Within a group, build the NFA corresponding to the given regular
+% expression, with submatch tracking. Then save the contents of all
+% relevant \tn{toks} registers into the variable outside the group.
+% The auxiliary \cs{regex_nfa:Nw} is not protected: this ensures that
+% the NFA will properly be replaced by an error message in expansion
+% contexts.
% \begin{macrocode}
-\prg_new_protected_conditional:Npnn \regex_check_nfa:N #1 { T , TF }
- { \exp_after:wN \regex_check_nfa_aux:Nw #1 \q_stop }
-\cs_new:Npn \regex_check_nfa_aux:Nw #1 #2 \q_stop
+\cs_new:Npn \regex_set_aux:N #1
+ {
+ \exp_not:n { \regex_nfa:Nw #1 }
+ \l_regex_max_state_int
+ = \int_use:N \l_regex_max_state_int
+ \l_regex_capturing_group_int
+ = \int_use:N \l_regex_capturing_group_int
+ \token_if_eq_meaning:NNT
+ \regex_match_loop_case_hook:
+ \regex_match_loop_caseless_hook:
+ {
+ \cs_set_eq:NN \regex_match_loop_case_hook:
+ \regex_match_loop_caseless_hook:
+ }
+ \prg_stepwise_function:nnnN
+ {0} {1} {\l_regex_max_state_int - \c_one }
+ \regex_set_aux:n
+ \s_stop
+ }
+\cs_new_nopar:Npn \regex_set_aux:n #1
+ { \tex_toks:D #1 { \tex_the:D \tex_toks:D #1 } }
+\cs_new:Npn \regex_nfa:Nw #1
{
- \if_meaning:w \regex_nfa:Nw #1
- \prg_return_true:
- \else:
- \msg_error:nnx { regex } { not-nfa } { \token_to_str:N #1 }
- \prg_return_false:
- \fi:
+ \msg_expandable_kernel_error:nnn { regex } { nfa-misused } {#1}
+ \use_none_delimit_by_s_stop:w
}
% \end{macrocode}
% \end{macro}
+% \end{macro}
+% \end{macro}
%
% \begin{macro}[int]{\regex_use:N}
% No error-checking.
% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_use:N #1
- { \exp_after:wN \use_none:nn #1 }
+\cs_new_protected_nopar:Npn \regex_use:N
+ { \exp_last_unbraced:No \use_none:nn }
% \end{macrocode}
% \end{macro}
%
-% \subsubsection{Generic auxiliary functions}
-%
-% \begin{macro}[aux]{\regex_user_aux:n}
-% This is an auxiliary used by most user functions.
-% The first and second arguments control whether we should track
-% submatches, and whether we track one or multiple submatches.
-% Everything is done within a group, so that |#1| can perform
-% \enquote{unsafe} assignments. Most user functions return a
-% result using \cs{group_insert_after:N}.
+% \begin{macro}[int, rEXP]{\regex_to_str:N}
+% \begin{macro}[int, rEXP]{\regex_to_str_aux:w}
+% Of course, we could simply set \cs{regex_to_str:N} equal
+% to \cs{tl_to_str:N}. After all, regex variables are in particular
+% token list variables. We do some more processing to start each
+% line with \cs{tex_toks:D} instead of a single very long line.
% \begin{macrocode}
-\cs_new_protected:Npn \regex_user_aux:n #1
+\cs_new_nopar:Npn \regex_to_str:N #1
{
- \group_begin:
- \tl_clear:N \l_regex_every_match_tl
- #1
- \group_end:
+ \exp_after:wN \regex_to_str_aux:Nw #1
+ \tex_toks:D \q_stop \prg_map_break: \tex_toks:D
+ \prg_break_point:n { }
+ }
+\cs_new_nopar:Npn \regex_to_str_aux:Nw #1#2\tex_toks:D
+ {
+ \use_none_delimit_by_q_stop:w #2 \q_stop
+ \tl_to_str:n {#1#2} \iow_newline:
+ \regex_to_str_aux:Nw \tex_toks:D
}
% \end{macrocode}
% \end{macro}
+% \end{macro}
+%
+% \subsubsection{Generic auxiliary functions}
+%
+% Most of \pkg{l3regex}'s work is done within a group.
%
-% \begin{macro}[aux]{\regex_return_after_group:}
-% Most of \pkg{l3regex}'s work is done within a group.
+% \begin{macro}[aux]{\regex_aux_return:}
% This function triggers either \cs{prg_return_false:}
% or \cs{prg_return_true:} as appropriate to whether a
% match was found or not.
% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_return_after_group:
+\cs_new_protected_nopar:Npn \regex_aux_return:
{
- \if_meaning:w \c_true_bool \l_regex_success_bool
- \group_insert_after:N \prg_return_true:
+ \if_meaning:w \c_true_bool \g_regex_success_bool
+ \prg_return_true:
\else:
- \group_insert_after:N \prg_return_false:
- \fi:
- }
-% \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}[aux]{\regex_extract_after_group:N}
-% Extract submatches, and store them in the user-given variable
-% after the group has ended.
-% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_extract_after_group:N #1
- {
- \if_meaning:w \c_true_bool \l_regex_success_bool
- \regex_extract:
- \group_insert_after:N \seq_set_eq:NN
- \group_insert_after:N #1
- \group_insert_after:N \g_regex_submatches_seq
+ \prg_return_false:
\fi:
}
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}[aux]{\regex_count_after_group:N}
-% Same procedure as \cs{regex_extract_after_group:N}, but simpler
-% since the match counting has already taken place.
+% \begin{macro}{\regex_aux_build_match:nn}
+% This auxiliary is used by user functions whose \meta{regex} argument
+% is given as an explicit regular expression within braces. In that
+% case, we need to build the automaton corresponding to that regular
+% expression, then perform the matching on the given token list |#2|.
% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_count_after_group:N #1
+\cs_new_protected:Npn \regex_aux_build_match:nn #1#2
{
- \group_insert_after:N \int_set_eq:NN
- \group_insert_after:N #1
- \group_insert_after:N \g_regex_match_count_int
+ \regex_build:n {#1}
+ \regex_match:n {#2}
}
% \end{macrocode}
% \end{macro}
%
-% \subsubsection{Matching}
-%
-% \begin{macro}[TF]{\regex_match:nn}
-% \begin{macro}[TF]{\regex_match:Nn}
-% We don't track submatches. Then either build the NFA corresponding
-% to the regular expression, or use a precompiled pattern. Then match,
-% using the internal \cs{regex_match:n}. Finally return the result
-% after closing the group.
+% \begin{macro}{\regex_aux_use_match:Nn}
+% This auxiliary is used by user functions whose \meta{regex} argument
+% is given as a pre-compiled regex variable. We make sure that
+% the token list variable indeed is an automaton (by testing the
+% first token). If not, the match is deemed unsuccessful, after
+% raising an error. If we have an automaton, \enquote{use} it,
+% then perform the matching on the given token list |#2|.
% \begin{macrocode}
-\prg_new_protected_conditional:Npnn \regex_match:nn #1#2 { T , F , TF }
+\cs_new_protected:Npn \regex_aux_use_match:Nn #1#2
{
- \regex_user_aux:n
+ \exp_args:No \tl_if_head_eq_meaning:nNTF {#1} \regex_nfa:Nw
{
- \regex_disable_submatches:
- \regex_build:n {#1}
+ \regex_use:N #1
\regex_match:n {#2}
- \regex_return_after_group:
}
- }
-\prg_new_protected_conditional:Npnn \regex_match:Nn #1#2 { T , F , TF }
- {
- \regex_check_nfa:NTF #1
{
- \regex_user_aux:n
- {
- \regex_disable_submatches:
- \regex_use:N #1
- \regex_match:n {#2}
- \regex_return_after_group:
- }
+ \msg_kernel_error:nnx { regex } { not-nfa } { \token_to_str:N #1 }
+ \bool_gset_false:N \g_regex_success_bool
}
- { \prg_return_false: }
}
% \end{macrocode}
% \end{macro}
-% \end{macro}
%
-% \begin{macro}{\regex_count:nnN}
-% \begin{macro}{\regex_count:NnN}
-% Instead of aborting once the first \enquote{best match} is found,
-% we repeat the search. The code is such that the search will not
-% start on the same character, hence avoiding infinite loops.
+% \begin{macro}
+% {
+% \regex_extract_once:nnN, \regex_extract_once:NnN,
+% \regex_extract_all:nnN, \regex_extract_all:NnN,
+% \regex_replace_once:nnN, \regex_replace_once:NnN,
+% \regex_replace_all:nnN, \regex_replace_all:NnN,
+% \regex_split:nnN, \regex_split:NnN
+% }
+% \begin{macro}[TF]
+% {
+% \regex_extract_once:nnN, \regex_extract_once:NnN,
+% \regex_extract_all:nnN, \regex_extract_all:NnN,
+% \regex_replace_once:nnN, \regex_replace_once:NnN,
+% \regex_replace_all:nnN, \regex_replace_all:NnN,
+% \regex_split:nnN, \regex_split:NnN
+% }
+% We define here $40$ user functions, following a common pattern
+% in terms of an auxiliary such as \cs{regex_extract_once_aux:NnnN}
+% (those auxiliaries are defined in the coming sections).
+% The arguments handed to the auxiliary are
+% \cs{regex_aux_build_match:nn} or \cs{regex_aux_use_match:Nn},
+% followed by the three arguments of the user function.
+% The conditionals call \cs{regex_aux_return:} to return
+% either \texttt{true} or \texttt{false} once matching
+% has been performed.
% \begin{macrocode}
-\cs_new_protected:Npn \regex_count:nnN #1#2#3
+\cs_set_protected:Npn \regex_tmp:w #1#2#3
{
- \regex_user_aux:n
+ \cs_new_protected_nopar:Npn #1
+ { #3 \regex_aux_build_match:nn }
+ \cs_new_protected_nopar:Npn #2
+ { #3 \regex_aux_use_match:Nn }
+ \prg_new_protected_conditional:Npnn #1 ##1##2##3 { T , F , TF }
{
- \regex_disable_submatches:
- \int_gzero:N \g_regex_match_count_int
- \tl_set:Nn \l_regex_every_match_tl
- {
- \int_gincr:N \g_regex_match_count_int
- \regex_match_once:
- }
- \regex_build:n {#1}
- \regex_match:n {#2}
- \regex_count_after_group:N #3
+ #3 \regex_aux_build_match:nn {##1} {##2} ##3
+ \regex_aux_return:
}
- }
-\cs_new_protected:Npn \regex_count:NnN #1#2#3
- {
- \regex_check_nfa:NT #1
+ \prg_new_protected_conditional:Npnn #2 ##1##2##3 { T , F , TF }
{
- \regex_user_aux:n
- {
- \regex_disable_submatches:
- \int_gzero:N \g_regex_match_count_int
- \tl_set:Nn \l_regex_every_match_tl
- {
- \int_gincr:N \g_regex_match_count_int
- \regex_match_once:
- }
- \regex_use:N #1
- \regex_match:n {#2}
- \regex_count_after_group:N #3
- }
+ #3 \regex_aux_use_match:Nn {##1} {##2} ##3
+ \regex_aux_return:
}
}
+\tl_map_inline:nn
+ {
+ { extract_once } { extract_all }
+ { replace_once } { replace_all }
+ { split }
+ }
+ {
+ \exp_args:Nccc \regex_tmp:w
+ { regex_#1:nnN } { regex_#1:NnN } { regex_#1_aux:NnnN }
+ }
% \end{macrocode}
% \end{macro}
% \end{macro}
%
-% \subsubsection{Submatch extraction}
+% \subsubsection{Submatches, once the correct match is found}
+%
+% \begin{macro}[int]{\regex_extract:}
+% \begin{macro}[aux]{\regex_extract_aux_b:wn}
+% \begin{macro}[aux]{\regex_extract_aux_e:wn}
%
-% \begin{macro}{\regex_extract_once:nnN}
-% \begin{macro}{\regex_extract_once:NnN}
-% \begin{macro}[TF]{\regex_extract_once:nnN}
-% \begin{macro}[TF]{\regex_extract_once:NnN}
% \begin{macrocode}
-\cs_new_protected:Npn \regex_extract_once:nnN #1#2#3
+\cs_new_protected_nopar:Npn \regex_extract:
{
- \regex_user_aux:n
- {
- \regex_build:n {#1}
- \regex_match:n {#2}
- \regex_extract_after_group:N #3
- }
+ \int_set_eq:NN \l_regex_submatch_start_int \l_regex_submatch_int
+ \if_meaning:w \c_true_bool \g_regex_success_bool
+ \prg_replicate:nn \l_regex_capturing_group_int
+ {
+ \tex_skip:D \l_regex_submatch_int \c_zero sp \scan_stop:
+ \int_incr:N \l_regex_submatch_int
+ }
+ \prop_map_inline:Nn \l_regex_success_submatches_prop
+ {
+ \if_num:w ##1 \c_max_int
+ \exp_after:wN \regex_extract_aux_b:wn \int_use:N
+ \else:
+ \exp_after:wN \regex_extract_aux_e:wn \int_use:N
+ \fi:
+ \int_eval:w \l_regex_submatch_start_int + ##1 {##2}
+ }
+ \fi:
}
-\prg_new_protected_conditional:Npnn \regex_extract_once:nnN #1#2#3
- { T , F , TF }
+\cs_new_protected_nopar:Npn \regex_extract_aux_b:wn #1 < #2
{
- \regex_user_aux:n
- {
- \regex_build:n {#1}
- \regex_match:n {#2}
- \regex_extract_after_group:N #3
- \regex_return_after_group:
- }
+ \tex_skip:D #1 = #2 sp
+ plus \etex_gluestretch:D \tex_skip:D #1 \scan_stop:
}
-\cs_new_protected:Npn \regex_extract_once:NnN #1#2#3
+\cs_new_protected_nopar:Npn \regex_extract_aux_e:wn #1 > #2
{
- \regex_check_nfa:NT #1
- {
- \regex_user_aux:n
- {
- \regex_use:N #1
- \regex_match:n {#2}
- \regex_extract_after_group:N #3
- }
- }
- }
-\prg_new_protected_conditional:Npnn \regex_extract_once:NnN #1#2#3
- { T , F , TF }
- {
- \regex_check_nfa:NTF #1
- {
- \regex_user_aux:n
- {
- \regex_use:N #1
- \regex_match:n {#2}
- \regex_extract_after_group:N #3
- \regex_return_after_group:
- }
- }
- { \prg_return_false: }
+ \tex_skip:D #1
+ = 1 \tex_skip:D #1 plus #2 sp \scan_stop:
}
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
-% \end{macro}
%
-% \begin{macro}{\regex_extract_all:nnN}
-% \begin{macro}{\regex_extract_all:NnN}
-% \begin{macro}[aux]{\regex_extract_all_aux:}
-% \begin{macro}[aux]{\regex_extract_all_after_group:N}
-% Similarly to \cs{regex_count:nnN} functions,
-% recurse through matches of the pattern. Then we do
-% something slightly different, extracting submatches.
-% Submatches are not extracted if the pattern matched
-% an empty string at the start of the match attempt
-% (to avoid adding spurious empty items to the resulting
-% sequence).
+% \begin{macro}{\regex_group_end_extract_seq:N}
+% \begin{macro}[aux]{\regex_extract_seq_aux:n}
+% \begin{macro}[aux]{\regex_extract_seq_aux:ww}
% \begin{macrocode}
-\cs_new_protected:Npn \regex_extract_all:nnN #1#2#3
+\cs_new_protected:Npn \regex_group_end_extract_seq:N #1
{
- \regex_user_aux:n
- {
- \seq_gclear:N \g_regex_extract_all_seq
- \tl_set:Nn \l_regex_every_match_tl { \regex_extract_all_aux: }
- \regex_build:n {#1}
- \regex_match:n {#2}
- \regex_extract_all_after_group:N #3
- }
+ \cs_set_eq:NN \seq_item:n \scan_stop:
+ \flag_lower:N \l_regex_group_begin_flag
+ \flag_lower:N \l_regex_group_end_flag
+ \tl_set:Nx \l_regex_tmpa_tl
+ {
+ \prg_stepwise_function:nnnN
+ \l_regex_max_state_int
+ \c_one
+ { \l_regex_submatch_int - \c_one }
+ \regex_extract_seq_aux:n
+ }
+ \flag_test:NF \l_regex_group_begin_flag
+ { \flag_test:NF \l_regex_group_end_flag { \use_none:nnn } }
+ \msg_kernel_error:nn { regex } { sequence-unbalanced }
+ \tl_set:Nx \l_regex_tmpa_tl { \l_regex_tmpa_tl }
+ \exp_args:NNNo \group_end:
+ \tl_set:Nn #1 \l_regex_tmpa_tl
}
-\cs_new_protected:Npn \regex_extract_all:NnN #1#2#3
+\cs_new_nopar:Npn \regex_extract_seq_aux:n #1
{
- \regex_check_nfa:NT #1
+ \seq_item:n
{
- \regex_user_aux:n
- {
- \seq_gclear:N \g_regex_extract_all_seq
- \tl_set:Nn \l_regex_every_match_tl { \regex_extract_all_aux: }
- \regex_use:N #1
- \regex_match:n {#2}
- \regex_extract_all_after_group:N #3
- }
+ \exp_after:wN \regex_extract_seq_aux:ww
+ \int_value:w \regex_submatch_nesting_aux:n {#1} ; #1;
}
}
-\cs_new_protected_nopar:Npn \regex_extract_all_aux:
- {
- \regex_extract:
- \seq_gconcat:NNN \g_regex_extract_all_seq
- \g_regex_extract_all_seq \g_regex_submatches_seq
- \regex_match_once:
- }
-\cs_new_protected_nopar:Npn \regex_extract_all_after_group:N #1
- {
- \seq_if_empty:NF \g_regex_extract_all_seq
- {
- \regex_extract:
- \group_insert_after:N \seq_set_eq:NN
- \group_insert_after:N #1
- \group_insert_after:N \g_regex_extract_all_seq
- }
+\cs_new_nopar:Npn \regex_extract_seq_aux:ww #1; #2;
+ { %^^A todo: use flags
+ \if_num:w #1 < \c_zero
+ \flag_raise:N \l_regex_group_end_flag
+ \prg_replicate:nn {-#1} { \exp_not:n { { \if_false: } \fi: } }
+ \fi:
+ \regex_query_submatch:w #2;
+ \if_num:w #1 > \c_zero
+ \flag_raise:N \l_regex_group_begin_flag
+ \prg_replicate:nn {#1} { \exp_not:n { \if_false: { \fi: } } }
+ \fi:
}
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
-% \end{macro}
%
-% \subsubsection{Splitting a string by matches of a regex}
+% \subsubsection{Matching}
%
-% \begin{macro}{\regex_split:nnN}
-% \begin{macro}{\regex_split:NnN}
-% \begin{macro}[aux]{\regex_split_aux:}
-% \begin{macro}[aux]{\regex_split_after_group:N}
-% Similarly to \cs{regex_count:nnN} functions,
-% recurse through matches of the pattern. Then we do
-% something slightly different, extracting submatches.
-% Submatches are not extracted if the pattern matched
-% an empty string at the start of the match attempt
-% (to avoid adding spurious empty items to the resulting
-% sequence).
+% \begin{macro}[TF]{\regex_match:nn}
+% \begin{macro}[TF]{\regex_match:Nn}
+% \begin{macro}{\regex_match_aux:n}
+% We don't track submatches. Then either build the NFA corresponding
+% to the regular expression, or use a precompiled pattern. Then match,
+% using the internal \cs{regex_match:n}. Finally return the result
+% after closing the group.
% \begin{macrocode}
-\cs_new_protected:Npn \regex_split:nnN #1#2#3
+\prg_new_protected_conditional:Npnn \regex_match:nn #1#2 { T , F , TF }
{
- \regex_user_aux:n
- {
- \seq_gclear:N \g_regex_split_seq
- \tl_set:Nn \l_regex_every_match_tl { \regex_split_aux: }
- \regex_build:n {#1}
- \regex_match:n {#2}
- \regex_split_after_group:N #3
- }
+ \regex_match_aux:n
+ { \regex_aux_build_match:nn {#1} {#2} }
}
-\cs_new_protected:Npn \regex_split:NnN #1#2#3
+\prg_new_protected_conditional:Npnn \regex_match:Nn #1#2 { T , F , TF }
{
- \regex_check_nfa:NT #1
- {
- \regex_user_aux:n
- {
- \seq_gclear:N \g_regex_split_seq
- \tl_set:Nn \l_regex_every_match_tl { \regex_split_aux: }
- \regex_use:N #1
- \regex_match:n {#2}
- \regex_split_after_group:N #3
- }
- }
+ \regex_match_aux:n
+ { \regex_aux_use_match:Nn #1 {#2} }
}
-\cs_new_protected_nopar:Npn \regex_split_aux:
+\cs_new_protected:Npn \regex_match_aux:n #1
{
- \int_compare:nNnF \l_regex_start_step_int = \l_regex_success_step_int
- {
- \regex_extract:
- \seq_pop:NN \g_regex_submatches_seq \l_regex_tmpa_tl
- \regex_extract_aux:nTF {0}
- {
- \seq_gput_left:Nx \g_regex_submatches_seq
- {
- \regex_query_substr:NN
- \l_regex_start_step_int \l_regex_tmpa_tl
- }
- }
- { \msg_error:nn { regex } { internal } }
- \seq_gconcat:NNN \g_regex_split_seq
- \g_regex_split_seq \g_regex_submatches_seq
- }
- \regex_match_once:
+ \group_begin:
+ \tl_clear:N \l_regex_every_match_tl
+ \regex_disable_submatches:
+ #1
+ \group_end:
+ \regex_aux_return:
}
-\cs_new_protected_nopar:Npn \regex_split_after_group:N #1
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\regex_count:nnN}
+% \begin{macro}{\regex_count:NnN}
+% \begin{macro}{\regex_count_aux:n}
+% Instead of aborting once the first \enquote{longest match} is found,
+% we repeat the search. The code is such that the search will not
+% start on the same character, hence avoiding infinite loops.
+% \begin{macrocode}
+\cs_new_protected_nopar:Npn \regex_count:nnN
+ { \regex_count_aux:NnnN \regex_aux_build_match:nn }
+\cs_new_protected_nopar:Npn \regex_count:NnN
+ { \regex_count_aux:NnnN \regex_aux_use_match:Nn }
+\cs_new_protected:Npn \regex_count_aux:NnnN #1#2#3#4
{
- \int_compare:nNnTF \l_regex_start_step_int = \l_regex_current_step_int
- {
- \bool_if:NF \l_regex_success_empty_bool
- { \seq_gput_right:Nn \g_regex_split_seq { } }
- }
- {
- \seq_gput_right:Nx \g_regex_split_seq
- {
- \regex_query_substr:NN
- \l_regex_start_step_int \l_regex_current_step_int
- }
- }
- \group_insert_after:N \seq_set_eq:NN
- \group_insert_after:N #1
- \group_insert_after:N \g_regex_split_seq
+ \group_begin:
+ \regex_disable_submatches:
+ \int_zero:N \l_regex_match_count_int
+ \tl_set:Nn \l_regex_every_match_tl
+ {
+ \int_incr:N \l_regex_match_count_int
+ \regex_match_once:
+ }
+ #1 {#2} {#3}
+ \exp_args:NNNo
+ \group_end:
+ \int_set:Nn #4 { \int_use:N \l_regex_match_count_int }
}
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
-% \end{macro}
%
-% \subsubsection{String replacement}
+% \subsubsection{Submatch extraction}
%
-% \begin{macro}[int]{\regex_replacement:n}
-% \begin{macro}[aux]{\regex_replacement_escaped:N}
-% \begin{macro}[aux]{\regex_replacement_submatch:w}
-% \begin{macro}[aux]{\regex_replacement_submatch_aux:nN}
+% \begin{macro}[aux]{\regex_extract_once_aux:NnnN}
+% As announced, here comes the auxiliary for extracting one match.
+% Since we only want one match, \cs{l_regex_every_match_tl}
+% is empty, and does not trigger the matching code again.
+% After matching, \cs{regex_extract:} extracts submatches
+% into various \tn{skip} registers, and those are then concatenated
+% into a sequence by \cs{regex_group_end_extract_seq:N}. That function
+% is also responsible for closing the group.
% \begin{macrocode}
-\cs_new_protected:Npn \regex_replacement:n #1
- {
- \str_aux_escape:NNNn
- \prg_do_nothing:
- \regex_replacement_escaped:N
- \prg_do_nothing:
- {#1}
- \tl_set_eq:NN \l_regex_replacement_tl \g_str_result_tl
- \tl_set:Nx \l_regex_replacement_tl
- { \l_regex_replacement_tl \prg_do_nothing: }
- }
-\cs_new_nopar:Npn \regex_replacement_escaped:N #1
+\cs_new_protected:Npn \regex_extract_once_aux:NnnN #1#2#3#4
{
- \regex_token_if_other_digit:NTF #1
- {
- \exp_not:n
- { \exp_not:n { \seq_item:Nn \g_regex_submatches_seq } } {#1}
- }
- {
- \token_if_eq_charcode:NNTF g #1
- { \exp_not:N \regex_replacement_submatch:w }
- { #1 }
- }
+ \group_begin:
+ \tl_set:Nn \l_regex_every_match_tl { \regex_extract: }
+ #1 {#2} {#3}
+ \regex_group_end_extract_seq:N #4
}
-\cs_new_nopar:Npn \regex_replacement_submatch:w #1
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[aux]{\regex_extract_all_aux:NnnN}
+%^^A todo: update doc
+% The set of submatches will be built progressively
+% in \cs{l_regex_result_seq}. For each match, extract
+% the submatches, and concatenate that to the right of
+% the result sequence, then start matching again.
+% Finally, copy the result in the user's sequence variable.
+% \begin{macrocode}
+\cs_new_protected:Npn \regex_extract_all_aux:NnnN #1#2#3#4
{
- \exp_after:wN \token_if_eq_meaning:NNTF \c_lbrace_str #1
- { \regex_replacement_submatch_aux:nN { } }
- {
- \regex_token_if_other_digit:NTF #1
- { \exp_not:n { \seq_item:Nn \g_regex_submatches_seq } }
- { \msg_expandable_kernel_error:nn { regex } { g-misused } }
- #1
- }
- }
-\cs_new_nopar:Npn \regex_replacement_submatch_aux:nN #1 #2
+ \group_begin:
+ \tl_set:Nn \l_regex_every_match_tl
+ { \regex_extract: \regex_match_once: }
+ #1 {#2} {#3}
+ \regex_group_end_extract_seq:N #4
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Splitting a token list by matches of a regex}
+%
+% \begin{macro}[aux]{\regex_split_aux:NnnN}
+% Recurse through the matches, and for each, do the following.
+% Extract the submatches into various \tn{skip} registers,
+% then replace the match |\0|, which should not be kept in the
+% final result, and replace it by the part of the token list
+% before the match.
+% This process must be inhibited to avoid creating empty items
+% if the regex matched an empty token list at the place where
+% the match attempt started.
+% After the last successful match, we need to add to the result
+% the part of the token list after the last match, unless the
+% last match was empty and at the very end.
+% Finally, \cs{regex_group_end_extract_seq:N} builds a sequence
+% from all the \tn{skip} registers, and assigns it to |#4|
+% after closing the group.
+% \begin{macrocode}
+\cs_new_protected:Npn \regex_split_aux:NnnN #1#2#3#4
{
- \regex_token_if_other_digit:NTF #2
- { \regex_replacement_submatch_aux:nN {#1#2} }
- {
- \exp_not:n { \seq_item:Nn \g_regex_submatches_seq } {#1}
- \exp_after:wN \token_if_eq_meaning:NNF \c_rbrace_str #2
- {
- \msg_expandable_kernel_error:nn { regex } { g-misused }
- #2
- }
- }
+ \group_begin:
+ \tl_set:Nn \l_regex_every_match_tl
+ {
+ \if_num:w \l_regex_start_step_int < \l_regex_success_step_int
+ \regex_extract:
+ \tex_skip:D \l_regex_submatch_start_int
+ = \l_regex_start_step_int sp
+ plus \tex_skip:D \l_regex_submatch_start_int \scan_stop:
+ \fi:
+ \regex_match_once:
+ }
+ #1 {#2} {#3}
+ \tex_skip:D \l_regex_submatch_int
+ = \l_regex_start_step_int sp
+ plus \l_regex_current_step_int sp \scan_stop:
+ \int_incr:N \l_regex_submatch_int
+ \if_num:w \l_regex_start_step_int = \l_regex_current_step_int
+ \if_meaning:w \c_true_bool \l_regex_success_empty_bool
+ \int_decr:N \l_regex_submatch_int
+ \fi:
+ \fi:
+ \regex_group_end_extract_seq:N #4
}
% \end{macrocode}
% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
%
-% \begin{macro}[aux]{\regex_replace_after_group:N}
-% I wonder why I implemented things with \tn{aftergroup}
-% rather than simply placing that code after the explicit
-% \cs{group_end:} that I have. (General remark.)
+% \subsubsection{Replacement}
+%
+% \begin{macro}[aux]{\regex_replace_once_aux:NnnN}
+% The replacement text is analysed by \cs{regex_replacement:n},
+% which defines \cs{regex_replacement_tl:n} to expand to the
+% replaced token list, assuming that submatches are stored in
+% various \tn{skip} registers, as done by \cs{regex_extract:}.
+% If there is a match, we grab
+% the parts before and after it, and get the result
+% by \texttt{x}-expanding twice.
% \begin{macrocode}
-\cs_new_protected_nopar:Npn \regex_replace_after_group:N #1
+\cs_new_protected:Npn \regex_replace_once_aux:NnnN #1#2#3#4
+ {
+ \group_begin:
+ \tl_clear:N \l_regex_every_match_tl
+ \regex_replacement:n {#3}
+ \exp_args:Nno #1 {#2} #4
+ \if_meaning:w \c_true_bool \g_regex_success_bool
+ \regex_extract:
+ \int_set:Nn \l_regex_tmpa_int
+ { \regex_nesting:n { \l_regex_submatch_start_int } }
+ \if_num:w \l_regex_tmpa_int = \c_zero
+ \else:
+ \msg_kernel_error:nnx { regex } { replace-unbalanced }
+ { \l_regex_tmpa_int }
+ \fi:
+ \tl_set:Nx \l_regex_tmpa_tl
+ {
+ \if_num:w \l_regex_tmpa_int < \c_zero
+ \prg_replicate:nn { - \l_regex_tmpa_int }
+ { \exp_not:n { { \if_false: } \fi: } }
+ \fi:
+ \regex_query_substr:nn
+ { \l_regex_min_step_int }
+ { \tex_skip:D \l_regex_submatch_start_int }
+ \regex_replacement_tl:n { \l_regex_submatch_start_int }
+ \regex_query_substr:nn
+ { \etex_gluestretch:D \tex_skip:D \l_regex_submatch_start_int }
+ { \l_regex_max_step_int }
+ \if_num:w \l_regex_tmpa_int > \c_zero
+ \prg_replicate:nn { \l_regex_tmpa_int }
+ { \exp_not:n { \if_false: { \fi: } } }
+ \fi:
+ }
+ \tl_set:Nx \l_regex_tmpa_tl { \l_regex_tmpa_tl }
+ \exp_args:NNNo \group_end:
+ \tl_set:Nn #4 \l_regex_tmpa_tl
+ \else:
+ \group_end:
+ \fi:
+ }
+\cs_new_protected_nopar:Npn \regex_replace_once_aux:w #1;
{
- \group_insert_after:N \tl_set:Nx
- \group_insert_after:N #1
- \group_insert_after:N {
- \group_insert_after:N \tl_to_str:N
- \group_insert_after:N \g_regex_replaced_str
- \group_insert_after:N }
}
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}{\regex_replace_once:nnN}
-% \begin{macro}{\regex_replace_once:NnN}
-% \begin{macro}[TF]{\regex_replace_once:nnN}
-% \begin{macro}[TF]{\regex_replace_once:NnN}
-% \begin{macro}[aux]{\regex_replace_once_aux:Nn}
+% \begin{macro}[aux]{\regex_replace_all_aux:NnnN}
+% For every match, extract submatches, and add the part before
+% the beginning of the match, as well as the replacement,
+% to the result. After the last match, extract the end
+% of the token list, and add it to the replaced token list.
% \begin{macrocode}
-\cs_new_protected:Npn \regex_replace_once_aux:Nn #1#2
+\cs_new_protected:Npn \regex_replace_all_aux:NnnN #1#2#3#4
{
\group_begin:
- \regex_replace_after_group:N #1
- \tl_clear:N \l_regex_every_match_tl
- #2
- \exp_args:No \regex_match:n {#1}
- \regex_extract:
- \regex_extract_aux:nTF {0}
+ \tl_set:Nn \l_regex_every_match_tl
{
- \cs_set_nopar:Npn \regex_tmp:w
- { \str_skip_do:nn { \l_regex_tmpb_tl } { } }
- \tl_gset:Nx \g_regex_replaced_str
- {
- \regex_query_substr:NN \c_zero \l_regex_tmpa_tl
- \l_regex_replacement_tl
- \exp_after:wN \regex_tmp:w \l_regex_query_other_str
- }
+ \regex_extract:
+ \tex_skip:D \l_regex_submatch_start_int
+ = \tex_the:D \tex_skip:D \l_regex_submatch_start_int
+ minus \l_regex_start_step_int sp \scan_stop:
+ \regex_match_once:
}
- { \tl_gset_eq:NN \g_regex_replaced_str \l_regex_query_other_str }
- \group_end:
+ \regex_replacement:n {#3}
+ \exp_args:Nno #1 {#2} #4
+ \int_set:Nn \l_regex_tmpa_int
+ {
+ 0
+ \prg_stepwise_function:nnnN
+ \l_regex_max_state_int
+ \l_regex_capturing_group_int
+ { \l_regex_submatch_int - \c_one }
+ \regex_nesting:n
+ }
+ \if_num:w \l_regex_tmpa_int = \c_zero
+ \else:
+ \msg_kernel_error:nnx { regex } { replace-unbalanced }
+ { \l_regex_tmpa_int }
+ \fi:
+ \tl_set:Nx \l_regex_tmpa_tl
+ {
+ \if_num:w \l_regex_tmpa_int < \c_zero
+ \prg_replicate:nn { - \l_regex_tmpa_int }
+ { \exp_not:n { { \if_false: } \fi: } }
+ \fi:
+ \prg_stepwise_function:nnnN
+ \l_regex_max_state_int
+ \l_regex_capturing_group_int
+ { \l_regex_submatch_int - \c_one }
+ \regex_replace_all_aux:n
+ \regex_query_substr:nn
+ \l_regex_start_step_int \l_regex_max_step_int
+ \if_num:w \l_regex_tmpa_int > \c_zero
+ \prg_replicate:nn { \l_regex_tmpa_int }
+ { \exp_not:n { \if_false: { \fi: } } }
+ \fi:
+ }
+ \tl_set:Nx \l_regex_tmpa_tl { \l_regex_tmpa_tl }
+ \exp_args:NNNo \group_end:
+ \tl_set:Nn #4 \l_regex_tmpa_tl
}
-\cs_new_protected:Npn \regex_replace_once:nnN #1#2#3
+\cs_new_nopar:Npn \regex_replace_all_aux:n #1
{
- \regex_replace_once_aux:Nn #3
- {
- \regex_build:n {#1}
- \regex_replacement:n {#2}
- }
+ \regex_query_substr:nn
+ { \etex_glueshrink:D \tex_skip:D #1 } { \tex_skip:D #1 }
+ \regex_replacement_tl:n {#1}
}
-\cs_new_protected:Npn \regex_replace_once:NnN #1#2#3
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Messages}
+%
+% \begin{macrocode}
+\msg_kernel_new:nnnn { regex } { sequence-unbalanced }
{
- \regex_replace_once_aux:Nn #3
+ Missing~
+ \flag_test:NTF \l_regex_group_end_flag
{
- \regex_use:N #1
- \regex_replacement:n {#2}
+ left~
+ \flag_test:NTF \l_regex_group_begin_flag
+ { and~right~braces } { brace }
}
+ { right~brace }
+ \ inserted~in~extracted~match.
}
-\prg_new_protected_conditional:Npnn \regex_replace_once:nnN #1#2#3 {T,F,TF}
{
- \regex_replace_once_aux:Nn #3
- {
- \regex_build:n {#1}
- \regex_replacement:n {#2}
- \regex_return_after_group:
- }
+ LaTeX~was~asked~to~extract~submatches~or~split~a~token~list~
+ according~to~a~given~regular~expression,~but~some~of~the~resulting~
+ items~were~not~balanced.
}
-\prg_new_protected_conditional:Npnn \regex_replace_once:NnN #1#2#3 {T,F,TF}
+\msg_kernel_new:nnnn { regex } { replace-unbalanced }
+ { The~result~of~a~replacement~does~not~have~balanced~braces. }
{
- \regex_replace_once_aux:Nn #3
+ LaTeX~was~asked~to~do~some~regular~expression~replacement,~
+ and~the~resulting~token~list~would~not~have~the~same~number~
+ of~begin-group~and~end-group~tokens. \\ \\
+ \ \ \ \
+ \prg_case_int:nnn {#1}
{
- \regex_use:N #1
- \regex_replacement:n {#2}
- \regex_return_after_group:
+ { -1 } { A~left~brace~was }
+ { 1 } { A~right~brace~was }
}
+ {
+ \int_abs:n {#1} ~
+ \int_compare:nNnTF {#1} < \c_zero { left } { right } ~
+ braces ~ were
+ }
+ \ inserted.
}
% \end{macrocode}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
%
-% \begin{macro}{\regex_replace_all:nnN}
-% \begin{macro}{\regex_replace_all:NnN}
-% \begin{macro}[aux]{\regex_replace_all_aux:Nn}
-% \begin{macro}[aux]{\regex_replace_all_aux:}
% \begin{macrocode}
-\cs_new_protected:Npn \regex_replace_all_aux:Nn #1#2
+\msg_kernel_new:nnnn { regex } { missing-rparen }
+ { Missing~right~parenthesis~added~in~regular~expression. }
{
- \group_begin:
- \regex_replace_after_group:N #1
- \tl_set:Nn \l_regex_every_match_tl { \regex_replace_all_aux: }
- \tl_gclear:N \g_regex_replaced_str
- #2
- \exp_args:No \regex_match:n {#1}
- \cs_set_nopar:Npn \regex_tmp:w
- { \str_skip_do:nn { \l_regex_start_step_int } { } }
- \tl_gput_right:Nx \g_regex_replaced_str
- { \exp_after:wN \regex_tmp:w \l_regex_query_other_str }
- \group_end:
+ LaTeX~was~given~a~regular~expression~with~\int_eval:n{#1}~
+ more~left~parenthes\int_compare:nTF{#1=1}{i}{e}s~than~right~
+ parenthes\int_compare:nTF{#1=1}{i}{e}s.
}
-\cs_new_protected_nopar:Npn \regex_replace_all_aux:
+\msg_kernel_new:nnnn { regex } { extra-rparen }
+ { Extra~right~parenthesis~ignored~in~regular~expression. }
{
- \regex_extract:
- \regex_extract_aux:nTF {0}
- {
- \tl_gput_right:Nx \g_regex_replaced_str
- {
- \regex_query_substr:NN
- \l_regex_start_step_int \l_regex_tmpa_tl
- \l_regex_replacement_tl
- }
- }
- { \msg_error:nn { regex } { internal } }
- \regex_match_once:
+ LaTeX~came~across~a~closing~parenthesis~when~no~submatch~group~
+ was~open.~The~parenthesis~will~be~ignored.
}
-\cs_new_protected:Npn \regex_replace_all:nnN #1#2#3
+\msg_kernel_new:nnnn { regex } { backwards-range }
+ { Range~[#1-#2]~out~of~order~in~character~class. }
{
- \regex_replace_all_aux:Nn #3
- {
- \regex_build:n {#1}
- \regex_replacement:n {#2}
- }
+ In~ranges~of~characters~[x-y]~appearing~in~character~classes,~
+ the~first~character~code~must~not~be~larger~than~the~second.~
+ Here,~#1~has~character~code~\int_eval:n {`#1},~while~#2~has~
+ character~code~\int_eval:n {`#2}.
}
-\cs_new_protected:Npn \regex_replace_all:NnN #1#2#3
+\msg_kernel_new:nnnn { regex } { unsupported }
+ { Unsupported~construction~`#1'. }
{
- \regex_replace_all_aux:Nn #3
- {
- \regex_use:N #1
- \regex_replacement:n {#2}
- }
+ The~construction~`#1'~is~not~supported~by~the~LaTeX~
+ regular~expression~module.~Perhaps~some~character~should~
+ have~been~escaped?
+ }
+\msg_kernel_new:nnnn { regex } { not-nfa }
+ { This~is~not~a~regular~expression~variable. }
+ {
+ LaTeX~was~expecting~`#1'~to~be~a~regular~expression~variable.\\
+ This~control~sequence~is~not~a~regex~variable.~It's~current~meaning~
+ is~\\\\
+ \ \ \ \ \token_to_str:N #1 = \token_to_meaning:N #1 .
}
% \end{macrocode}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
%
-% \subsection{Messages}
-%
-% Negative codes are specific to the \LaTeX3 implementation.
-% Other error codes match with the PCRE codes (not all of
-% the PCRE errors can occur, since many constructions are
-% not supported).
% \begin{macrocode}
-% \msg_new:nnn { regex } { -999 } { File~not~found }
-\msg_new:nnn { regex } { -998 } { Unsupported~construct }
-\msg_new:nnn { regex } { -997 }
- { The~regular~expression~is~too~large~(32768~states). }
-\msg_new:nnn { regex } { 1 } { \iow_char:N\\~at~end~of~pattern }
+% \msg_new:nnn { regex } { 1 } { \iow_char:N\\~at~end~of~pattern }
% \msg_new:nnn { regex } { 2 } { \iow_char:N\\c~at~end~of~pattern }
-\msg_new:nnn { regex } { 4 }
- { Numbers~out~of~order~in~\iow_char:N\{\iow_char\}~quantifier. }
-\msg_new:nnn { regex } { 6 }
- { Missing~terminating~\iow_char:N\]~for~character~class }
-\msg_new:nnn { regex } { 7 }
- { Invalid~escape~sequence~in~character~class }
-\msg_new:nnn { regex } { 8 }
- { Range~out~of~order~in~character~class }
-\msg_new:nnn { regex } { 22 } { Mismatched~parentheses }
-\msg_new:nnn { regex } { 34 }
- { Character~value~in~\iow_char:N\\x{...}~sequence~is~too~large }
+% \msg_new:nnn { regex } { 4 }
+% { Numbers~out~of~order~in~\iow_char:N\{\iow_char\}~quantifier. }
+% \msg_new:nnn { regex } { 6 }
+% { Missing~terminating~\iow_char:N\]~for~character~class }
+% \msg_new:nnn { regex } { 7 }
+% { Invalid~escape~sequence~in~character~class }
+% \msg_new:nnn { regex } { 34 }
+% { Character~value~in~\iow_char:N\\x{...}~sequence~is~too~large }
% \msg_new:nnn { regex } { 44 } { Invalid~UTF-8~string }
% \msg_new:nnn { regex } { 46 }
% { Malformed~\iow_char:N\\P~or\iow_char:N\\p~sequence }
@@ -3867,18 +4464,46 @@
% \end{macrocode}
%
% \begin{macrocode}
-\msg_new:nnn { regex } { not-nfa }
+% \end{macrocode}
+%
+% \begin{macrocode}
+\msg_kernel_new:nnnn { regex } { build-c }
+ { Misused~\iow_char:N\\c~or\iow_char:N\\C~command~in~regular~expression. }
+ {
+ The~\iow_char:N\\C~escape~sequence~can~be~followed~by~
+ one~of~the~letters~ABCDELMOPSTU.~The~\iow_char:N\\c~
+ escape~sequence~can~also~be~followed~by~a~brace~group.
+ }
+\msg_kernel_new:nnnn { regex } { unknown-option }
+ { Unknown~option~`#1'~for~regular~expressions. }
+ {
+ LaTeX~came~across~something~like~`(?#1)'~in~a~regular~expression,~
+ but~the~option~`#1'~is~not~known.~It~will~be~ignored.
+ }
+\msg_kernel_new:nnnn { regex } { invalid-in-option }
+ { Invalid~character~in~option~of~a~regular~expression. }
+ {
+ The~character~or~escape~sequence~`#1'~is~not~defined~
+ as~an~option~within~regular~expressions.
+ }
+\msg_kernel_new:nnnn { regex } { replacement-g }
+ { Missing~brace~for~the~\iow_char:N\\g~construction~in~a~replacement~text. }
+ {
+ In~the~replacement~text~for~a~regular~expression~search,~
+ submatches~are~represented~either~as~\iow_char:N \\g{dd..d},~
+ or~\\d,~where~`d'~are~single~digits.~Here,~a~brace~is~missing.
+ }
+\msg_kernel_new:nnnn { regex } { replacement-c }
+ { Something~is~wrong~with~the~replacement~text. }
{
- LaTeX~was~expecting~a~regular~expression~variable.\\
- Instead,~LaTeX~found~'#1'.
+ The~\iow_char:N\\c~escape~sequence~can~only~be~followed~by~
+ ABCDELMN~or~by~a~brace.
}
% \end{macrocode}
%
% \begin{macrocode}
\msg_kernel_new:nnn { regex } { nfa-misused }
{ Automaton~#1 used~incorrectly. }
-\msg_kernel_new:nnn { regex } { g-misused }
- { \g misused~in~replacement~text. }
% \end{macrocode}
%
% \begin{macrocode}