Issue #1285 - Implement named capturing groups for replacing

This commit is contained in:
Martok 2022-12-21 18:53:27 +01:00 • committed by roytam1
commit 6eead2deb4
6 changed files with 279 additions and 113 deletions

View file

@ -1318,10 +1318,10 @@ GetParen(JSLinearString* matched, const JS::Value& capture, JSSubString* out)
template <typename CharT> template <typename CharT>
static bool static bool
InterpretDollar(JSLinearString* matched, JSLinearString* string, size_t position, size_t tailPos, InterpretDollar(JSLinearString* matched, JSLinearString* string, size_t position, size_t tailPos,
MutableHandle<CapturesVector> captures, JSLinearString* replacement, Handle<CapturesVector> captures, Handle<CapturesVector> namedCaptures, JSLinearString* replacement,
const CharT* replacementBegin, const CharT* currentDollar, const CharT* replacementBegin, const CharT* currentDollar,
const CharT* replacementEnd, const CharT* replacementEnd,
JSSubString* out, size_t* skip) JSSubString* out, size_t* skip, uint32_t* currentNamedCapture)
{ {
MOZ_ASSERT(*currentDollar == '$'); MOZ_ASSERT(*currentDollar == '$');
@ -1363,6 +1363,35 @@ InterpretDollar(JSLinearString* matched, JSLinearString* string, size_t position
return true; return true;
} }
// '$<': Named Captures
if (c == '<') {
// Step 1.
if (namedCaptures.length() == 0) {
return false;
}
// Step 2.b
const CharT* nameStart = currentDollar + 2;
const CharT* nameEnd = js_strchr_limit(nameStart, '>', replacementEnd);
// Step 2.c
if (!nameEnd) {
return false;
}
// Step 2.d
// We precompute named capture replacements in InitNamedCaptures.
// They are stored in the order in which we will need them, so here
// we can just take the next one in the list.
size_t nameLength = nameEnd - nameStart;
*skip = nameLength + 3; // $<...>
// Steps 2.d.iii-iv
GetParen(matched, namedCaptures[*currentNamedCapture], out);
*currentNamedCapture += 1;
return true;
}
*skip = 2; *skip = 2;
switch (c) { switch (c) {
default: default:
@ -1393,8 +1422,9 @@ InterpretDollar(JSLinearString* matched, JSLinearString* string, size_t position
template <typename CharT> template <typename CharT>
static bool static bool
FindReplaceLengthString(JSContext* cx, HandleLinearString matched, HandleLinearString string, FindReplaceLengthString(JSContext* cx, HandleLinearString matched, HandleLinearString string,
size_t position, size_t tailPos, MutableHandle<CapturesVector> captures, size_t position, size_t tailPos, Handle<CapturesVector> captures,
HandleLinearString replacement, size_t firstDollarIndex, size_t* sizep) Handle<CapturesVector> namedCaptures, HandleLinearString replacement,
size_t firstDollarIndex, size_t* sizep)
{ {
CheckedInt<uint32_t> replen = replacement->length(); CheckedInt<uint32_t> replen = replacement->length();
@ -1403,11 +1433,13 @@ FindReplaceLengthString(JSContext* cx, HandleLinearString matched, HandleLinearS
const CharT* replacementBegin = replacement->chars<CharT>(nogc); const CharT* replacementBegin = replacement->chars<CharT>(nogc);
const CharT* currentDollar = replacementBegin + firstDollarIndex; const CharT* currentDollar = replacementBegin + firstDollarIndex;
const CharT* replacementEnd = replacementBegin + replacement->length(); const CharT* replacementEnd = replacementBegin + replacement->length();
uint32_t currentNamedCapture = 0;
do { do {
JSSubString sub; JSSubString sub;
size_t skip; size_t skip;
if (InterpretDollar(matched, string, position, tailPos, captures, replacement, if (InterpretDollar(matched, string, position, tailPos, captures, namedCaptures,
replacementBegin, currentDollar, replacementEnd, &sub, &skip)) replacement, replacementBegin, currentDollar, replacementEnd,
&sub, &skip, &currentNamedCapture))
{ {
if (sub.length > skip) if (sub.length > skip)
replen += sub.length - skip; replen += sub.length - skip;
@ -1432,14 +1464,14 @@ FindReplaceLengthString(JSContext* cx, HandleLinearString matched, HandleLinearS
static bool static bool
FindReplaceLength(JSContext* cx, HandleLinearString matched, HandleLinearString string, FindReplaceLength(JSContext* cx, HandleLinearString matched, HandleLinearString string,
size_t position, size_t tailPos, MutableHandle<CapturesVector> captures, size_t position, size_t tailPos, Handle<CapturesVector> captures, Handle<CapturesVector> namedCaptures,
HandleLinearString replacement, size_t firstDollarIndex, size_t* sizep) HandleLinearString replacement, size_t firstDollarIndex, size_t* sizep)
{ {
return replacement->hasLatin1Chars() return replacement->hasLatin1Chars()
? FindReplaceLengthString<Latin1Char>(cx, matched, string, position, tailPos, captures, ? FindReplaceLengthString<Latin1Char>(cx, matched, string, position, tailPos, captures,
replacement, firstDollarIndex, sizep) namedCaptures, replacement, firstDollarIndex, sizep)
: FindReplaceLengthString<char16_t>(cx, matched, string, position, tailPos, captures, : FindReplaceLengthString<char16_t>(cx, matched, string, position, tailPos, captures,
replacement, firstDollarIndex, sizep); namedCaptures, replacement, firstDollarIndex, sizep);
} }
/* /*
@ -1450,7 +1482,7 @@ FindReplaceLength(JSContext* cx, HandleLinearString matched, HandleLinearString
template <typename CharT> template <typename CharT>
static void static void
DoReplace(HandleLinearString matched, HandleLinearString string, DoReplace(HandleLinearString matched, HandleLinearString string,
size_t position, size_t tailPos, MutableHandle<CapturesVector> captures, size_t position, size_t tailPos, Handle<CapturesVector> captures, Handle<CapturesVector> namedCaptures,
HandleLinearString replacement, size_t firstDollarIndex, StringBuffer &sb) HandleLinearString replacement, size_t firstDollarIndex, StringBuffer &sb)
{ {
JS::AutoCheckCannotGC nogc; JS::AutoCheckCannotGC nogc;
@ -1460,6 +1492,7 @@ DoReplace(HandleLinearString matched, HandleLinearString string,
MOZ_ASSERT(firstDollarIndex < replacement->length()); MOZ_ASSERT(firstDollarIndex < replacement->length());
const CharT* currentDollar = replacementBegin + firstDollarIndex; const CharT* currentDollar = replacementBegin + firstDollarIndex;
const CharT* replacementEnd = replacementBegin + replacement->length(); const CharT* replacementEnd = replacementBegin + replacement->length();
uint32_t currentNamedCapture = 0;
do { do {
/* Move one of the constant portions of the replacement value. */ /* Move one of the constant portions of the replacement value. */
size_t len = currentDollar - currentChar; size_t len = currentDollar - currentChar;
@ -1468,8 +1501,8 @@ DoReplace(HandleLinearString matched, HandleLinearString string,
JSSubString sub; JSSubString sub;
size_t skip; size_t skip;
if (InterpretDollar(matched, string, position, tailPos, captures, replacement, if (InterpretDollar(matched, string, position, tailPos, captures, namedCaptures, replacement,
replacementBegin, currentDollar, replacementEnd, &sub, &skip)) replacementBegin, currentDollar, replacementEnd, &sub, &skip, &currentNamedCapture))
{ {
sb.infallibleAppendSubstring(sub.base, sub.offset, sub.length); sb.infallibleAppendSubstring(sub.base, sub.offset, sub.length);
currentChar += skip; currentChar += skip;
@ -1483,9 +1516,117 @@ DoReplace(HandleLinearString matched, HandleLinearString string,
sb.infallibleAppend(currentChar, replacement->length() - (currentChar - replacementBegin)); sb.infallibleAppend(currentChar, replacement->length() - (currentChar - replacementBegin));
} }
/*
* This function finds the list of named captures of the form
* "$<name>" in a replacement string and converts them into jsids, for
* use in InitNamedReplacements.
*/
template <typename CharT>
static bool CollectNames(JSContext* cx, HandleLinearString replacement,
size_t firstDollarIndex,
MutableHandle<GCVector<jsid>> names) {
JS::AutoCheckCannotGC nogc;
MOZ_ASSERT(firstDollarIndex < replacement->length());
const CharT* replacementBegin = replacement->chars<CharT>(nogc);
const CharT* currentDollar = replacementBegin + firstDollarIndex;
const CharT* replacementEnd = replacementBegin + replacement->length();
// https://tc39.es/ecma262/#table-45, "$<" section
while (currentDollar && currentDollar + 1 < replacementEnd) {
if (currentDollar[1] == '<') {
// Step 2.b
const CharT* nameStart = currentDollar + 2;
const CharT* nameEnd = js_strchr_limit(nameStart, '>', replacementEnd);
// Step 2.c
if (!nameEnd) {
return true;
}
// Step 2.d.i
size_t nameLength = nameEnd - nameStart;
JSAtom* atom = AtomizeChars(cx, nameStart, nameLength);
if (!atom || !names.append(AtomToId(atom))) {
return false;
}
currentDollar = nameEnd + 1;
} else {
currentDollar += 2;
}
currentDollar = js_strchr_limit(currentDollar, '$', replacementEnd);
}
return true;
}
/*
* When replacing named captures, the spec requires us to perform
* `Get(match.groups, name)` for each "$<name>". These `Get`s can be
* script-visible; for example, RegExp can be extended with an `exec`
* method that wraps `groups` in a proxy. To make sure that we do the
* right thing, if a regexp has named captures, we find the named
* capture replacements before beginning the actual replacement.
* This guarantees that we will call GetProperty once and only once for
* each "$<name>" in the replacement string, in the correct order.
*
* This function precomputes the results of step 2 of the '$<' case
* here: https://tc39.es/proposal-regexp-named-groups/#table-45, so
* that when we need to access the nth named capture in InterpretDollar,
* we can just use the nth value stored in namedCaptures.
*/
static bool InitNamedCaptures(JSContext* cx, HandleLinearString replacement,
HandleObject groups, size_t firstDollarIndex,
MutableHandle<CapturesVector> namedCaptures) {
Rooted<GCVector<jsid>> names(cx, GCVector<jsid>(cx));
if (replacement->hasLatin1Chars()) {
if (!CollectNames<Latin1Char>(cx, replacement, firstDollarIndex, &names)) {
return false;
}
} else {
if (!CollectNames<char16_t>(cx, replacement, firstDollarIndex, &names)) {
return false;
}
}
// https://tc39.es/ecma262/#table-45, "$<" section
RootedId id(cx);
RootedValue capture(cx);
for (uint32_t i = 0; i < names.length(); i++) {
// Step 2.d.i
id = names[i];
// Step 2.d.ii
if (!GetProperty(cx, groups, groups, id, &capture)) {
return false;
}
// Step 2.d.iii
if (capture.isUndefined()) {
if (!namedCaptures.append(capture)) {
return false;
}
} else {
// Step 2.d.iv
JSString* str = ToString<CanGC>(cx, capture);
if (!str) {
return false;
}
JSLinearString* linear = str->ensureLinear(cx);
if (!linear) {
return false;
}
if (!namedCaptures.append(StringValue(linear))) {
return false;
}
}
}
return true;
}
static bool static bool
NeedTwoBytes(HandleLinearString string, HandleLinearString replacement, NeedTwoBytes(HandleLinearString string, HandleLinearString replacement, HandleLinearString matched,
HandleLinearString matched, Handle<CapturesVector> captures) Handle<CapturesVector> captures, Handle<CapturesVector> namedCaptures)
{ {
if (string->hasTwoByteChars()) if (string->hasTwoByteChars())
return true; return true;
@ -1502,19 +1643,38 @@ NeedTwoBytes(HandleLinearString string, HandleLinearString replacement,
return true; return true;
} }
for (size_t i = 0, len = namedCaptures.length(); i < len; i++) {
Value capture = namedCaptures[i];
if (capture.isUndefined())
continue;
if (capture.toString()->hasTwoByteChars())
return true;
}
return false; return false;
} }
/* ES 2016 draft Mar 25, 2016 21.1.3.14.1. */ /* ES 2016 draft Mar 25, 2016 21.1.3.14.1. */
bool bool
js::RegExpGetSubstitution(JSContext* cx, HandleLinearString matched, HandleLinearString string, js::RegExpGetSubstitution(JSContext* cx, HandleArrayObject matchResult, HandleLinearString string,
size_t position, HandleObject capturesObj, HandleLinearString replacement, size_t position, HandleLinearString replacement, size_t firstDollarIndex,
size_t firstDollarIndex, MutableHandleValue rval) HandleValue groups, MutableHandleValue rval)
{ {
MOZ_ASSERT(firstDollarIndex < replacement->length()); MOZ_ASSERT(firstDollarIndex < replacement->length());
// Step 1 (skipped). // Step 1 (skipped).
// Step 10 (reordered).
uint32_t matchResultLength = matchResult->length();
MOZ_ASSERT(matchResultLength > 0);
MOZ_ASSERT(matchResultLength == matchResult->getDenseInitializedLength());
const Value& matchedValue = matchResult->getDenseElement(0);
RootedLinearString matched(cx, matchedValue.toString()->ensureLinear(cx));
if (!matched)
return false;
// Step 2. // Step 2.
size_t matchLength = matched->length(); size_t matchLength = matched->length();
@ -1523,33 +1683,36 @@ js::RegExpGetSubstitution(JSContext* cx, HandleLinearString matched, HandleLinea
// Step 6. // Step 6.
MOZ_ASSERT(position <= string->length()); MOZ_ASSERT(position <= string->length());
// Step 10 (reordered). uint32_t nCaptures = matchResultLength - 1;
uint32_t nCaptures;
if (!GetLengthProperty(cx, capturesObj, &nCaptures))
return false;
Rooted<CapturesVector> captures(cx, CapturesVector(cx)); Rooted<CapturesVector> captures(cx, CapturesVector(cx));
if (!captures.reserve(nCaptures)) if (!captures.reserve(nCaptures))
return false; return false;
// Step 7. // Step 7.
RootedValue capture(cx); for (uint32_t i = 1; i <= nCaptures; i++) {
for (uint32_t i = 0; i < nCaptures; i++) { const Value& capture = matchResult->getDenseElement(i);
if (!GetElement(cx, capturesObj, capturesObj, i, &capture))
return false;
if (capture.isUndefined()) { if (capture.isUndefined()) {
captures.infallibleAppend(capture); captures.infallibleAppend(capture);
continue; continue;
} }
MOZ_ASSERT(capture.isString()); JSLinearString* captureLinear = capture.toString()->ensureLinear(cx);
RootedLinearString captureLinear(cx, capture.toString()->ensureLinear(cx));
if (!captureLinear) if (!captureLinear)
return false; return false;
captures.infallibleAppend(StringValue(captureLinear)); captures.infallibleAppend(StringValue(captureLinear));
} }
Rooted<CapturesVector> namedCaptures(cx, CapturesVector(cx));
if (groups.isObject()) {
RootedObject groupsObj(cx, &groups.toObject());
if (!InitNamedCaptures(cx, replacement, groupsObj, firstDollarIndex, &namedCaptures)) {
return false;
}
} else {
MOZ_ASSERT(groups.isUndefined());
}
// Step 8 (skipped). // Step 8 (skipped).
// Step 9. // Step 9.
@ -1564,14 +1727,14 @@ js::RegExpGetSubstitution(JSContext* cx, HandleLinearString matched, HandleLinea
// Step 11. // Step 11.
size_t reserveLength; size_t reserveLength;
if (!FindReplaceLength(cx, matched, string, position, tailPos, &captures, replacement, if (!FindReplaceLength(cx, matched, string, position, tailPos, captures, namedCaptures,
firstDollarIndex, &reserveLength)) replacement, firstDollarIndex, &reserveLength))
{ {
return false; return false;
} }
StringBuffer result(cx); StringBuffer result(cx);
if (NeedTwoBytes(string, replacement, matched, captures)) { if (NeedTwoBytes(string, replacement, matched, captures, namedCaptures)) {
if (!result.ensureTwoByteChars()) if (!result.ensureTwoByteChars())
return false; return false;
} }
@ -1580,10 +1743,10 @@ js::RegExpGetSubstitution(JSContext* cx, HandleLinearString matched, HandleLinea
return false; return false;
if (replacement->hasLatin1Chars()) { if (replacement->hasLatin1Chars()) {
DoReplace<Latin1Char>(matched, string, position, tailPos, &captures, DoReplace<Latin1Char>(matched, string, position, tailPos, captures, namedCaptures,
replacement, firstDollarIndex, result); replacement, firstDollarIndex, result);
} else { } else {
DoReplace<char16_t>(matched, string, position, tailPos, &captures, DoReplace<char16_t>(matched, string, position, tailPos, captures, namedCaptures,
replacement, firstDollarIndex, result); replacement, firstDollarIndex, result);
} }

View file

@ -122,9 +122,9 @@ extern MOZ_MUST_USE bool
RegExpInstanceOptimizableRaw(JSContext* cx, JSObject* obj, JSObject* proto); RegExpInstanceOptimizableRaw(JSContext* cx, JSObject* obj, JSObject* proto);
extern MOZ_MUST_USE bool extern MOZ_MUST_USE bool
RegExpGetSubstitution(JSContext* cx, HandleLinearString matched, HandleLinearString string, RegExpGetSubstitution(JSContext* cx, HandleArrayObject matchResult, HandleLinearString string,
size_t position, HandleObject capturesObj, HandleLinearString replacement, size_t position, HandleLinearString replacement, size_t firstDollarIndex,
size_t firstDollarIndex, MutableHandleValue rval); HandleValue namedCaptures, MutableHandleValue rval);
extern MOZ_MUST_USE bool extern MOZ_MUST_USE bool
GetFirstDollarIndex(JSContext* cx, unsigned argc, Value* vp); GetFirstDollarIndex(JSContext* cx, unsigned argc, Value* vp);

View file

@ -395,9 +395,8 @@ function RegExpReplaceSlowPath(rx, S, lengthS, replaceValue,
var n, capN, replacement; var n, capN, replacement;
if (functionalReplace || firstDollarIndex !== -1) { if (functionalReplace || firstDollarIndex !== -1) {
// Steps 14.g-j. // Steps 14.g-k.
replacement = RegExpGetComplexReplacement(result, matched, S, position, replacement = RegExpGetComplexReplacement(result, matched, S, position,
nCaptures, replaceValue, nCaptures, replaceValue,
functionalReplace, firstDollarIndex); functionalReplace, firstDollarIndex);
} else { } else {
@ -411,16 +410,21 @@ function RegExpReplaceSlowPath(rx, S, lengthS, replaceValue,
if (capN !== undefined) if (capN !== undefined)
ToString(capN); ToString(capN);
} }
// Step 14.j, 14.l., GetSubstitution Step 11.
// We don't need namedCaptures, but ToObject is visible to script.
var namedCaptures = result.groups;
if (namedCaptures !== undefined)
ToObject(namedCaptures);
replacement = replaceValue; replacement = replaceValue;
} }
// Step 14.l. // Step 14.m.
if (position >= nextSourcePosition) { if (position >= nextSourcePosition) {
// Step 14.l.ii. // Step 14.m.ii.
accumulatedResult += Substring(S, nextSourcePosition, accumulatedResult += Substring(S, nextSourcePosition,
position - nextSourcePosition) + replacement; position - nextSourcePosition) + replacement;
// Step 14.l.iii. // Step 14.m.iii.
nextSourcePosition = position + matchLength; nextSourcePosition = position + matchLength;
} }
} }
@ -433,15 +437,14 @@ function RegExpReplaceSlowPath(rx, S, lengthS, replaceValue,
return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition); return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
} }
// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8 // ES 2021 draft 21.2.5.10
// steps 14.g-k. // https://tc39.es/ecma262/#sec-regexp.prototype-@@replace
// steps 14.g-l.
// Calculates functional/substitution replaceement from match result. // Calculates functional/substitution replaceement from match result.
// Used in the following functions: // Used in the following functions:
// * RegExpGlobalReplaceOptFunc // * RegExpGlobalReplaceOptFunc
// * RegExpGlobalReplaceOptElemBase // * RegExpGlobalReplaceOptElemBase
// * RegExpGlobalReplaceOptSubst
// * RegExpLocalReplaceOptFunc // * RegExpLocalReplaceOptFunc
// * RegExpLocalReplaceOptSubst
// * RegExpReplaceSlowPath // * RegExpReplaceSlowPath
function RegExpGetComplexReplacement(result, matched, S, position, function RegExpGetComplexReplacement(result, matched, S, position,
nCaptures, replaceValue, nCaptures, replaceValue,
@ -451,13 +454,8 @@ function RegExpGetComplexReplacement(result, matched, S, position,
var captures = []; var captures = [];
var capturesLength = 0; var capturesLength = 0;
// Step 14.j.i (reordered). // Step 14.k.i (reordered).
// For `nCaptures` <= 4 case, call `replaceValue` directly, otherwise _DefineDataProperty(captures, capturesLength++, matched);
// use `std_Function_apply` with all arguments stored in `captures`.
// In latter case, store `matched` as the first element here, to
// avoid unshift later.
if (functionalReplace && nCaptures > 4)
_DefineDataProperty(captures, capturesLength++, matched);
// Step 14.g, 14.i, 14.i.iv. // Step 14.g, 14.i, 14.i.iv.
for (var n = 1; n <= nCaptures; n++) { for (var n = 1; n <= nCaptures; n++) {
@ -473,29 +471,41 @@ function RegExpGetComplexReplacement(result, matched, S, position,
} }
// Step 14.j. // Step 14.j.
var namedCaptures = result.groups;
// Step 14.k.
if (functionalReplace) { if (functionalReplace) {
switch (nCaptures) { // For `nCaptures` <= 4 case, call `replaceValue` directly, otherwise
case 0: // use `std_Function_apply` with all arguments stored in `captures`.
return ToString(replaceValue(matched, position, S)); if (namedCaptures === undefined) {
case 1: switch (nCaptures) {
return ToString(replaceValue(matched, SPREAD(captures, 1), position, S)); case 0:
case 2: return ToString(replaceValue(SPREAD(captures, 1), position, S));
return ToString(replaceValue(matched, SPREAD(captures, 2), position, S)); case 1:
case 3: return ToString(replaceValue(SPREAD(captures, 2), position, S));
return ToString(replaceValue(matched, SPREAD(captures, 3), position, S)); case 2:
case 4: return ToString(replaceValue(SPREAD(captures, 3), position, S));
return ToString(replaceValue(matched, SPREAD(captures, 4), position, S)); case 3:
default: return ToString(replaceValue(SPREAD(captures, 4), position, S));
// Steps 14.j.ii-v. case 4:
_DefineDataProperty(captures, capturesLength++, position); return ToString(replaceValue(SPREAD(captures, 5), position, S));
_DefineDataProperty(captures, capturesLength++, S); }
return ToString(callFunction(std_Function_apply, replaceValue, null, captures));
} }
// Steps 14.k.ii-v.
_DefineDataProperty(captures, capturesLength++, position);
_DefineDataProperty(captures, capturesLength++, S);
if (namedCaptures !== undefined) {
_DefineDataProperty(captures, capturesLength++, namedCaptures);
}
return ToString(callFunction(std_Function_apply, replaceValue, undefined, captures));
} }
// Steps 14.k.i. // Step 14.l.
return RegExpGetSubstitution(matched, S, position, captures, replaceValue, if (namedCaptures !== undefined) {
firstDollarIndex); namedCaptures = ToObject(namedCaptures);
}
return RegExpGetSubstitution(captures, S, position, replaceValue, firstDollarIndex,
namedCaptures);
} }
// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8 // ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8

View file

@ -53,7 +53,7 @@ function FUNC_NAME(rx, S, lengthS, replaceValue, fullUnicode
break; break;
var nCaptures; var nCaptures;
#if defined(FUNCTIONAL) || defined(SUBSTITUTION) #if defined(FUNCTIONAL)
// Steps 14.a-b. // Steps 14.a-b.
nCaptures = std_Math_max(result.length - 1, 0); nCaptures = std_Math_max(result.length - 1, 0);
#endif #endif
@ -68,18 +68,19 @@ function FUNC_NAME(rx, S, lengthS, replaceValue, fullUnicode
var position = result.index; var position = result.index;
lastIndex = position + matchLength; lastIndex = position + matchLength;
// Steps g-j. // Steps g-l.
var replacement; var replacement;
#if defined(FUNCTIONAL) #if defined(FUNCTIONAL)
replacement = RegExpGetComplexReplacement(result, matched, S, position, replacement = RegExpGetComplexReplacement(result, matched, S, position,
nCaptures, replaceValue, nCaptures, replaceValue,
true, -1); true, -1);
#elif defined(SUBSTITUTION) #elif defined(SUBSTITUTION) // Step l.i
replacement = RegExpGetComplexReplacement(result, matched, S, position, var namedCaptures = result.groups;
if (namedCaptures !== undefined) {
nCaptures, replaceValue, namedCaptures = ToObject(namedCaptures);
false, firstDollarIndex); }
// Step l.ii
replacement = RegExpGetSubstitution(result, S, position, replaceValue, firstDollarIndex, namedCaptures);
#elif defined(ELEMBASE) #elif defined(ELEMBASE)
if (IsObject(elemBase)) { if (IsObject(elemBase)) {
var prop = GetStringDataProperty(elemBase, matched); var prop = GetStringDataProperty(elemBase, matched);
@ -96,7 +97,6 @@ function FUNC_NAME(rx, S, lengthS, replaceValue, fullUnicode
nCaptures = std_Math_max(result.length - 1, 0); nCaptures = std_Math_max(result.length - 1, 0);
replacement = RegExpGetComplexReplacement(result, matched, S, position, replacement = RegExpGetComplexReplacement(result, matched, S, position,
nCaptures, replaceValue, nCaptures, replaceValue,
true, -1); true, -1);
} }
@ -104,11 +104,11 @@ function FUNC_NAME(rx, S, lengthS, replaceValue, fullUnicode
replacement = replaceValue; replacement = replaceValue;
#endif #endif
// Step 14.l.ii. // Step 14.m.ii.
accumulatedResult += Substring(S, nextSourcePosition, accumulatedResult += Substring(S, nextSourcePosition,
position - nextSourcePosition) + replacement; position - nextSourcePosition) + replacement;
// Step 14.l.iii. // Step 14.m.iii.
nextSourcePosition = lastIndex; nextSourcePosition = lastIndex;
// Step 11.c.iii.2. // Step 11.c.iii.2.
@ -116,6 +116,7 @@ function FUNC_NAME(rx, S, lengthS, replaceValue, fullUnicode
lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1; lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1;
if (lastIndex > lengthS) if (lastIndex > lengthS)
break; break;
lastIndex |= 0;
} }
} }

View file

@ -60,9 +60,9 @@ function FUNC_NAME(rx, S, lengthS, replaceValue
return S; return S;
} }
// Steps 11.c, 12-13, 14.a-b (skipped). // Steps 11.c, 12-13 (skipped).
#if defined(FUNCTIONAL) || defined(SUBSTITUTION) #if defined(FUNCTIONAL)
// Steps 14.a-b. // Steps 14.a-b.
var nCaptures = std_Math_max(result.length - 1, 0); var nCaptures = std_Math_max(result.length - 1, 0);
#endif #endif
@ -88,19 +88,21 @@ function FUNC_NAME(rx, S, lengthS, replaceValue
// Steps g-j. // Steps g-j.
#if defined(FUNCTIONAL) #if defined(FUNCTIONAL)
replacement = RegExpGetComplexReplacement(result, matched, S, position, replacement = RegExpGetComplexReplacement(result, matched, S, position,
nCaptures, replaceValue, nCaptures, replaceValue,
true, -1); true, -1);
#elif defined(SUBSTITUTION) #elif defined(SUBSTITUTION)
replacement = RegExpGetComplexReplacement(result, matched, S, position, // Step l.i
var namedCaptures = result.groups;
nCaptures, replaceValue, if (namedCaptures !== undefined) {
false, firstDollarIndex); namedCaptures = ToObject(namedCaptures);
}
// Step l.ii
replacement = RegExpGetSubstitution(result, S, position, replaceValue, firstDollarIndex, namedCaptures);
#else #else
replacement = replaceValue; replacement = replaceValue;
#endif #endif
// Step 14.l.ii. // Step 14.m.ii.
var accumulatedResult = Substring(S, 0, position) + replacement; var accumulatedResult = Substring(S, 0, position) + replacement;
// Step 15. // Step 15.

View file

@ -1697,39 +1697,29 @@ static bool
intrinsic_RegExpGetSubstitution(JSContext* cx, unsigned argc, Value* vp) intrinsic_RegExpGetSubstitution(JSContext* cx, unsigned argc, Value* vp)
{ {
CallArgs args = CallArgsFromVp(argc, vp); CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 6); MOZ_ASSERT(args.length() == 6);
RootedString matched(cx, args[0].toString()); RootedArrayObject matchResult(cx, &args[0].toObject().as<ArrayObject>());
RootedString string(cx, args[1].toString());
RootedLinearString string(cx, args[1].toString()->ensureLinear(cx));
if (!string)
return false;
int32_t position = int32_t(args[2].toNumber()); int32_t position = int32_t(args[2].toNumber());
MOZ_ASSERT(position >= 0); MOZ_ASSERT(position >= 0);
RootedObject captures(cx, &args[3].toObject()); RootedLinearString replacement(cx, args[3].toString()->ensureLinear(cx));
#ifdef DEBUG if (!replacement)
bool isArray = false; return false;
MOZ_ALWAYS_TRUE(IsArray(cx, captures, &isArray));
MOZ_ASSERT(isArray);
#endif
RootedString replacement(cx, args[4].toString()); int32_t firstDollarIndex = int32_t(args[4].toNumber());
int32_t firstDollarIndex = int32_t(args[5].toNumber());
MOZ_ASSERT(firstDollarIndex >= 0); MOZ_ASSERT(firstDollarIndex >= 0);
RootedLinearString matchedLinear(cx, matched->ensureLinear(cx)); RootedValue namedCaptures(cx, args[5]);
if (!matchedLinear) MOZ_ASSERT(namedCaptures.isUndefined() || namedCaptures.isObject());
return false;
RootedLinearString stringLinear(cx, string->ensureLinear(cx));
if (!stringLinear)
return false;
RootedLinearString replacementLinear(cx, replacement->ensureLinear(cx));
if (!replacementLinear)
return false;
return RegExpGetSubstitution(cx, matchedLinear, stringLinear, size_t(position), captures, return RegExpGetSubstitution(cx, matchResult, string, size_t(position), replacement,
replacementLinear, size_t(firstDollarIndex), args.rval()); size_t(firstDollarIndex), namedCaptures, args.rval());
} }
static bool static bool