Rill v0.13 Reference

Standard library · Text

text/regex

Imported as import "text/regex" as regex, its names are then regex.…. Every signature below is the one the checker infers.

Regular expressions: a small backtracking matcher, written in Rill.

What it knows: literals and the escapes \. \\ \n \t; . (anything but a newline); classes [abc], [a-z0-9], [^...], and \d \w \s with their negations \D \W \S, alone or inside a class; the anchors ^ and $; groups (...), which capture, and (?:...), which do not; |; and the counts * + ? {n} {n,} {n,m}, each greedy, or lazy with a ? after it. A match is the leftmost one, alternatives tried in the order written and counts as many as they can — Perl's answer, not POSIX's longest.

Bytes, like every string here: . is one byte, and a class of é is a class of its two bytes. Backtracking keeps the matcher small and the patterns familiar, and can be slow on a pattern built to defeat it — (a*)*b on a long run of as — as it is everywhere else it is used.

date = regex.compile("(\\d{4})-(\\d{2})-(\\d{2})")?
regex.is_match(date, "on 2026-09-14 at")            # => true
regex.find(date, "on 2026-09-14 at")                # => Some((3, 13))
regex.captures(date, "on 2026-09-14 at")            # => Some(Cons(2026-09-14, Cons(2026, Cons(09, Cons(14, Nil)))))
spaces = regex.compile("\\s+")?
regex.replace_all(spaces, "a  b   c", " ")           # => a b c
regex.split(regex.compile(",\\s*")?, "a, b,c")   # => Cons(a, Cons(b, Cons(c, Nil)))

Types

Node

  • Lit(byte: Int)
  • Any
  • Class(set: Buf(U8))
  • Start
  • End
  • Seq(items: List(Node))
  • Alt(branches: List(Node))
  • Group(index: Int, inner: Node)
  • Rep(inner: Node, least: Int, most: Int, greedy: Bool)

Re

A compiled pattern: the tree and how many groups it captures.

  • Re(node: Node, groups: Int)

Functions

fn is_match(r: Re, s: Str) -> Bool

r = regex.compile("^[a-z]+$")?
regex.is_match(r, "hello")    # => true
regex.is_match(r, "Hello")    # => false

fn find(r: Re, s: Str) -> Option((Int, Int))

Where the first match is: Some((start, end)), the end excluded.

r = regex.compile("\\d+")?
regex.find(r, "room 101")   # => Some((5, 8))
regex.find(r, "no digits")  # => None

fn find_from(r: Re, s: Str, from: Int) -> Option((Int, Int))

r = regex.compile("\\d+")?
regex.find_from(r, "1 and 22", 1)   # => Some((6, 8))

fn find_all(r: Re, s: Str) -> List((Int, Int))

Every match, left to right, none overlapping; an empty match moves on by one byte so that the search always ends.

r = regex.compile("\\d+")?
regex.find_all(r, "1, 22, 333")   # => Cons((0, 1), Cons((3, 5), Cons((7, 10), Nil)))

fn captures(r: Re, s: Str) -> Option(List(Str))

The match and every group, group 0 the whole; a group that took no part is "".

r = regex.compile("(\\w+)@(\\w+)")?
regex.captures(r, "mail me@host now")   # => Some(Cons(me@host, Cons(me, Cons(host, Nil))))

fn captures_from(r: Re, s: Str, from: Int) -> Option(List(Str))

r = regex.compile("(\\d)")?
regex.captures_from(r, "1 2", 1)   # => Some(Cons(2, Cons(2, Nil)))

fn replace_all(r: Re, s: Str, with: Str) -> Str

Every match replaced by what f makes of it.

r = regex.compile("[aeiou]")?
regex.replace_all(r, "banana", "_")   # => b_n_n_

fn replace_by(r: Re, s: Str, f: (Str) -> Str) -> Str

r = regex.compile("\\d+")?
regex.replace_by(r, "a1b22", \m -> "<" + m + ">")   # => a<1>b<22>

fn split(r: Re, s: Str) -> List(Str)

The pieces between the matches.

r = regex.compile("\\s*;\\s*")?
regex.split(r, "a ; b;c")   # => Cons(a, Cons(b, Cons(c, Nil)))

fn compile(pattern: Str) -> Result(Re, Str)

res_map(regex.compile("a(b"), \r -> r.groups)   # => Err(`a(b`: `(` is never closed at byte 1)
r = regex.compile("a+")?
regex.is_match(r, "caab")   # => true

Tests

  • test_literals_classes_anchors
  • test_counts_and_groups
  • test_all_replace_split
  • test_bad_patterns