4 Commits
Author SHA1 Message Date
Antonio PallaresandCursor 2c5f57a83c Share one arity check across the strict operators (#7571)
Seven operators hand-wrote the same count check and the same message.
Move it to Operators, next to evalArgs, whose result it always checks.

Messages are unchanged, so no fixture moves.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 13:09:59 +02:00
Antonio PallaresandCursor 863a4b1f26 Add rc.regexReplace custom operator (#7558)
* Add rc.regexReplace custom operator

Co-authored-by: Cursor <cursoragent@cursor.com>

* Place the replacement template divergence on the right axis

The note blamed the three regex engines, but the template is parsed by
each platform's own API and never reaches ICU, so this is the one part of
the feature where the two devices differ rather than agreeing against
Funnels. Records what each actually does with $& and with a $1 the
pattern cannot supply.

Co-authored-by: Cursor <cursoragent@cursor.com>

* State the replacement contract without surveying the engines

What a reader needs is that the replacement is taken literally; which
template syntax each engine would otherwise have honoured is background
that belongs with whoever authors the patterns.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Compose rc.regexReplace with an operator that still exists

The fixture proving a replaced string is usable downstream fed it to
rc.length, which main has since removed. rc.upper keeps the point of the
case without depending on an operator that is gone.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 13:09:59 +02:00
Antonio PallaresandCursor fd8441097a Add rc.regexExtract custom operator (#7557)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 13:09:58 +02:00
Antonio PallaresandCursor aa30cdcf1c Add rc.regexMatch custom operator (#7556)
* Add rc.regexMatch custom operator

Co-authored-by: Cursor <cursoragent@cursor.com>

* Report why a regex pattern failed to compile

The engine discarded the compile error and said only that the pattern was
bad, which leaves whoever authored the rule to find the mistake by eye.

NSRegularExpression has little to add today: every syntax failure is the
same NSCocoaErrorDomain 2048 whose description restates the pattern. The
catch is here so the message keeps its shape across the engines, where
the JVM names the fault and points at the offset.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Name the engines the regex operators actually run on

The divergence list said ICU here and java.util.regex on Android, which
is wrong: Android's java.util.regex has wrapped ICU4C since 2.3, so both
devices run ICU and Funnels is the outlier. Read the old note and you
would expect \d to behave differently on the two devices when it behaves
differently in Funnels instead.

Adds the rule the old list was missing — ICU rejects a bare literal }
that JS accepts — and the one genuine device difference, unknown escapes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Pin how the regex operators read an empty input

An empty input was untested, and it is the input a variable lookup is
most likely to produce by accident. All three engines agree: the string
is still searched, so a pattern that can match nothing succeeds on it
and one that needs a character does not.

The empty *pattern* is deliberately not fixtured. ICU refuses to compile
it while JS reads it as a match at every position, so it is a divergence
rather than a shared rule, and it is now recorded as one.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Delegate regex syntax to the platform's own documentation

The divergence list restated what each engine does, which the vendor
pages already document and do more reliably than a comment that has to be
kept true by hand. The syntax reference now lives behind a link, leaving
only what this file decides for itself.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Name only the pattern when a regex fails to compile

The Android branch hand-assembled the first line of PatternSyntaxException's
own message, and its iOS counterpart appended a localizedDescription that
only repeats the pattern back. Neither is asserted anywhere, and the useful
half is OpenJDK phrasing that a device's ICU-backed engine words differently.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Bind both regex operands before reading them

The arity guard proves the count matches what the caller asked for, not
that it reaches two, so a caller passing a smaller count would index past
the end. An out-of-bounds read traps, and a trap is the one failure the
engine cannot hand back as an error.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 13:09:58 +02:00