Standard Library
class Regex
A compiled regular expression.
since 0.1.0-alpha.1linuxwindowswasm
Overview
Build it once from a pattern, then match it against any number of strings. Matching time is linear in the length of the input: the engine never backtracks, so no pattern can make a search run away. The syntax covers literals and escapes, ., character classes, \d \w \s, alternation, capturing, non-capturing and named groups, greedy and lazy (*?) quantifiers, anchors and word boundaries; backreferences and lookaround are not supported. Matching is leftmost-first (the first alternative that matches wins, as in Perl and C#). Offsets and lengths count bytes, and . matches one byte.
A Regex is a reference object that is safe to share: it holds only its compiled program. The constructors treat the pattern as code and throw RegexException when it is malformed. When the pattern comes from outside the program, use regex::compile, which returns None instead of throwing.
Description
The regular-expression API has five public types: Regex, Match, Group, RegexOptions and
RegexException. A Regex is a compiled pattern. It is an object that you create once and share
freely, and it never changes. Its methods work on a string and report what they found as plain
values.
Test, find, and read named groups
Regex userRe = Regex("^[a-z0-9_]{3,16}$");
console.writeln(userRe.isMatch("ann_01"));
console.writeln(userRe.isMatch("A"));
Regex kv = Regex("(?<key>\\w+)=(?<val>[^;]*)");
Match? m = kv.find("x=1;y=22");
if (m != None) {
console.writeln(m.value);
console.writeln(m.index);
console.writeln(m.length);
Group? key = m.group("key");
if (key != None) { console.writeln(key.value); }
console.writeln(m.group(2).value);
console.writeln(m.groups.length());
}
Array<Match> all = kv.matches("x=1;y=22;z=");
console.writeln(all.length());
for (Match one in all) { console.writeln(one.group(2).value); }
console.writeln(kv.groupCount());
console.writeln(kv.groupNames());
true
false
x=1
0
3
x
1
3
3
1
22
2
[key, val]
Construction
Regex(pattern) compiles a pattern string. Regex(pattern, options) also takes a RegexOptions
value, and Regex(pattern, flags) takes a string of option letters (i, m and s, in any
order). Regex::FromProgram(program) wraps an already compiled program in constant time; see the
std.regex entry for the compile-time recipe.
A pattern you write in source code is code: a malformed one throws RegexException. A pattern
that arrives as data, from a configuration file or from user input, should go through
regex::compile(pattern), which returns None for a malformed pattern and never throws.
Matches
find(s) returns the first match as a Match?, None when there is none, and matches(s) returns
every non-overlapping match in order. isMatch(s) only answers whether a match exists, and
count(s) counts matches without building any Match values. find and isMatch also take a
start offset, find(s, from), to begin searching partway into the string. Offsets and lengths are
counted in bytes.
A Match is a value, and it exists only when the search succeeded. index, length and value describe
the whole match. groups is an array whose element 0 is the whole match, followed by each
capture group in order of its opening parenthesis; group(i) reads one by number (an out-of-range
number throws RuntimeException) and group(name) reads one by its (?<name>...) name, returning
None for a name the pattern does not declare.
A Group has matched, index, length and value. A group that did not take part in a
successful match, such as the untaken side of an alternation, is a real and reachable state: its
matched is false, its index is -1, its length is 0 and its value is "".
A group that did not take part
Regex r = Regex("(\\d+)|([a-z]+)");
Match? found = r.find("abc");
if (found != None) {
Group digits = found.group(1);
Group letters = found.group(2);
console.writeln(digits.matched);
console.writeln(digits.index);
console.writeln("[${digits.value}]");
console.writeln(letters.matched);
console.writeln(letters.value);
try {
found.group(5);
} catch (RuntimeException e) {
console.writeln("no such group");
}
}
false
-1
[]
true
abc
no such group
Replace and split
replace(s, replacement) replaces every match, and replace(s, replacement, count) replaces at most
count of them. Instead of a string, the replacement can be a function (Match) => string, called
once for each match. split(s) cuts the string at each match, and split(s, limit) returns at most
limit pieces, with the rest of the string left unsplit in the last one. When the separator
pattern contains a capture group, the captured text is included among the pieces.
A replacement string is read once per call with this grammar.
$0to$99insert a group by number; the longest valid group number is taken, so$10is group ten when the pattern has ten groups and group one followed by0otherwise.$0is the whole match.${name}inserts a named group.$$inserts a literal$. A$followed by anything else, or at the end, is a literal$.- A group that did not take part inserts nothing.
- A reference to a group that does not exist throws
RegexException. A replacement is code, and a silent pass-through would hide a typo.
Leviathan's own string literals interpolate ${...}, so write a named reference in source as
"\${name}" to deliver the three characters ${name} to the regex engine.
Replacing with groups, a count, and a function
Regex date = Regex("(\\d+)-(\\d+)-(\\d+)");
console.writeln(date.replace("on 2026-10-02 ok", "$3/$2/$1"));
Regex named = Regex("(?<y>\\d+)-(?<m>\\d+)");
console.writeln(named.replace("2026-10", "\${m}.\${y}"));
console.writeln(named.replace("2026-10", "$$\${m}"));
Regex digits = Regex("\\d+");
console.writeln(digits.replace("a1b22c333", "#"));
console.writeln(digits.replace("a1b22c333", "#", 2));
console.writeln(digits.replace("a1b22c333", (Match m) => "<" + m.value + ">"));
console.writeln(digits.split("a1b22c333d"));
console.writeln(digits.split("a1b22c333d", 2));
console.writeln(Regex("([,;])").split("a,b;c"));
try {
console.writeln(digits.replace("a1", "$7"));
} catch (RegexException e) {
console.writeln("bad reference");
}
on 02/10/2026 ok
10.2026
$10
a#b#c#
a#b#c333
a<1>b<22>c<333>
[a, b, c, d]
[a, b22c333d]
[a, ,, b, ;, c]
bad reference
Options
RegexOptions has three switches, set by name: ignoreCase (ASCII letters only), multiline
(^ and $ also match at line breaks) and dotAll (. also matches a newline). The string form
"im" or "s" is shorthand for the same switches. A RegexException carries the usual
message and an offset, the best-known byte position of the error in the pattern, or -1 when
no position is known.
Rules
findreturnsNonewhen nothing matches, so a match is never an empty placeholder.- A pattern literal that is malformed throws
RegexException;regex::compileis the form that returnsNoneinstead. - Matching is leftmost-first with greedy quantifiers by default, as in Perl, C# and JavaScript: among the matches that start leftmost, the one the pattern lists first wins, not the longest.
- After a zero-length match at position
i, the search resumes ati + 1, somatches,replaceandsplitalways terminate. replacereplaces every match unless acountis given.- Matching works on bytes:
.matches one byte, so a character that takes several bytes in UTF-8 is matched by several dots. ignoreCasefolds ASCII letters only.- A
Regexis immutable and can be shared freely.
Examples
Choosing between throw and None
Regex? fromData = regex::compile("[a-");
console.writeln(fromData == None);
try {
Regex fromCode = Regex("(abc");
console.writeln(fromCode.isMatch("abc"));
} catch (RegexException e) {
console.writeln("malformed: ${e.message}");
console.writeln(e.offset);
}
Regex ci = Regex("hello", RegexOptions(ignoreCase: true));
console.writeln(ci.isMatch("HeLLo"));
Regex lines = Regex("^b$", "m");
console.writeln(lines.isMatch("a\nb\nc"));
console.writeln(Regex("a.b").isMatch("a\nb"));
console.writeln(Regex("a.b", "s").isMatch("a\nb"));
true
malformed: regex: unterminated group at offset 4
4
true
true
false
true
Notes
Not implemented: Unicode character classes (\p{L} is rejected as a malformed pattern) and Unicode
case folding.
Examples
Validating and extracting
Regex userRe = Regex("^[a-z0-9_]{3,16}$");
console.writeln(userRe.isMatch("alice_01"));
console.writeln(userRe.isMatch("Al"));
Regex kv = Regex(r"(?<key>\w+)=(?<val>[^;]*)");
Match? m = kv.find("name=bob;age=7");
if (m != None) {
console.writeln(m.group("key")?.value ?? "");
console.writeln(m.group("val")?.value ?? "");
}
console.writeln(kv.count("a=1;b=2;c=3"));
true
false
name
bob
3
Constructors
FromProgram
Regex::FromProgram(Array<int> prog)Wrap a program that was compiled ahead of time, without parsing anything.
Use it with regex::compileProgram and comptime, so that a constant pattern is compiled when the program is built and a mistake in it is reported at its source line. The wrapper does not know the pattern text or the options, so pattern() returns "" and options() returns the defaults.
Parameters
- prog
- A program produced by
regex::compileProgram.
Examples
Wrapping a compiled program
Array<int> prog = regex::compileProgram(r"[a-z]+@[a-z]+", "");
Regex mail = Regex::FromProgram(prog);
console.writeln(mail.isMatch("mail bob@host now"));
console.writeln(mail.count("a@b c@d"));
console.writeln(mail.pattern() == "");
true
2
true
See also: compileProgram
new
new(string pattern)Compile a pattern.
Parameters
- pattern
- The pattern text.
Throws
RegexException- when the pattern is malformed.
Examples
Compiling with options
Regex plain = Regex(r"\d+");
Regex loose = Regex("hello", RegexOptions(ignoreCase: true));
Regex flagged = Regex("hello", "i");
console.writeln(plain.isMatch("order 66"));
console.writeln(loose.isMatch("HELLO"));
console.writeln(flagged.isMatch("HELLO"));
try {
Regex bad = Regex("a", "q");
} catch (RegexException e) {
console.writeln(e.message);
}
true
true
true
regex: unknown flag at offset 0
new(string pattern, RegexOptions options)Compile a pattern with options.
Parameters
- pattern
- The pattern text.
- options
- The options to apply.
Throws
RegexException- when the pattern is malformed.
new(string pattern, string flags)Compile a pattern with options given as a flag string.
Parameters
- pattern
- The pattern text.
- flags
- Any of the letters
i(ignore case),m(multiline) ands(dot matches line breaks), in any order;""for none.
Throws
RegexException- when the pattern is malformed or
flagscontains another character.
Methods
count
count(string s) -> intCount the non-overlapping matches without building Match values.
The result equals matches(s).length(), computed faster.
Parameters
- s
- The text to search.
Returns
The number of matches.
Examples
Counting
Regex re = Regex(r"\d+-\d+");
console.writeln(re.count("2026-10 2027-01 2028-02"));
console.writeln(re.count("none"));
3
0
countAll
Count the matches in every string of an array.
The engine is set up once and reused for the whole array.
Parameters
- rows
- The strings to search.
Returns
One count per row, in order.
Examples
Counting per row
console.writeln(Regex(r"\d").countAll(["a1b2", "", "123"]));
[2, 0, 3]
find
find(string s) -> Match | NoneFind the first match in a string.
Parameters
- s
- The text to search.
Returns
The first (leftmost) match, or None when there is none.
Examples
First match, and the next one from an offset
Regex re = Regex(r"(?<year>\d+)-(?<month>\d+)");
Match? m = re.find("on 2026-10 and 2027-01");
if (m != None) console.writeln("${m.index}:${m.value}");
Match? m2 = re.find("on 2026-10 and 2027-01", 8);
if (m2 != None) console.writeln("${m2.index}:${m2.value}");
console.writeln(re.find("none") == None);
3:2026-10
15:2027-01
true
find(string s, int from) -> Match | NoneFind the first match at or after a byte offset.
Parameters
- s
- The text to search.
- from
- The byte offset where the search starts; a negative value is treated as
0.
Returns
The first match starting at or after from, or None when there is none.
groupCount
groupCount() -> intThe number of capture groups in the pattern.
Non-capturing groups (?:...) are not counted, and the whole match (group 0) is not counted.
Returns
The number of capture groups.
Examples
Counting groups
console.writeln(Regex(r"(\d+)-(\d+)").groupCount());
console.writeln(Regex(r"(?:ab)+(\d)").groupCount());
console.writeln(Regex(r"\d+").groupCount());
2
1
0
groupNames
groupNames() -> Array<string>The names of the named groups, in the order they are declared in the pattern.
Returns
The names declared with (?<name>...); empty when the pattern has none.
Examples
Listing the names
Regex re = Regex(r"(?<year>\d+)-(?<month>\d+)");
console.writeln(re.groupNames());
console.writeln(Regex(r"\d+").groupNames().length());
[year, month]
0
isMatch
isMatch(string s) -> boolTest whether the pattern matches anywhere in a string.
Parameters
- s
- The text to search.
Returns
true when some part of s matches.
Examples
Testing from the start and from an offset
Regex re = Regex(r"(?<year>\d+)-(?<month>\d+)");
console.writeln(re.isMatch("2026-10"));
console.writeln(re.isMatch("2026-10", 5));
console.writeln(re.isMatch("x 2026-10 y", 2));
true
false
true
isMatch(string s, int from) -> boolTest whether the pattern matches anywhere in a string at or after a byte offset.
Parameters
- s
- The text to search.
- from
- The byte offset where the search starts; a negative value is treated as
0.
Returns
true when some part of s from that offset on matches.
isMatchAll
Test the pattern against every string of an array.
The engine is set up once and reused for the whole array.
Parameters
- rows
- The strings to test.
Returns
One bool per row, in order.
Examples
Testing a column
Regex code = Regex(r"^[A-Z]{2}\d{2}$");
console.writeln(code.isMatchAll(["AB12", "ab12", "XY99", "X9"]));
[true, false, true, false]
matches
Find every non-overlapping match, left to right.
An empty match is reported once at its position and the search then moves one byte forward, so the call always ends.
Parameters
- s
- The text to search.
Returns
The matches in order; empty when there are none.
Examples
Collecting all matches
Regex re = Regex(r"\d+-\d+");
Array<Match> all = re.matches("on 2026-10 and 2027-01");
console.writeln(all.length());
for (Match x in all) console.writeln(x.value);
console.writeln(re.matches("nothing").length());
console.writeln(Regex("x*").matches("axb").length());
2
2026-10
2027-01
0
4
options
options() -> RegexOptionsThe options this Regex was constructed with.
Returns
The options; every flag is false when none were given.
Examples
Reading the options back
Regex re = Regex("a", "im");
RegexOptions o = re.options();
console.writeln("${o.ignoreCase} ${o.multiline} ${o.dotAll}");
true true false
pattern
pattern() -> stringThe pattern text this Regex was constructed from.
Returns
The pattern, exactly as it was passed to the constructor.
Examples
Reading the pattern back
Regex re = Regex(r"(\d+)-(\d+)");
console.writeln(re.pattern());
(\d+)-(\d+)
replace
replace(string s, (Match) => string fn) -> stringReplace matches using a function that builds each replacement.
All matches are replaced. The function receives each Match in turn and returns the text to put in its place.
Parameters
- s
- The text to search.
- fn
- Called once per match; returns the replacement text.
Returns
The text with every match replaced.
Examples
Replacing with a function
Regex num = Regex(r"\d+");
console.writeln(num.replace("a1b22c333", (Match m) => "<${m.value}>"));
console.writeln(num.replace("a1b22c333", (Match m) => "<${m.value}>", 1));
a<1>b<22>c<333>
a<1>b22c333
replace(string s, (Match) => string fn, int count) -> stringReplace the first matches using a function that builds each replacement.
Parameters
- s
- The text to search.
- fn
- Called once per replaced match; returns the replacement text.
- count
- The most matches to replace; the rest are left as they are.
Returns
The text with up to count matches replaced.
replace(string s, string replacement) -> stringReplace every match with a replacement string.
The replacement string may refer to groups: $0 is the whole match, $1 to $99 are groups by number (a two-digit number is read when that group exists), ${name} is a named group, $$ is a literal $, and a $ followed by anything else, or at the end, is a literal $. A group that did not take part is replaced by "". In Leviathan source write the replacement as a raw string (r"${name}") or escape it ("\${name}"), because ${...} inside an ordinary string is interpolation.
Parameters
- s
- The text to search.
- replacement
- The replacement text, with the
$references described above.
Returns
The text with every match replaced.
Throws
RegexException- when the replacement names a group the pattern does not declare, refers to a group number the pattern does not have, or has a
${with no closing}.
Examples
Replacement references
Regex num = Regex(r"\d+");
console.writeln(num.replace("a1b22c333", "#"));
console.writeln(num.replace("a1b22c333", "#", 2));
Regex kv = Regex(r"(\w+)=(\w+)");
console.writeln(kv.replace("a=1 b=2", r"$2=$1"));
Regex named = Regex(r"(?<k>\w+)=(?<v>\w+)");
console.writeln(named.replace("a=1 b=2", r"${v}:${k}"));
console.writeln(num.replace("cost 5", r"$$$0"));
try {
kv.replace("a=1", r"$3");
} catch (RegexException e) {
console.writeln(e.message);
}
a#b#c#
a#b#c333
1=a 2=b
1:a 2:b
cost $5
regex: group reference out of range in replacement: $3
replace(string s, string replacement, int count) -> stringReplace the first matches with a replacement string.
The replacement string uses the same $ references as replace(s, replacement).
Parameters
- s
- The text to search.
- replacement
- The replacement text, with
$references. - count
- The most matches to replace; the rest are left as they are.
Returns
The text with up to count matches replaced.
Throws
RegexException- when the replacement names a group the pattern does not declare, refers to a group number the pattern does not have, or has a
${with no closing}.
split
split(string s) -> Array<string>Split a string at every match.
When the pattern has capture groups, the text of each group is put into the result after the piece that precedes the match. Pieces next to the ends or to each other can be empty.
Parameters
- s
- The text to split.
Returns
The pieces between the matches; a string with no match gives one piece, the whole string.
Examples
Splitting
Regex sep = Regex(r"\s*,\s*");
console.writeln(sep.split("a , b,c"));
console.writeln(sep.split("abc"));
console.writeln(Regex(",").split("a,b,"));
console.writeln(Regex("([,;])").split("a,b"));
[a, b, c]
[abc]
[a, b, ]
[a, ,, b]
split(string s, int limit) -> Array<string>Split a string at the first matches, up to a number of pieces.
Parameters
- s
- The text to split.
- limit
- The most pieces to return; the unsplit rest of the string is the last piece. A value of zero or less means no limit.
Returns
The pieces.
See also
- compile — Compile a pattern that comes from data, without throwing.
- Match — A successful match: where it is, what it matched, and its capture groups.
- RegexOptions — Options that change how a pattern is interpreted: ASCII case folding, line anchors and whether
.crosses line breaks. - Group — One capture group of a match: whether it took part, and where and what it matched.
- RegexException — The exception thrown for a malformed pattern, and for a malformed or out-of-range reference in a replacement string.
- regex — Regular expressions: the
Regexlibrary and its namespace-level helpers.