SCUA

How-to

Match text with regular expressions

The regex module is a ReDoS-safe regular-expression engine: it matches in linear time and can never hang on a pathological pattern, so it is safe to run on patterns that came from a user or a model. It needs no capability — it is pure computation. The dialect is the RE2 / Rust / ripgrep subset, so if you know ripgrep's regexes you already know these.

import regex

#Compile once, then reuse

regex.compile(pattern) returns Ok(re) or a clear Error — it never silently mis-compiles. Compile a pattern once and reuse the re across many matches.

match regex.compile("fn\\s+(?<name>\\w+)")
  Ok(re) -> ...          -- use `re` below
  Error(why) -> print(`bad pattern: {why}`)
end

#The match verbs

All offsets are 1-based codepoint positions (the same convention as fs.grep's col), and the engine is codepoint-native, so multibyte text just works. (The stop field is named stop, not end, because end is a keyword.)

  • regex.is_match(re, s)bool — does re match anywhere in s?
  • regex.find(re, s){ start, stop, text } | nil — the leftmost match (half-open start..stop), or nil.
  • regex.find_all(re, s){ { start, stop, text } } — every non-overlapping match, left to right.
  • regex.captures(re, s){ groups, named } | nil — the match with its groups. groups is a 1-based list (group k → its {start, stop, text}, or nil if it didn't participate); named maps each (?<name>) to its group.
  • regex.replace(re, s, repl)string — replace every match. In repl, $1..$9 are numbered groups, ${name} is a named group, $0 the whole match, and $$ a literal $.
  • regex.source(re)string — the pattern the re was compiled from.
match regex.compile("(?<user>\\w+)@(?<host>\\w+)")
  Ok(re) -> do
    print(regex.is_match(re, "a@example"))                     -- true
    let m = regex.find(re, "mail: a@example here")             -- { start=7, stop=16, text="a@example" }
    let caps = regex.captures(re, "a@example")
    if caps != nil then print(caps.named.host.text) end        -- "example"
    print(regex.replace(re, "a@x b@y", "${host}/${user}"))     -- "x/a y/b"
  end
  Error(why) -> print(why)
end

(Inside a function that returns a Result you can use the postfix ? operator — let re = regex.compile(pattern)? — to unwrap the Ok or return the Error to your caller.)

#Supported syntax

Literals and . (any codepoint except newline); character classes [a-z], [^0-9], and the shorthands \w \s \d \W \S \D; anchors ^ $ \b \B; alternation a|b; groups — capturing (…), non-capturing (?:…), named (?<name>…); quantifiers * + ? {m} {m,} {m,n} and their lazy *? +? ?? forms; and the inline flag (?i) for case-insensitive matching.

#Loud failure, never silent

Backreferences and lookaround cannot be supported by a linear-time engine, so they — and other unsupported constructs — fail at compile with a specific message that names the problem, rather than silently matching nothing:

regex.compile("a\\1")      -- Error: backreferences ... are not supported
regex.compile("(?=x)")     -- Error: lookaround ... is not supported by the linear-time engine
regex.compile("(a")        -- Error: unbalanced parenthesis

This matches ripgrep's default engine, which also has no backreferences or lookaround.

#On a data-table column

The same re works inside a frame filter:

import regex

let tickets = frame({
  note = ["refund request", "shipped", "CHARGEBACK dispute"],
})
match regex.compile("(?i)refund|chargeback")
  Ok(re) -> print(tickets.filter(note.matches(re)))
  Error(why) -> print(why)
end

-- Or the multi-column shortcut:
print(tickets.search("ship", { columns = ["note"] }))

See Search a text column. Fixed-string column filters use note.contains("lit") (no regex needed).

#v1 limits

\w \s \d and \b use ASCII semantics for now (identical to Unicode on ASCII identifiers, which is what most code search needs); \p{L} and full Unicode character classes are a planned addition and currently fail to compile. Multiline matching (. across newlines) is not available — patterns are line-scoped. The match atom and every offset are still whole codepoints.