Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Stdlib

Regex

bool Regex.IsMatch(input, pattern) · string Regex.Replace(input, pattern, replacement) · List<string> Regex.Split(input, pattern) · Match Regex.Match(input, pattern) · Match[] Regex.Matches(input, pattern)

Match, replace, split and CAPTURE with regular expressions — the C#-faithful System.Text.RegularExpressions spelling. `Regex.IsMatch(s, "\\d+")` tests a pattern, `Regex.Replace` rewrites every match, `Regex.Split` breaks a string on a pattern, and `Regex.Match`/`Regex.Matches` return match objects whose groups are read by number or by name. Pure — no capability needed. Every call runs under a platform match-timeout.

stable4 examples compiled by CIregextextbcl

Summary#

Regex is the regular-expression surface, spelled exactly as in C# (System.Text.RegularExpressions):

var ok    = Regex.IsMatch("order-1042", "\\d+");          // true — the pattern matches somewhere
var clean = Regex.Replace("a1b2c", "\\d", "-");           // "a-b-c" — every match rewritten
var parts = Regex.Split("a, b ,c", "\\s*,\\s*");          // ["a", "b", "c"]
var m     = Regex.Match("ERROR:42", "(?<level>\\w+):(\\d+)");
//          m.Success · m.Value · m.Index · m.Groups["level"] · m.Groups[2]

It is pure — no capability, no using required (though a pasted using System.Text.RegularExpressions; is accepted and does nothing). Patterns are standard .NET regex syntax.

Signature#

bool         Regex.IsMatch(string input, string pattern)                       // matches anywhere?
string       Regex.Replace(string input, string pattern, string replacement)   // rewrite every match
List<string> Regex.Split(string input, string pattern)                         // split on the pattern
Match        Regex.Match(string input, string pattern)                         // the FIRST match
Match[]      Regex.Matches(string input, string pattern)                       // every match

Description#

IsMatch returns true when pattern matches anywhere in input. Anchor with ^$ for a whole-string match.

Replace returns input with every match rewritten to replacement. The replacement supports numbered group backreferences — $1, $2, … — exactly as in C#:

Regex.Replace("2026-07-12", "(\\d+)-(\\d+)-(\\d+)", "$1/$2/$3")   // "2026/07/12"

Split returns the substrings between matches as a List<string> you can foreach, index, and read .Count on.

Match timeout (host protection). Every Regex call runs under a fixed platform match-timeout. A pattern with catastrophic backtracking (a nested quantifier like (a+)+$ on a long non-matching input) can otherwise spin a CPU effectively forever; past the cap the call is aborted rather than allowed to hang. You cannot raise the cap — write a cheaper pattern if you hit it. This is the only safety limit on the surface; normal patterns never come near it.

Capturing groups — Match and Matches#

Match returns the FIRST match; Matches returns every one, left to right and non-overlapping.

Match always returns an object. An unsuccessful search answers a Match with Success = false rather than null — so if (m.Success) is the question you ask, and there is nothing to null-check first.

var m = Regex.Match(line, "(?<level>\\w+):(\\d+)");
m.Success        // did it match?
m.Value          // the whole matched text
m.Index          // where it starts, in characters
m.Length         // how long it is
m.Groups.Count   // how many groups, counting the whole match at 0

A group is read by number or by name, and a group that did not participate answers Success = false with an empty Value — never a fault, and never a missing entry:

m.Groups[0]          // the WHOLE match — group numbering starts at 0, exactly as in C#
m.Groups[2].Value    // a numbered group
m.Groups["level"]    // a named group, from `(?<level>…)`
m.Groups[99]         // Success = false — out of range is a group that did not participate
m.Groups["nope"]     // Success = false — so an OPTIONAL capture reads without a prior check

Named groups are numbered AFTER unnamed ones, which is .NET's rule and not source order. In (?<level>\\w+):(\\d+) the level group is number 2 and (\\d+) is number 1. Read a named group by its name and the question does not arise.

Match and Matches do not push down into a query. Postgres' regex dialect is POSIX rather than .NET's, and regexp_matches returns a set of arrays rather than a scalar — so a pushed-down match would be a different function wearing the same name, and the compiler refuses it where you wrote it. Narrow the query with Regex.IsMatch, which DOES push down, and capture from the rows it returns.

They run on the SERVER only. IsMatch, Replace and Split also run in the browser; Match/Matches do not yet, and a render expression that calls one is a compile error naming the component. Call them in a function or an action and render what they produce.

Still not available. The compiled-instance form (new Regex(pattern)) — the three statics plus the two match members cover validation, rewriting, tokenizing and extraction.

Examples#

Validate an email field:

bool LooksLikeEmail(string s) {
  return Regex.IsMatch(s, "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$");
}

Pull the parts out of a log line — by name, so the group NUMBERS never have to be counted:

string Level(string line) {
  var m = Regex.Match(line, "(?<level>[A-Za-z]+):(?<code>[0-9]+)");
  return m.Success ? m.Groups["level"].Value : "";
}

/// Every code in a line, in order — `Matches` is an ordinary array, so `foreach` reads it.
string[] Codes(string line) {
  var out = new List<string>();
  foreach (var m in Regex.Matches(line, "(?<level>[A-Za-z]+):(?<code>[0-9]+)")) {
    out.Add(m.Groups["code"].Value);
  }
  return out.ToArray();
}

Normalise whitespace and strip punctuation:

string Slugify(string title) {
  var lower = Text.Lower(Text.Trim(title));
  var spaced = Regex.Replace(lower, "[^a-z0-9]+", "-");   // non-alphanumerics → a single dash
  return spaced;
}

Tokenise a delimited line, tolerating irregular spacing:

List<string> Fields(string csvLine) {
  return Regex.Split(csvLine, "\\s*,\\s*");               // "a, b ,c" → ["a", "b", "c"]
}

See also#

  • <span class="planned" title="this page is planned and not written yet">stdlib-text</span> — the plain-string operations (Upper, Contains, Replace, Split) for non-pattern work
  • JsonSerializer — the other pure BCL surface for parsing structured text

Related

JsonSerializer

Turn a value into a JSON string and a JSON string into a typed object — the C#-faithful System.Text.Json spelling…