Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Match text with regular expressions

std.regex compiles a pattern once into a Regex, then searches text with it. Matching takes time proportional to the length of the text, whatever the pattern: there is no backtracking, so no pattern can make a search run for hours. compile takes Limits, which also bound the pattern itself, so a pattern from an untrusted source is safe to compile.

Keep the compiled Regex when searching repeatedly. The library selects prefix filters and faster matching engines where the pattern permits them; captures, match ordering and resource limits keep the same meaning.

Find matches

find returns the first match as byte offsets; text copies the matched part. find_all returns every match and fails rather than return more than its limit:

import std.regex

def main() -> Result[(), AllocError | IoError | std.regex.RegexError]:
    limits = std.regex.limits()
    year = std.regex.compile("\\b(19|20)\\d\\d\\b", &limits)?
    text = "Released 1999, revised 2024, not 12345."
    match year.find(text)?:
        case Some(found):
            print(found.start, found.end, found.text(text)?)?
        case Nothing:
            print("no year")?
    print(len(year.find_all(text, 10)?))?
    print(year.is_match("in 1850")?)?
    print(str.repr(year.find_all(text, 1))?)?
    Ok(())
9 13 1999
2
False
Result[list[std.regex.Match], std.regex.RegexError].Err(std.regex.RegexError.LimitExceeded(std.regex.Limit.Matches))

A Plenty string needs \\ for each backslash in the pattern. \d, \w, \s and \b are ASCII-only; for other scripts, use properties such as \p{Alphabetic}.

Groups and replacement

Parentheses capture. (?P<name>...) names a group. captures returns every group of the first match; a group that did not take part is Nothing. In a replacement template, $1 and ${name} insert a group and $$ inserts a dollar sign:

import std.regex

def main() -> Result[(), AllocError | IoError | std.regex.RegexError]:
    limits = std.regex.limits()
    date = std.regex.compile("(?P<year>\\d{4})-(?P<month>\\d{2})", &limits)?
    text = "from 2024-05 to 1999-12"
    match date.captures(text)?:
        case Some(groups):
            match groups.name("year"):
                case Some(group):
                    print(group.text(text)?)?
                case Nothing:
                    print("group did not match")?
            match groups.get(2):
                case Some(group):
                    print(group.text(text)?)?
                case Nothing:
                    print("group did not match")?
        case Nothing:
            pass
    print(date.replace_all(text, "${month}/$1", 100)?)?
    words = std.regex.compile("\\s*,\\s*", &limits)?
    print(" | ".join(words.split("red , green,blue", 10)?)?)?
    Ok(())
2024
05
from 05/2024 to 12/1999
red | green | blue

The last argument of replace_all and split is a limit on the result size or the number of pieces. Write ${1}0 when a group number is followed by a digit. A bare $name is an error: use ${name}.

Errors and limits

A pattern error reports its byte offset. A pattern that would build too large a matcher, and a search that would do too much work, are limit errors:

import std.regex
from std.regex import Limits

def main() -> Result[(), AllocError | IoError | std.regex.RegexError]:
    limits = std.regex.limits()
    print(str.repr(std.regex.compile("total: (\\d+", &limits))?)?
    print(str.repr(std.regex.compile("(a{1000}){1000}", &limits))?)?
    tight = Limits(1000, 16, 100, 1000, 1000000, 100)
    word = std.regex.compile("\\w+!", &tight)?
    print(str.repr(word.find("no exclamation mark in this sentence"))?)?
    Ok(())
Result[std.regex.Regex, std.regex.RegexError].Err(std.regex.RegexError.Syntax(std.regex.SyntaxError(offset=7, message="unclosed group")))
Result[std.regex.Regex, std.regex.RegexError].Err(std.regex.RegexError.LimitExceeded(std.regex.Limit.Instructions))
Result[Option[std.regex.Match], std.regex.RegexError].Err(std.regex.RegexError.LimitExceeded(std.regex.Limit.Work))

The default limits suit patterns written into a program. For patterns from users, lower them to what a request may spend.

See the std.regex reference for the full syntax, every limit, and searching bytes.