LEVIATHAN v962456e · 962456eee1

Standard Library

namespace regex

Regular expressions: the Regex library and its namespace-level helpers.

since 0.1.0-alpha.1linuxwindowswasm

Overview

The regex namespace holds one-call conveniences that compile a pattern (through a small cache of recently used patterns) and run it: compile, isMatch, find, matches, replace, split and count, each taking the input string first and the pattern second, with an optional RegexOptions or flag string ("i", "m", "s") after. escape turns literal text into a pattern that matches exactly that text. It also holds the lower-level compiled-program boundary (compileProgram and the program... functions) for patterns that are compiled once, ahead of time.

Matching is byte-oriented and runs in time linear in the input: there is no backtracking, so no pattern can make a match run away. Backreferences and lookaround are not supported.

A pattern that is a literal in the source is code: a malformed one throws RegexException. A pattern that arrives as data should go through regex::compile, which returns None instead of throwing.

Description

The regex namespace holds the matching engine behind Regex, plus helper functions that take the pattern as an argument. It is written entirely in Leviathan and needs no native support, so it behaves the same on every platform. Most code uses the Regex type (see its entry) and reaches this namespace only for the helpers and for compile-time patterns.

Linear time. The engine never backtracks. Matching takes time proportional to the length of the input times the size of the compiled pattern, whatever the pattern is, so a pattern supplied by a user cannot trigger runaway matching. A pattern such as ^(a+)+$, which freezes a backtracking engine on a string of many as followed by !, is answered at once.

A pattern that would defeat a backtracking engine

Regex nested = Regex("^(a+)+$");
string input = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!";
console.writeln(nested.isMatch(input));
Regex clash = Regex("(x+x+)+y");
console.writeln(clash.isMatch("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"));
false
false

Syntax. The engine reads these constructs:

  • literals and backslash escapes such as \., \( and \{;
  • ., which matches any one byte except a newline unless the dotAll option is on;
  • character classes [abc], [a-z], [^abc], and the shorthands \d, \w and \s with their negations \D, \W and \S, which also work inside a class ([\w-]);
  • alternation a|b;
  • groups: capturing (...), non-capturing (?:...) and named (?<name>...);
  • greedy quantifiers *, +, ?, {n}, {n,}, {n,m}, and their lazy forms with a trailing ?;
  • anchors ^ and $, which also match at line breaks in multiline mode, and the word boundary \b.

Backreferences and lookaround ((?=, (?!, (?<=) are not supported: a lookaround is rejected as a malformed pattern, and \1 is not a backreference (it matches the digit 1 itself). Inline option groups such as (?i) are also rejected; pass options through RegexOptions or the flags string.

Everything is byte-oriented. Offsets and lengths count bytes, and . matches one byte, so a character that takes two bytes in UTF-8 is matched by ^..$ and not by ^.$.

Syntax and byte semantics

console.writeln(regex::isMatch("foo bar", "\\bbar\\b"));
console.writeln(regex::isMatch("foobar", "\\bbar\\b"));
Match? lazy = regex::find("aaa", "a+?");
if (lazy != None) { console.writeln(lazy.value); }
Match? greedy = regex::find("aaa", "a+");
if (greedy != None) { console.writeln(greedy.value); }
console.writeln(regex::isMatch("a-b", "^[\\w-]+$"));
console.writeln(regex::isMatch("é", "^.$"));
console.writeln(regex::isMatch("é", "^..$"));
console.writeln(regex::compile("a(?=b)") == None);
true
false
a
aaa
true
false
true
true

One-call helpers

The functions compile, isMatch, find, matches, replace, split and count take the string first and the pattern second: regex::isMatch(s, pattern). Each also has a form with a RegexOptions value and a form with a flags string after the pattern, and replace also accepts a (Match) => string function in place of a replacement string. They compile the pattern on every call, helped by a small cache of recently used patterns, so for a pattern used in a loop, build one Regex instead. As with the Regex constructor, a malformed pattern passed to any helper other than compile throws RegexException; regex::compile returns None.

regex::escape(s) returns s with every pattern metacharacter quoted, so it can be embedded as literal text in a larger pattern.

Helpers and escape

console.writeln(regex::count("a1b2c3", "[0-9]"));
console.writeln(regex::isMatch("ABC", "abc"));
console.writeln(regex::isMatch("ABC", "abc", "i"));
console.writeln(regex::replace("a b  c", "\\s+", "_"));
console.writeln(regex::split("a1b22c", "\\d+"));
console.writeln(regex::escape("1+1=2 (really)?"));
3
false
true
a_b_c
[a, b, c]
1\+1=2 \(really\)\?

Compiling a constant pattern at build time

regex::compileProgram(pattern, flags) turns a pattern into a flat Array<int> program, and Regex::FromProgram(program) wraps a program as a Regex in constant time. Because compileProgram is pure, a comptime declaration runs it while the program is being built. A malformed constant pattern then fails the build at the line of the pattern, not as a surprise at run time, and no parsing is left to do when the program starts. A compiled Regex also has isMatchAll and countAll, which test a whole array of strings with one prepared engine.

A comptime pattern applied to a column

comptime Array<int> EMAIL_P = regex::compileProgram("^[\\w.+-]+@[\\w-]+\\.[\\w.]+$", "");
Regex emailRe = Regex::FromProgram(EMAIL_P);
console.writeln(emailRe.isMatch("ann@example.com"));
console.writeln(emailRe.isMatch("not an email"));
Array<string> column = ["a@b.co", "nope", "x@y.org"];
console.writeln(emailRe.isMatchAll(column));
console.writeln(emailRe.countAll(column));
true
false
[true, false, true]
[1, 0, 1]

Rules

  • Matching time is linear in the input length times the compiled pattern's size; there is no backtracking.
  • Backreferences and lookaround are not supported; inline (?i)-style groups are rejected.
  • Offsets and lengths are in bytes.
  • compileProgram and Regex::FromProgram are the boundary between pattern text and matching; a program is an Array<int> and can be built once and reused.
  • A constant pattern passed to a comptime call is checked when the program is built.

Examples

Where a quantifier belongs

Array<string> words = regex::split("one, two,three ,four", "\\s*,\\s*");
console.writeln(words);
console.writeln(regex::count("banana", "an"));
console.writeln(regex::replace("2026-10-02", "-", "/"));
[one, two, three, four]
2
2026/10/02

Notes

The pattern language is deliberately close to the common core of Perl, C# and JavaScript, minus the features that need backtracking.

Examples

Conveniences in one place

console.writeln(regex::isMatch("order 66", r"\d+"));
Match? m = regex::find("key=value", r"(\w+)=(\w+)");
if (m != None) {
    console.writeln(m.group(1).value);
    console.writeln(m.group(2).value);
}
console.writeln(regex::replace("a1b22c", r"\d+", "#"));
console.writeln(regex::split("a, b;c", r"[,;]\s*"));
console.writeln(regex::count("banana", "an"));
console.writeln(regex::escape("1+1=2?"));
true
key
value
a#b#c
[a, b, c]
2
1\+1=2\?

Functions

compile

compile(string pattern) -> Regex | None

Compile a pattern that comes from data, without throwing.

Use it for patterns read from a file, a configuration or user input. For a pattern written in the source, the Regex constructor is the better choice because it fails loudly.

Parameters

pattern
The pattern text.

Returns

The compiled Regex, or None when the pattern is malformed.

Examples

A good and a bad pattern

Regex? r = regex::compile(r"(\d+)");
if (r != None) console.writeln(r.groupCount());
console.writeln(regex::compile("(") == None);
Regex? ci = regex::compile("abc", "i");
if (ci != None) console.writeln(ci.isMatch("xABCx"));
1
true
true
compile(string pattern, RegexOptions options) -> Regex | None

Compile a pattern that comes from data, with options, without throwing.

Parameters

pattern
The pattern text.
options
The options to apply.

Returns

The compiled Regex, or None when the pattern is malformed.

compile(string pattern, string flags) -> Regex | None

Compile a pattern that comes from data, with a flag string, without throwing.

Parameters

pattern
The pattern text.
flags
Any of the letters i, m and s, in any order; "" for none.

Returns

The compiled Regex, or None when the pattern is malformed or flags contains another character.

See also: Regex

compileProgram

compileProgram(string pattern, string flags) -> Array<int>

Compile a pattern into a self-contained program that the program... functions run.

The result is an ordinary Array<int> holding everything the matcher needs, so it can be stored, passed around and reused for any number of inputs. The function is pure, and a call with constant arguments is evaluated when the program is built: written as a comptime value, a malformed constant pattern is reported at the source line of the pattern instead of when the program runs. Most code should use Regex and Match instead and reach this function only for ahead-of-time compilation (wrap the result with Regex::FromProgram).

The syntax covers literals and escapes, ., character classes, \d \w \s (and the uppercase negations), alternation, capturing, non-capturing and named groups, greedy and lazy quantifiers, anchors, word boundaries. Backreferences and lookaround are not supported. Offsets and lengths are counted in bytes.

Parameters

pattern
The pattern text.
flags
Zero or more of i (ignore ASCII case), m (^ and $ also match at line breaks) and s (. also matches a line break), in any order; "" for none.

Returns

The compiled program.

Throws

RuntimeException
when the pattern is malformed or flags contains another character; the message names the problem and ends with at offset N.

Examples

Compile once, then match

Array<int> prog = regex::compileProgram(r"(\d+)-(\d+)", "");
console.writeln(regex::programIsMatch(prog, "call 555-1234 now"));
console.writeln(regex::programIsMatch(prog, "no digits"));
Array<int> ic = regex::compileProgram("hello", "i");
console.writeln(regex::programIsMatch(ic, "Say HELLO"));
try {
    regex::compileProgram("a(b", "");
} catch (RuntimeException e) {
    console.writeln("caught: ${e.message}");
}
true
false
true
caught: regex: unterminated group at offset 3

See also: FromProgram, programIsMatch

count

count(string s, string pattern) -> int

Count the non-overlapping matches of a pattern in a string.

Parameters

s
The text to search.
pattern
The pattern text.

Returns

The number of matches.

Throws

RegexException
when the pattern is malformed.

Examples

Counting

console.writeln(regex::count("a1b22", r"\d"));
console.writeln(regex::count("banana", "an"));
console.writeln(regex::count("AaA", "a", "i"));
3
2
3
count(string s, string pattern, RegexOptions o) -> int

Count the non-overlapping matches of a pattern in a string, with options.

Parameters

s
The text to search.
pattern
The pattern text.
o
The options to apply.

Returns

The number of matches.

Throws

RegexException
when the pattern is malformed.
count(string s, string pattern, string flags) -> int

Count the non-overlapping matches of a pattern in a string, with a flag string.

Parameters

s
The text to search.
pattern
The pattern text.
flags
Any of the letters i, m and s, in any order; "" for none.

Returns

The number of matches.

Throws

RegexException
when the pattern is malformed or flags contains another character.

escape

escape(string literal) -> string

Turn text into a pattern that matches exactly that text.

Every metacharacter (\ . ^ $ | ( ) [ ] { } * + ?) is preceded by a backslash. Use it to put user-supplied text inside a larger pattern.

Parameters

literal
The text to match literally.

Returns

The escaped pattern.

Examples

Matching text that contains metacharacters

console.writeln(regex::escape("1+1=2?"));
Regex e = Regex(regex::escape("1+1=2"));
console.writeln(e.isMatch("so 1+1=2 ok"));
console.writeln(regex::isMatch("11=2", regex::escape("1+1=2")));
1\+1=2\?
true
false

find

find(string s, string pattern) -> Match | None

Find the first match of a pattern in a string.

Parameters

s
The text to search.
pattern
The pattern text.

Returns

The first match, or None when there is none.

Throws

RegexException
when the pattern is malformed.

Examples

Finding with and without options

Match? m = regex::find("key=value", r"(\w+)=(\w+)");
if (m != None) console.writeln(m.group(2).value);
Match? line = regex::find("a\nb", "^b", "m");
if (line != None) console.writeln(line.index);
Match? dot = regex::find("a\nb", "a.b", RegexOptions(dotAll: true));
if (dot != None) console.writeln(dot.length);
console.writeln(regex::find("zzz", r"\d") == None);
value
2
3
true
find(string s, string pattern, RegexOptions o) -> Match | None

Find the first match of a pattern in a string, with options.

Parameters

s
The text to search.
pattern
The pattern text.
o
The options to apply.

Returns

The first match, or None when there is none.

Throws

RegexException
when the pattern is malformed.
find(string s, string pattern, string flags) -> Match | None

Find the first match of a pattern in a string, with a flag string.

Parameters

s
The text to search.
pattern
The pattern text.
flags
Any of the letters i, m and s, in any order; "" for none.

Returns

The first match, or None when there is none.

Throws

RegexException
when the pattern is malformed or flags contains another character.

isMatch

isMatch(string s, string pattern) -> bool

Test whether a pattern matches anywhere in a string.

The pattern is compiled on each call; compiled patterns are kept in a small cache, so repeating the same pattern is cheap. For many strings and one pattern, a Regex object is clearer.

Parameters

s
The text to search.
pattern
The pattern text.

Returns

true when some part of s matches.

Throws

RegexException
when the pattern is malformed.

Examples

Testing with and without options

console.writeln(regex::isMatch("order 66", r"\d+"));
console.writeln(regex::isMatch("HELLO", "hello"));
console.writeln(regex::isMatch("HELLO", "hello", "i"));
console.writeln(regex::isMatch("HELLO", "hello", RegexOptions(ignoreCase: true)));
try {
    regex::isMatch("x", "(");
} catch (RegexException e) {
    console.writeln(e.message);
}
true
false
true
true
regex: unterminated group at offset 1
isMatch(string s, string pattern, RegexOptions o) -> bool

Test whether a pattern matches anywhere in a string, with options.

Parameters

s
The text to search.
pattern
The pattern text.
o
The options to apply.

Returns

true when some part of s matches.

Throws

RegexException
when the pattern is malformed.
isMatch(string s, string pattern, string flags) -> bool

Test whether a pattern matches anywhere in a string, with a flag string.

Parameters

s
The text to search.
pattern
The pattern text.
flags
Any of the letters i, m and s, in any order; "" for none.

Returns

true when some part of s matches.

Throws

RegexException
when the pattern is malformed or flags contains another character.

matches

matches(string s, string pattern) -> Array<Match>

Find every non-overlapping match of a pattern in a string.

Parameters

s
The text to search.
pattern
The pattern text.

Returns

The matches in order; empty when there are none.

Throws

RegexException
when the pattern is malformed.

Examples

All matches

console.writeln(regex::matches("a1b22", r"\d+").length());
console.writeln(regex::matches("A1b22", "[a-z]", "i").length());
for (Match m in regex::matches("x=1, y=22", r"\d+")) console.writeln(m.value);
2
2
1
22
matches(string s, string pattern, RegexOptions o) -> Array<Match>

Find every non-overlapping match of a pattern in a string, with options.

Parameters

s
The text to search.
pattern
The pattern text.
o
The options to apply.

Returns

The matches in order; empty when there are none.

Throws

RegexException
when the pattern is malformed.
matches(string s, string pattern, string flags) -> Array<Match>

Find every non-overlapping match of a pattern in a string, with a flag string.

Parameters

s
The text to search.
pattern
The pattern text.
flags
Any of the letters i, m and s, in any order; "" for none.

Returns

The matches in order; empty when there are none.

Throws

RegexException
when the pattern is malformed or flags contains another character.

programCount

programCount(Array<int> program, string input) -> int

Count the non-overlapping matches of a compiled program in the input.

An empty match is counted once at its position and the scan then moves one byte forward, so the count always terminates.

Parameters

program
A program from compileProgram.
input
The text to search.

Returns

The number of matches.

Throws

RuntimeException
when program is not a program produced by compileProgram.

Examples

Counting numbers

Array<int> prog = regex::compileProgram(r"\d+", "");
console.writeln(regex::programCount(prog, "a1 b22 c333"));
console.writeln(regex::programCount(prog, "none"));
3
0

See also: count

programCountAll

programCountAll(Array<int> program, Array<string> rows) -> Array<int>

Count the matches of a compiled program in every string of an array.

The program is set up once and reused for the whole array.

Parameters

program
A program from compileProgram.
rows
The strings to search.

Returns

One count per row, in order.

Throws

RuntimeException
when program is not a program produced by compileProgram.

Examples

Counting words per row

Array<int> words = regex::compileProgram(r"[a-z]+", "");
console.writeln(regex::programCountAll(words, ["one two", "", "a b c"]));
[2, 0, 3]

See also: countAll

programFind

programFind(Array<int> program, string input) -> Array<int>

Find the first match of a compiled program and return its capture positions.

The result holds start and end byte offsets in pairs: slots 0 and 1 are the whole match, then two slots for each capture group in order. A group that did not take part in the match has -1 in both of its slots. When there is no match the array is empty. Most code should use Regex.find, which wraps these numbers in a Match.

Parameters

program
A program from compileProgram.
input
The text to search.

Returns

The capture positions, or an empty array when nothing matches.

Throws

RuntimeException
when program is not a program produced by compileProgram.

Examples

Reading capture positions

Array<int> prog = regex::compileProgram(r"(\d+)-(\d+)", "");
console.writeln(regex::programFind(prog, "call 555-1234 now"));
console.writeln(regex::programFind(prog, "none").length());
[5, 13, 5, 8, 9, 13]
0

See also: find

programIsMatch

programIsMatch(Array<int> program, string input) -> bool

Test whether a compiled program matches anywhere in the input.

Parameters

program
A program from compileProgram.
input
The text to search.

Returns

true when some part of input matches.

Throws

RuntimeException
when program is not a program produced by compileProgram.

Examples

Testing two inputs

Array<int> prog = regex::compileProgram(r"^\d{3}$", "");
console.writeln(regex::programIsMatch(prog, "123"));
console.writeln(regex::programIsMatch(prog, "1234"));
true
false

See also: compileProgram

programIsMatchAll

programIsMatchAll(Array<int> program, Array<string> rows) -> Array<bool>

Test a compiled program against every string of an array.

The program is set up once and reused for the whole array, which is cheaper than testing the strings one by one.

Parameters

program
A program from compileProgram.
rows
The strings to test.

Returns

One bool per row, in order: true where the row matches.

Throws

RuntimeException
when program is not a program produced by compileProgram.

Examples

Filtering a column

Array<int> prog = regex::compileProgram(r"^\d{3}$", "");
Array<string> rows = ["123", "12", "abc", "999"];
console.writeln(regex::programIsMatchAll(prog, rows));
[true, false, false, true]

See also: isMatchAll

replace

replace(string s, string pattern, (Match) => string fn) -> string

Replace every match of a pattern using a function that builds each replacement.

Parameters

s
The text to search.
pattern
The pattern text.
fn
Called once per match; returns the replacement text.

Returns

The text with every match replaced.

Throws

RegexException
when the pattern is malformed.

Examples

Replacing with a function

console.writeln(regex::replace("a1b22", r"\d+", (Match m) => "<${m.value}>"));
console.writeln(regex::replace("a1B22", "b", (Match m) => "[${m.value}]", "i"));
a<1>b<22>
a1[B]22
replace(string s, string pattern, (Match) => string fn, RegexOptions o) -> string

Replace every match of a pattern using a function, with options.

Parameters

s
The text to search.
pattern
The pattern text.
fn
Called once per match; returns the replacement text.
o
The options to apply.

Returns

The text with every match replaced.

Throws

RegexException
when the pattern is malformed.
replace(string s, string pattern, (Match) => string fn, string flags) -> string

Replace every match of a pattern using a function, with a flag string.

Parameters

s
The text to search.
pattern
The pattern text.
fn
Called once per match; returns the replacement text.
flags
Any of the letters i, m and s, in any order; "" for none.

Returns

The text with every match replaced.

Throws

RegexException
when the pattern is malformed or flags contains another character.
replace(string s, string pattern, string replacement) -> string

Replace every match of a pattern 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.
pattern
The pattern text.
replacement
The replacement text, with the $ references described above.

Returns

The text with every match replaced.

Throws

RegexException
when the pattern is malformed, or 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

Replacing text

console.writeln(regex::replace("a1b22c", r"\d+", "#"));
console.writeln(regex::replace("Hello hello", "hello", "bye", "i"));
console.writeln(regex::replace("a=1 b=2", r"(\w+)=(\w+)", r"$2=$1"));
console.writeln(regex::replace("cost 5", r"\d", r"$$$0"));
a#b#c
bye bye
1=a 2=b
cost $5
replace(string s, string pattern, string replacement, RegexOptions o) -> string

Replace every match of a pattern with a replacement string, with options.

The replacement string uses the same $ references as replace(s, pattern, replacement).

Parameters

s
The text to search.
pattern
The pattern text.
replacement
The replacement text, with $ references.
o
The options to apply.

Returns

The text with every match replaced.

Throws

RegexException
when the pattern is malformed, or 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 }.
replace(string s, string pattern, string replacement, string flags) -> string

Replace every match of a pattern with a replacement string, with a flag string.

The replacement string uses the same $ references as replace(s, pattern, replacement).

Parameters

s
The text to search.
pattern
The pattern text.
replacement
The replacement text, with $ references.
flags
Any of the letters i, m and s, in any order; "" for none.

Returns

The text with every match replaced.

Throws

RegexException
when the pattern is malformed or flags contains another character, or 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, string pattern) -> Array<string>

Split a string at every match of a pattern.

When the pattern has capture groups, the text of each group is put into the result after the piece that precedes the match.

Parameters

s
The text to split.
pattern
The pattern text.

Returns

The pieces between the matches; a string with no match gives one piece, the whole string.

Throws

RegexException
when the pattern is malformed.

Examples

Splitting

console.writeln(regex::split("a1b2c", r"\d"));
console.writeln(regex::split("aXbxc", "x", "i"));
console.writeln(regex::split("a, b;c", r"[,;]\s*"));
[a, b, c]
[a, b, c]
[a, b, c]
split(string s, string pattern, RegexOptions o) -> Array<string>

Split a string at every match of a pattern, with options.

Parameters

s
The text to split.
pattern
The pattern text.
o
The options to apply.

Returns

The pieces between the matches.

Throws

RegexException
when the pattern is malformed.
split(string s, string pattern, string flags) -> Array<string>

Split a string at every match of a pattern, with a flag string.

Parameters

s
The text to split.
pattern
The pattern text.
flags
Any of the letters i, m and s, in any order; "" for none.

Returns

The pieces between the matches.

Throws

RegexException
when the pattern is malformed or flags contains another character.

See also

  • Regex — A compiled regular expression.
  • Match — A successful match: where it is, what it matched, and its capture groups.
  • RegexException — The exception thrown for a malformed pattern, and for a malformed or out-of-range reference in a replacement string.