Skip to content

The q* stdlib docs

qregex: regular expression matching

qregex is a compact regular-expression engine written from scratch with zero third-party dependencies. The pipeline is a recursive-descent parser that builds an AST, a compiler that emits a flat bytecode program, and a backtracking VM that executes it. It exposes three QQL built-ins — regex.test, regex.find, and regex.replace — and supports a practical subset of regex syntax.

Public built-ins#

FunctionArgumentsReturnsBehavior
regex.testpattern, textBoolTrue if the pattern matches anywhere in the text.
regex.findpattern, textStrThe first matching substring, or "" if none.
regex.replacepattern, text, replacementStrEvery non-overlapping match replaced by the replacement string.

Arguments are read as strings (an Int argument is stringified). All three search for an unanchored match unless you use the ^ and $ anchors. If the pattern fails to compile, regex.test/find return no match (false / ""), and regex.replace returns the text unchanged (identity).

Supported syntax#

Literals and . (Any)
Ordinary characters match themselves; . matches any single character.
Character classes
[a-z], [^...] negated; ranges with -; class members may be escapes like \d.
Anchors
^ matches start of text, $ matches end of text.
Alternation
a|b chooses between alternatives.
Groups
(...) groups; (?:...) non-capturing prefix is accepted and ignored.
Quantifiers
* + ? are greedy; append ? for lazy (*?, +?, ??).
Counted repetition
{n}, {n,}, {n,m}; greedy or lazy with a trailing ?. An invalid brace is treated as a literal {.
Escapes
\d \w \s and their negations \D \W \S; whitespace escapes \n \t \r; any other escaped char is that literal character.

Class semantics: \d is ASCII digits, \w is alphanumeric or underscore (Unicode-aware alphanumeric), \s is whitespace; the uppercase forms are the negations. The whole pattern must parse to the end — trailing unparsed input such as a stray ) makes compilation fail.

How it runs#

pattern string
      |
  recursive-descent parser  ->  AST (Char, Any, Class, Alt, Star, Repeat, ...)
      |
  emit  ->  bytecode (Char, Any, Class, Bol, Eol, Jmp, Split, Match)
      |
  backtracking VM  ->  run from each start position until Match
Compile-and-match pipeline

The VM is a backtracking interpreter: Split tries its first branch recursively and falls back to the second, which is how greedy vs lazy quantifiers and alternation are encoded. search tries each start offset from 0 to the end of the text and returns the first match span. To guard against pathological (catastrophic-backtracking) patterns, a single run is capped at 5,000,000 steps; exceeding that returns no match.

regex.replace scans left to right: at each position it tries to match; on a non-empty match it appends the replacement and skips past the match, otherwise it copies one character and advances. This replaces all non-overlapping matches.

Resource budget#

qregex is the heaviest stdlib package: each call compiles the pattern to a program, materializes the text as a Vec<char>, runs a backtracking search, and (for replace) builds a fresh output string. Cost scales with pattern plus text size, so every regex.* function declares an 8192-byte (8 KB) memory budget and zero storage.

Examples#

regex.find returns the first run of digits in the text.

Request

regex.find("\\d+", "abc42def")

# wire form:
{ "0": { "Str": "\\d+" }, "1": { "Str": "abc42def" } }

Response

{ "result": { "Str": "42" } }

Output

42

Request

regex.test("^\\w+@\\w+\\.\\w+$", "a@b.com")
regex.replace("\\d+", "a1b22c333", "#")

Response

{ "result": { "Bool": true } }
{ "result": { "Str": "a#b#c#" } }

Output

true
a#b#c#