These libraries are experimental. APIs may change without notice. Generated from source with koruc 0.1.7 on 8/19/2026.
PCRE2
@korulang/pcre2@0.0.1PCRE2 (Perl-compatible regular expressions) for Koru with phantom obligation types
pcre2/index.kz · 7 tors
@korulang/pcre2 — the definitive Koru PCRE2 edition · 31 more lines
@korulang/pcre2 — the definitive Koru PCRE2 edition
Lifts PCRE2 (the Perl-Compatible Regular Expressions library that git,
PHP, nginx, and Apache all rely on) into a phantom-obligation-typed Koru
edition. The raw C API has two hand-managed heap resources — the compiled
pattern (`pcre2_code`) and the per-search match data (`pcre2_match_data`) —
and the classic footgun of iterating matches with a manual offset cursor.
This edition compiles those footguns away:
• The compiled pattern is a phantom obligation: the build FAILS if you
forget to free it, and fails if you use it after freeing it.
• The match-data buffer never touches your hands at all — the `find.all`
loop owns it, iterates every match for you, and frees it exactly once.
You cannot leak it because you never hold it.
• Each match is a per-iteration BORROW, valid only inside its `! match`
body — capture that borrow past the body and the phantom checker
rejects the build (KORU030), the same wall sqlite3's `! row` uses.
USAGE:
~import libs/pcre2
~libs/pcre2:compile(pattern: "(\\w+)@(\\w+)")
| ok re |>
libs/pcre2:find.all(re: re, subject: "a@b and c@d")
! match m |>
libs/pcre2:group.text(m: m, index: 1): user |>
libs/pcre2:group.text(m: m, index: 2): host |>
std/io:print.ln("{{ user:s }} at {{ host:s }}")
| done |> libs/pcre2:free(re)
| err e |> libs/pcre2:free(re)
| err e |> std/io:print.ln("bad pattern: {{ e.msg:s }}")
Phantom lifecycles
Derived from the phantom labels in the declarations below — state! issues an
obligation the compiler will chase, !state discharges it, a bare state holds it without moving it. Nothing here is hand-drawn.
Regex 1 state compiled!Match 1 state match!// Compile — mint the `compiled` obligation
//
// A pattern that PCRE2 cannot compile is a loud `err` (with the exact PCRE2
// diagnostic and the byte offset it choked on), never a silent null.
~pub tor compile { pattern: string }
| ok *Regex<compiled!>
| err { code: i32, offset: usize, msg: string }// Free — discharge the `compiled` obligation
~pub tor free { re: *Regex<!compiled> }// find.all — iterate every match; the match-data buffer never escapes
//
// `! match` fires once per match in `subject`, its body inlined here. The
// `*Match<match!>` handed to each firing is a per-iteration borrow the engine
// auto-releases at the body boundary (see `unmatch`). The match-data
// buffer is created once and freed exactly once by this proc — the caller
// never holds it and therefore cannot leak it (safe by construction, proven
// at compile time). The `compiled` obligation is BORROWED, not threaded —
// `find.all` only reads the pattern, never frees it, so the caller keeps the
// live obligation across the whole call and still owes exactly one `free`.
~pub tor find.all { re: *Regex<compiled>, subject: string }
! match *Match<match!>
| done
| err { code: i32, msg: string }// No-op consumer of the per-match borrow. The compiler finds it by SIGNATURE
// (it consumes `<!match>`) and auto-inserts a call at each match-body boundary;
// the user never calls it. Its existence is what lets the phantom checker prove
// the borrow is scoped — capture it past the match body and the build fails
// with KORU030.
//
// NAMING: this is deliberately a single non-dotted segment. sqlite3's exemplar
// spells the analogue `release.row`, but the auto-inserted call for a DOTTED
// release-event name is emitted as `<pkg>.release.row_event.handler` (dot kept)
// while the declaration registers flat as `release_row_event` — a codegen
// mismatch that fails to build. A single-segment name sidesteps it. See the
// README's toolchain-findings section and tests/TOOLCHAIN_REPRO_*.kz.
~pub tor unmatch { m: *Match<!match> }// Capture access — within a `! match` body (borrow reads, no obligation)
//
// Number of capture slots this match has (index 0 = the whole match, then one
// per `( )` group in the pattern).
~pub tor group.count { m: *Match<match> } -> i64// Text of capture group `index` (0 = whole match). Returns "" for a group that
// did not participate in the match (PCRE2_UNSET) or an out-of-range index —
// never reads out of bounds.
~pub tor group.text { m: *Match<match>, index: i32 } -> string// Byte offset where the whole match starts in the subject.
~pub tor match.start { m: *Match<match> } -> i64