The regular expressions of matches, replace and split (DMN 1.6, section 10.3.4.3, footnote 2 of
Table 75; D-80, was D-80).
The specification says that pattern, replacement and flags conform to clause 7.6 of XQuery 1.0 and
XPath 2.0 Functions and Operators, and that where XPath gives an error FEEL gives null. That dialect is
the regular expressions of XML Schema (appendix F of XSD part 2) with a few additions, and it is not the
one of Erlang (PCRE): \s is four characters and not the Unicode spaces, . leaves out the carriage
return, $ is only the end of the string, there are character classes with a subtraction
([A-Z-[OI]]) and blocks (\p{IsBasicLatin}), and there is no look-around, no \b, no inline flags and
no possessive quantifier. So a pattern is read here, strictly, in the dialect of XPath, and written
as a PCRE pattern that means the same; a pattern that is not in the dialect is :error, and the function
is null. Nothing of the pattern reaches the engine as it was written.
The dialect
Atoms. A character (not one of
. \ ? * + { } ( ) | [ ] ^ $),.(any character but a line feed or a carriage return, or any with the flags), an escape, a class, or a group. The escapes are\n \r \t, a character that is special (\\ \| \. \- \^ \? \* \+ \{ \} \( \) \[ \]),\s \S \d \D \w \W(as XML Schema has them:\sis a space, a tab, a line feed or a carriage return),\i \I \c \C(the characters of XML names),\p{..}and\P{..}(a category such asLorNd, or a block such asIsBasicLatin), and\1to\9, which is the text of a group that has been closed.- Classes.
[a-z0-9_],[^...], and[base-[subtracted]], the characters of the first that are not in the second. A-that is not the dash of a range or of a subtraction is a character only first in the class or last; anywhere else it has to be escaped ([a-c-e]is an error, as XML Schema has it). - Groups.
( )captures and(?: )does not. Any other(?is an error. - Quantifiers.
? * + {n} {n,} {n,m}, each with an optional?after it for the shortest match. - Anchors.
^and$are the start and the end of the string; with the flagm, the start and the end of a line (a line ends at a line feed). - Flags.
s(a dot is any character),m,i(a letter is its other case too, for the whole of Unicode: the Kelvin sign is ak) andx(the white space of the pattern is taken out first, but for what is in a class). Another character is an error.
A range whose end is before its start, a quantifier that has nothing to repeat, a back-reference to a
group that is not closed, an unbalanced bracket or parenthesis, an escape that is none of the above, and a
block that is not listed (the names of blocks/0, which are most of the scripts) are errors.
What is done with a pattern
compile/2 reads it. matches?/3 is true if some part of the input matches (the pattern is not anchored).
replace/4 replaces every match, left to right, the replacement having $0, $1, ... for the text of a
group (empty if the group did not take part or does not exist; the number takes as many digits as there are
groups to hold it), \$ for a dollar and \\ for a backslash, and any other $ or \ an error. split/3 cuts
the input at every match, and keeps the empty parts, those at the end included. replace and split need a
pattern that does not match the empty string (a* does), which is an error as in XPath. The input is data and
not a pattern.
The engine is told how long it may look (regex_match_limit of Feel.Limits); a match that takes more is
{:error, :limit}. It has to be asked to say so (:report_errors): otherwise the engine answers that
there is no match, which is a wrong answer and not an end of the run.
The size of the result
replace/5, split/4 and scan/4 take the most that their result may have (bytes for replace/5, items for the
others) and answer {:error, {:over, size}} where it would be more, with the count at which it passed. The walk over the
matches adds up what the result is as it goes and ends at the first match that makes it too long, and nothing is built
that is not within the cap: a replacement of 100 KB for each of a million matches is 100 GB, and the limit of a finished
string would look at it after it was made.
A pattern that is only text (,, ab, é: no character of the syntax, and no flag i or x) is searched for one
match at a time with :binary.match/3, so the walk costs what it walks: a megabyte of a split by a ends at the
100,001st part, in 26 ms and a few megabytes, where the engine took 600 ms and 380 MB for the million.
Any other pattern is run by the engine one match at a time too, so the walk costs what it walks (D-215): the
engine is asked for the first match from a byte offset of the whole text (the text is not cut, so ^, $ and the
characters before the offset mean what they mean in the whole), and for the next from where that one ended, with an
empty match stepped over as the engine's own :global run steps over it. The public :re.run/3 cannot do this
cheaply: a run for each match checks the whole text for UTF-8 again every time (80 KB took 3.5 s, and 1 MB does not
end). The function internal_run of :re (four arguments) is what the engine's own loop calls for each match after
the first, and its last argument says that the text has been checked; the first call checks it, as a :global run
does, and a text that is not UTF-8 raises as before. That function is not documented, and the loop that steps over an
empty match is that of OTP 28 and later, so where a version of OTP does not have the function, or is before 28, the
matches are listed whole as before (about 360 bytes of heap for a match, 360 MB for a megabyte of a and the pattern
[a]), and the cap and the engine's limit are crossed in the list, not on the walk.
Summary
Types
A pattern that is read: the program of the engine, the number of groups, and the text it is when it is only that text
(nil if it is more).
Functions
The names of the blocks that a pattern may use after Is, sorted.
Reads a pattern and its flags (nil for none), or says that they are not in the dialect.
Whether some part of the input matches. {:error, :limit} if the engine gave up (limit is the
regex_match_limit of the run).
Whether the pattern matches the empty string, which replace and split do not allow.
Replaces every match with the replacement, or :error if the replacement is not in the dialect (a $
not followed by a digit, a \\ not followed by a $ or a \\) or the pattern matches the empty string.
Cuts the input at every match, keeping the empty parts. The empty string is no parts.
Types
@type t() :: %Feel.Runtime.Regex{ groups: non_neg_integer(), literal: String.t() | nil, program: :re.mp() }
A pattern that is read: the program of the engine, the number of groups, and the text it is when it is only that text
(nil if it is more).
Functions
@spec blocks() :: [String.t()]
The names of the blocks that a pattern may use after Is, sorted.
Reads a pattern and its flags (nil for none), or says that they are not in the dialect.
iex> {:ok, regex} = Feel.Runtime.Regex.compile("a(b)c", nil)
iex> regex.groups
1
iex> Feel.Runtime.Regex.compile("a(", nil)
:error
iex> Feel.Runtime.Regex.compile("a", "q")
:error
@spec matches?(t(), String.t(), non_neg_integer() | :infinity) :: {:ok, boolean()} | {:error, :limit}
Whether some part of the input matches. {:error, :limit} if the engine gave up (limit is the
regex_match_limit of the run).
iex> {:ok, regex} = Feel.Runtime.Regex.compile("^fo*b", nil)
iex> Feel.Runtime.Regex.matches?(regex, "foobar", :infinity)
{:ok, true}
iex> Feel.Runtime.Regex.matches?(regex, "a foobar", :infinity)
{:ok, false}
Whether the pattern matches the empty string, which replace and split do not allow.
iex> {:ok, regex} = Feel.Runtime.Regex.compile("a*", nil)
iex> Feel.Runtime.Regex.matches_empty?(regex)
true
@spec replace( t(), String.t(), String.t(), non_neg_integer() | :infinity, non_neg_integer() | :infinity ) :: {:ok, String.t()} | :error | {:error, :limit} | {:error, {:over, pos_integer()}}
Replaces every match with the replacement, or :error if the replacement is not in the dialect (a $
not followed by a digit, a \\ not followed by a $ or a \\) or the pattern matches the empty string.
max is the most bytes the text may have (:infinity for no cap): a result that would be longer is
{:error, {:over, size}}, where size is the count of bytes when it passed max, and is not made.
iex> {:ok, regex} = Feel.Runtime.Regex.compile("(ab)|(a)", nil)
iex> Feel.Runtime.Regex.replace(regex, "abcd", "[1=$1][2=$2]", :infinity)
{:ok, "[1=ab][2=]cd"}
iex> Feel.Runtime.Regex.replace(regex, "abcd", "$", :infinity)
:error
iex> Feel.Runtime.Regex.replace(regex, "abcd", "xyz", :infinity, 4)
{:error, {:over, 5}}
@spec scan( t(), String.t(), non_neg_integer() | :infinity, non_neg_integer() | :infinity ) :: {:ok, [String.t()]} | :error | {:error, :limit} | {:error, {:over, pos_integer()}}
The text of every match, left to right: none is {:ok, []}. A pattern that matches the empty string is :error, as
for replace/5 and split/4: it would match everywhere.
max is the most matches there may be (:infinity for no cap): more are {:error, {:over, count}}, and are not made.
iex> {:ok, regex} = Feel.Runtime.Regex.compile("12[0-9]*", nil)
iex> Feel.Runtime.Regex.scan(regex, "references are 1234, 1256, 1378", :infinity)
{:ok, ["1234", "1256"]}
iex> Feel.Runtime.Regex.scan(regex, "nothing", :infinity)
{:ok, []}
iex> Feel.Runtime.Regex.scan(regex, "references are 1234, 1256, 1378", :infinity, 1)
{:error, {:over, 2}}
iex> {:ok, empty} = Feel.Runtime.Regex.compile("a*", nil)
iex> Feel.Runtime.Regex.scan(empty, "baaa", :infinity)
:error
@spec split( t(), String.t(), non_neg_integer() | :infinity, non_neg_integer() | :infinity ) :: {:ok, [String.t()]} | :error | {:error, :limit} | {:error, {:over, pos_integer()}}
Cuts the input at every match, keeping the empty parts. The empty string is no parts.
max is the most parts there may be (:infinity for no cap): more are {:error, {:over, count}}, and are not made.
iex> {:ok, regex} = Feel.Runtime.Regex.compile(";", nil)
iex> Feel.Runtime.Regex.split(regex, "a;b;c;;", :infinity)
{:ok, ["a", "b", "c", "", ""]}
iex> Feel.Runtime.Regex.split(regex, "", :infinity)
{:ok, []}
iex> Feel.Runtime.Regex.split(regex, "a;b;c;;", :infinity, 4)
{:error, {:over, 5}}