The text page gave you verbs for strings you can spell out.
This page is for the shapes you can only describe: a run of digits, a
word, a dash, a number. The regex unit answers those — with one promise
most engines cannot make and one refusal most engines will not: matching is
linear in the text for every pattern you can write, and the spellings
this engine class could never honor are refused at compile time instead of
silently meaning something else.
The example is examples/PatternMatching
in the tutorial repository.
Getting the file
make -C examples/PatternMatching runPatternMatching
every number in: order 66 of 512 items
66 at rune 6
512 at rune 12
Count says 2, Matches says 1
Replace order # of # items
ReplaceFirst order # of 512 items
groups of item-42: word item, number 42
letters of every script: 4 words in "Grüße Ελλάδα 東京 café"
the backreference spelling is refused: BadEscape
lookaround is refused by name: LookaroundThe example, walked
Compile once, ask many times
numbers := Compile("[0-9]+") on fail e do leave;A pattern is a small program, and Compile builds it once — the parse, the
checks, the instruction emission all happen here, so the matching verbs
never pay for them again. The compile is fallible, and the clause says so
where the pattern stands: a malformed pattern answers a RegexError by name
— BadEscape, BadClass, UnbalancedParen, BadQuantifier,
EmptyPattern, BadProperty, Lookaround — an expected, reasoned
rejection. The absence of a match,
by contrast, is never a failure: it is an ordinary Match whose Found is
False. Two kinds of “no”, kept apart, exactly as the
crossroads drew the line.
The syntax is the familiar core: literals, ., classes like [a-z] and
[^a] (with \d \w \s and \p{L} usable inside), grouping,
alternation |, the quantifiers * + ?, the anchors ^ $, and a
closed escape list. Closed is a feature: an escape outside the list is
BadEscape at compile time, never a guess at run time.
The sweep
m := Find(address numbers, text, 0);
while m.Found do
begin
WriteLn(" %ls at rune %lld", Copy(text, m.Start, m.Length), m.Start);
m := Find(address numbers, text, m.Start + m.Length);
end;Find answers a Match — Found, Start, Length, positions in runes
like every string measurement in Mica — and advancing from the previous
hit’s end walks every occurrence. The engine’s design promise holds
throughout: match time grows with the text, never with the pattern, so a
pattern that matches late or not at all costs the same linear walk, and the
catastrophic slowdowns other engines document as warnings cannot be
expressed here. The price of that class of engine is stated as plainly:
backreferences and lookaround are not in it, and the last lines of the
output show the backreference spelling refused rather than misread and the
lookaround refused by name — Lookaround — because a lookaround is exactly
what the linear-time guarantee cannot carry.
Rewriting and groups
WriteLn("Replace %ls", Replace(address numbers, text, "#"));Replace rewrites every hit, ReplaceFirst only the first, Count counts
without rewriting, Matches answers the one-bit question. And the
parentheses you write are answerable. A walk holds the match it found:
MatchStart opens one over a text, NextMatch steps it, and
Group(address walk, k) returns the k-th group’s own Match, which Copy
turns into text — the example splits item-42 into its word and its number.
The pattern itself is never written after Compile, which is what lets one
compiled pattern serve any number of walks at once.
Letters of every script
words := Compile("\\p{L}+") on fail e do leave;A class may name a Unicode general category: \p{L} is every letter of
every script, \p{N} every number, \p{Lu} the uppercase letters alone,
and \P{L} the complement — the one-letter families L M N P S Z C and
the two-letter categories the Unicode character database lists, inside a
bracket expression too, as [^\s\p{L}\p{N}]. The example counts the words
of a line written in three scripts with one pattern and gets four, because
a letter is a letter whatever its alphabet. A name the database does not
carry is BadProperty at compile time, like every other closed-list
refusal.
Try it
1. Refuse a pattern of your own. Change a pattern to "[a-" and watch
Compile answer BadClass through the same clause. Then try "" —
EmptyPattern. The reasons are values; a build tool can case over them.
2. Make the engine sweat — and fail to. Against a long text, try the
pattern (a+)+$ — the classic detonator elsewhere. Here it is just a
pattern: compile grows the instruction program a little, and matching stays
one linear sweep. The absence of drama is the feature.
What the unit promises
Compile once; match in time proportional to the text, every time; refuse at compile time what the engine class cannot honor; and keep no hidden state — a compiled pattern is a value no verb writes, and a walk over a text is a value you hold, so one pattern serves any number of walks in any number of tasks with no shared cell anywhere.
Next
Text: the value, the builder, and the window for the string verbs beside this unit, and UTF sources for why those positions are runes. Every example lives in the tutorial repository.