regex
import regex from "regex";regex finds patterns in text. Compile a pattern once into a Regex, then
search with it as often as you like:
let re: regex.Regex = regex.compile(r"""(?<year>\d{4})-(\d{2})-(\d{2})""") or {
io.println("bad pattern: {err}");
return 1;
};
let text: []const u8 = "released 2026-10-13";
let c: regex.Captures = re.captures(text) or { return 1; };
io.println("year {c.named("year")!.of(text)}, month {c.get(2)!.of(text)}");Write patterns as raw strings, r"""...""": a backslash and a brace are
then just characters, as the pattern needs them (in an ordinary string,
\d is an error and {4} starts an interpolation).
Matching takes time proportional to the text times the pattern, whatever
both are: there are no patterns that make a search run away, so patterns
and text from outside your program are safe to match. The price is that
backreferences (\1) and lookaround ((?=...), (?<=...)) aren’t
supported; they’re compile errors that say so. Everything else of the
usual syntax is there.
Text is UTF-8: . and a class match one character (a code point), and
every position the module gives you is a byte offset into the text, never
inside a character. \d, \w, \s, \b and (?i) work on ASCII.
The module is pure: it only computes.
Syntax
| Pattern | Matches |
|---|---|
x | the character x |
\x | x itself, for any punctuation: \. \* \( \\ … |
\t \n \r \f \v \a | tab, newline, carriage return, form feed, vertical tab, bell |
\xHH, \x{263A} | the character with that code |
. | any character but a newline (any at all with (?s)) |
[abc] [a-z] [^0-9] | one character in (or not in) the set; \d \w \s work inside |
\d \w \s | a digit, a word character ([0-9A-Za-z_]), white space |
\D \W \S | anything but those |
^ $ | the start and the end of the text (of a line with (?m)) |
\A \z | the start and the end of the text, always |
\b \B | a word boundary, and not one |
(re) | a group that captures |
(?<name>re) | a named group ((?P<name>re) works too) |
(?:re) | a group that doesn’t capture |
a|b | a, or else b |
re* re+ re? | 0 or more, 1 or more, 0 or 1 (as many as possible) |
re{n} re{n,} re{n,m} | exactly n, at least n, n to m (up to 1000) |
re*? re+? re?? re{n,m}? | the same, as few as possible |
(?i) (?m) (?s) (?U) | flags to the end of the group: ignore case, multi-line, . matches a newline, swap greedy and lazy |
(?i:re) | flags for re only; (?-i) turns one off |
When several matches are possible, you get the one a step-by-step matcher
would find first: the leftmost, and from there the first alternative and
the greedy (or lazy) repeat you wrote. a|ab finds a in ab.
Types
Regex
A compiled pattern. It frees its memory when it goes out of scope, and is
copied whole on assignment. Searching doesn’t change it, so one Regex
can be used by many searches, from many threads.
Span
pub struct Span {
start: i64; // the first byte of the match
end: i64; // just past its last byte
}Where a match (or a group) is. s.len() is its length in bytes and
s.of(text) its text: text[s.start..s.end].
Captures
The groups of one match: c.get(i) is group i (0 is the whole match),
c.named(name) a named group, both null when the group didn’t take part
in the match. c.len() counts the groups with group 0. get traps for a
group number the pattern doesn’t have.
RegexError
| Variant | Value | Description | When |
|---|---|---|---|
Syntax | 1 | invalid regular expression | a pattern that isn’t UTF-8 |
UnclosedGroup | 2 | missing ’)’ | a(b |
UnopenedGroup | 3 | unmatched ’)’ | a)b |
UnclosedClass | 4 | missing ’]’ | [ab |
BadRange | 5 | invalid class range | [z-a] |
BadEscape | 6 | invalid escape | \q, \x4 |
BadRepeat | 7 | repeat of nothing, or a repeat repeated | *a, a**, a{3,2} |
RepeatTooBig | 8 | repeat count above MAX_REPEAT | a{1001} |
BadGroupName | 9 | invalid or duplicate group name | (?<1x>a), two groups named x |
BadFlag | 10 | unknown flag | (?q) |
Unsupported | 11 | not supported by a linear-time engine | \1, (?=a), (?<!a), (?>a) |
TooBig | 12 | pattern compiles to more than MAX_PROGRAM instructions | (a{1000}){1000} |
Constants
| Constant | Value | Is |
|---|---|---|
MAX_REPEAT | 1000 | the largest count in {n,m} |
MAX_PROGRAM | 100000 | instructions a pattern may compile to |
MAX_GROUPS | 1000 | capturing groups in a pattern |
Compiling
compile
pub fn compile(pattern: []const u8) -> Regex or RegexErrorThe pattern compiled. Compile once and keep the Regex; compiling costs
more than a search.
error_at
pub fn error_at(pattern: []const u8) -> ?i64Where compile finds the pattern wrong, as a byte offset into it, or
null when it compiles. For pointing at the mistake in a message:
let pat: []const u8 = "ab(cd";
let r: regex.Regex = regex.compile(pat) or {
io.println("{err} at byte {regex.error_at(pat)!}"); // missing ')' at byte 2
return 1;
};escape
pub fn escape(text: []const u8) -> stringtext with every special character escaped: a pattern that matches
exactly text. regex.escape("1+1") is 1\+1.
pattern, group_count, group_index
pub fn Regex.pattern(*const self) -> []const u8
pub fn Regex.group_count(*const self) -> i64
pub fn Regex.group_index(*const self, name: []const u8) -> ?i64The pattern it was compiled from; its capturing groups (not counting 0); the number of a named group.
Searching
is_match
pub fn Regex.is_match(*const self, text: []const u8) -> boolWhether the pattern matches anywhere in text. The fastest search: it
stops at the first match it sees.
find, find_at
pub fn Regex.find(*const self, text: []const u8) -> ?Span
pub fn Regex.find_at(*const self, text: []const u8, at: i64) -> ?SpanThe first match, or null. find_at starts the search at byte at but
sees the whole text, so ^ and \b look at what comes before at. It
traps for at outside 0..text.len or inside a character.
let re: regex.Regex = regex.compile(r"""\d+""") or { return 1; };
let m: regex.Span = re.find("order 66 now") or { return 1; }; // 6..8captures, captures_at
pub fn Regex.captures(*const self, text: []const u8) -> ?Captures
pub fn Regex.captures_at(*const self, text: []const u8, at: i64) -> ?CapturesThe first match with its groups, or null. A group inside a repeat holds
its last repetition: (a|b)+ over abab gives group 1 at 3..4.
matches
pub fn Regex.matches(*const self, text: []const u8) -> MatchIterEvery match, left to right, none overlapping:
let words: regex.Regex = regex.compile(r"""\w+""") or { return 1; };
for (m in words.matches("one two three")) {
io.println(m.of("one two three"));
}A pattern that can match nothing finds the empty spots too, one per
character position, but never one right after the previous match: a*
over baaa finds 0..0 and 1..4. The iterator borrows the Regex and
the text: don’t keep it past either.
split
pub fn Regex.split(*const self, text: []const u8) -> SplitIterThe text between the matches, including what comes before the first and
after the last: , splits a,,b into a, empty, b. An empty match at
the very start or end splits nothing, so x* splits abc into a, b,
c.
let sep: regex.Regex = regex.compile(r"""\s*[,;]\s*""") or { return 1; };
for (part in sep.split("a , b;c")) { io.println(part); } // a, b, cReplacing
replace, replace_all
pub fn Regex.replace(*const self, text: []const u8, with: []const u8) -> string
pub fn Regex.replace_all(*const self, text: []const u8, with: []const u8) -> stringA new string: text with the first match (or every match) replaced. In
with, $1 is group 1’s text ($0 the whole match), ${name} a named
group’s, ${12} group 12, and $$ a dollar sign. A group that didn’t
match is empty.
let swap: regex.Regex = regex.compile(r"""(\w+)@(\w+)""") or { return 1; };
let s: string = swap.replace_all("ann@x, bob@y", "$2:$1"); // "x:ann, y:bob"Any other $ traps (a $5 with no group 5, $x, an unclosed ${):
write $$ for a dollar sign. Note that ${...} in an ordinary string is
an interpolation; write "$\{name\}", or use a raw string.
Last modified: