Softanza
StartFrançais
Softanza
Core data & language

Pattern matching

Regex at full PCRE2 power is the entry point; six pattern languages carry it to lists, numbers, time, tables, matrices and graphs. The lanes rate the text engine.

4 Strong · 5 Solid · 3 Partial · 0 Emerging

Measured against: PCRE2 · RE2 · Oniguruma · Folders: base/regex(opens in a new tab) · For programmers: this area's reference

a pattern, its matches and their places in the line
a pattern, its matches and their places in the line. rendered on 2026-10-01 at 04:00 by the site's render script tools/areas.ring, run inside the library at commit 0e72e2e2c · the source(opens in a new tab)

Rethought from first principles

What Softanza kept from what works, and what it rethought, as the library wrote it down in its design documents and narrations.

The Softanza wayPattern matching is one way of thinking for every kind of data. The regex is its entry point; Softanza carries the same idea to lists, numbers, time, tables, matrices and graphs, and lets you build a pattern in words or call it by its name.

Kept from the best practice

  • PCRE2 runs every match: proven, Unicode-aware, and written in the regex syntax programmers already know, with Unicode mode always switched on. source(opens in a new tab)
  • Simple matches are not reinvented: a classic pattern string, named groups and captures work as written; Softanza only adds clearer names around them. source(opens in a new tab)

Rethought

  • Listex, Matrex, Tablex and Graphex apply one pattern grammar to lists, matrices, tables and graphs, replacing the verbose conditional code structured data usually needs. source(opens in a new tab)
  • Numbrex and Timex describe a number's properties (prime, perfect, palindrome) or a timeline's events and durations, so you state what you mean, not how to test it. source(opens in a new tab)
  • stzRegexMaker builds a pattern from readable steps, with conditional (wrxm) and look-around (arxm) makers, because cryptic notation is error-prone and documents nothing. source(opens in a new tab)
  • Ready-made patterns are called by name — pat(:email), pat(:IPv6) — and can explain themselves with Explain(), because a raw pattern carries no sign of its intent. source(opens in a new tab)
  • Four scope verbs — Match, MatchLine, MatchWord, MatchSegment — replace the dotall, multiline and word-boundary flags, because a flag set elsewhere hides what the pattern means. source(opens in a new tab)
  • Match() asks whether the whole text fits the pattern; MatchFirst() asks whether it occurs anywhere — two different questions get two different verbs. source(opens in a new tab)

Under the hood it is PCRE2 10.47, statically linked, Unicode always on, with an unusually rich Haro layer (100+ methods, four fluent builder classes, a reactive match-to-code engine). On features it is at PCRE2 parity, with lookaround, backreferences, recursion and conditionals that RE2 and Rust refuse. Where it trails the modern engines is the safety-and-throughput story: no JIT, no linear-time matcher, and the ReDoS knob is not reachable from Haro, although the code for both is already compiled into the binary.

What a crafter does with it, and what stands out

  • Feature-complete PCRE2: lookaround, backrefs, recursion, conditionals that RE2 and Rust deliberately refuse
  • The clearest match-semantics API in its class: Match() anchors, MatchFirst() searches, partial as-you-type
  • Four fluent builder classes with no native peer
  • A reactive RegexUter match-to-code engine

What is owed

  • Wire the linear-time DFA matcher (pcre2_dfa_match is already in the binary)
  • Surface the ReDoS limits on the Haro stzRegex class
  • Named-group substitution ${name} in replace
  • Fix the broken Explain fallback (missing stzRegexAnalyzer) and the builder stubs

The Softanza way, run

What Softanza does differently in this area, shown by code taken from its narrations and guards, and run for this page.

A list is matched by its shape, the way a regex matches text: a number then a string, one to three numbers then a string, anything but a number.

Softanza
# a number, then a string
? Lx("[@N, @S]").Match([42, "hello"])
# one to three numbers, then a string
? Lx("[@N1-3, @S]").Match([1, 2, "end"])
# anything but a number
? Lx("[@!N]").Match(["string"])
? Lx("[@!N]").Match([42])
Output
1
1
1
0

run on 2026-10-15 13:37 inside the library at commit 4a184e6f0; taken from base/doc/narrations/stzlistex-pattern-matching-for-lists.md(opens in a new tab)

A ready-made pattern is called by name, matched against a whole value, and explains itself in plain words.

Softanza
rx(pat(:xlsConditionalExpression)) {
	? Match("B2<>C3")
	? Pattern() + NL
	? Explain() + NL
}
Output
1
^.*(?:=|<|>|<>).*$

Matches an Excel conditional expression

run on 2026-10-15 13:37 inside the library at commit 4a184e6f0; taken from base/test/regexmaker/02_match.ring(opens in a new tab)

A conditional pattern is declared like an if/else, international or local phone format, and the maker writes the regex.

Softanza
wrxm() {
	IfStartsWith("+")
	ThenMatch("\+1\d{10}")      # International format
	ElseMatch("\d{10}")         # Local format
	? Pattern()
}
Output
(?(?=^\+)\+1\d{10}|\d{10})

run on 2026-10-15 13:37 inside the library at commit 4a184e6f0; taken from base/test/regexmaker/34_validate_phone_numbers.ring(opens in a new tab)

A number is matched by what it is: 17 fits the pattern for primes, 18 does not.

Softanza
Nx = new stzNumbrex("{@Property(Prime)}")
? Nx.Match(17)
? Nx.Match(18)
Output
1
0

run on 2026-10-15 13:37 inside the library at commit 4a184e6f0; taken from base/test/numbrex/02_basic_property_matching.ring(opens in a new tab)

The articles of this area (10)

The lanes, one by one

Lanes and their notes are quoted as they were read at the source, on Read from the source on 2026-09-16.

Match, search and anchoring
Strong
Match() anchors whole content, MatchFirst() searches, MatchAt(s,n) from a position, plus partial as-you-type mode (PARTIAL_HARD/SOFT); nothing structural owedstzRegex Match / MatchFirst / MatchAt
Capture groups, numbered and named
Strong
Numbered and named captures with positions and section variants; Haro re-derives names in source order because PCRE2 enumerates them alphabetically
Unicode correctness
Strong
PCRE2 compiled with UTF | UCP unconditionally: p{L}, scripts, codepoint-correct w/d/dot over multibyte UTF-8, offset conversion at the boundaryregex.zig (UTF | UCP)
Advanced pattern features
Strong
Full PCRE2 set proven by engine tests: lookaround, backrefs, atomic, possessive, non-capturing, recursion (?R), conditionals; RE2 and Rust omit theseZig in-source tests (~57 blocks)
Replace and substitution
Solid
Engine-backed replace with $1 / ${1} / backref and $$ via StzRegexReplace and ReplaceRegex; ${name} parsed but only numeric honored; no Replace on stzRegexStzRegexReplace / ReplaceRegex
Split, find-all and iteration
Solid
Single-pass FindAllRegex / extract_all (O(n)) plus stzRegex Matches/MatchesZZ and SplitByRegex; split_get silently caps at 512 piecesFindAllRegex / extract_all / SplitByRegex
Flags and compiled-pattern reuse
Solid
Standard modifiers plus auto-multiline; compiles once, recompiles only on flag change; :DontCapture, :DisableOptimizations, :UseUnicode are no-ops; no cache
Fluent builder DSL
Solid
Four builders (stzRegexMaker, Recursive, Conditional W-DSL, Lookaround) emitting patterns; Quantifiers() is an empty TODO, MatchSameContentAs has a param bugstzRegexMaker
Reactive computing (RegexUter)
Solid
stzRegexuter: a trigger-pattern-code engine with @value, state tracking, dependency chains, per-position results; novel, built on eval()stzRegexUter
Named-pattern catalogue and explanation
Partial
4,842-line catalogue (stzRegexData) via rxp()/pat(); Explain() works only for named patterns, the fallback calls a nonexistent stzRegexAnalyzerbase/data/stzRegexData
Throughput: JIT and linear-time matcher
Partial
One backtracking pcre2_match path; pcre2_jit_compile and pcre2_dfa_match are compiled into the binary but never calledregex.zig (pcre2_match)
ReDoS safety and limits
Partial
stz_regex_set_limits(steps, input_len, depth) exists (engine test proves (a+)+$ aborts, ~10M-step default) but no Haro stzRegex method calls itstz_regex_set_limits engine test