Skip to content

Commit

Permalink
Bug 1362154: Part 7: Named capture replacements in self-hosted code r…
Browse files Browse the repository at this point in the history
…=arai

This patch implements the changes to RegExp.prototype[@@replace] to support replacing named captures.

See: https://tc39.es/proposal-regexp-named-groups/#sec-regexp.prototype-@@replace

(I'm not completely sure what to do with out-of-date step numbers. I updated them in code that I modified, and left them alone elsewhere.)

Differential Revision: https://phabricator.services.mozilla.com/D76039
  • Loading branch information
iainireland committed May 21, 2020
1 parent 6976094 commit 7b5df5e
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 49 deletions.
111 changes: 70 additions & 41 deletions js/src/builtin/RegExp.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ function RegExpReplaceSlowPath(rx, S, lengthS, replaceValue,

var n, capN, replacement;
if (functionalReplace || firstDollarIndex !== -1) {
// Steps 14.g-j.
// Steps 14.g-k.
replacement = RegExpGetComplexReplacement(result, matched, S, position,
nCaptures, replaceValue,
functionalReplace, firstDollarIndex);
Expand All @@ -414,16 +414,22 @@ function RegExpReplaceSlowPath(rx, S, lengthS, replaceValue,
if (capN !== undefined)
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;
}

// Step 14.l.
// Step 14.m.
if (position >= nextSourcePosition) {
// Step 14.l.ii.
// Step 14.m.ii.
accumulatedResult += Substring(S, nextSourcePosition,
position - nextSourcePosition) + replacement;

// Step 14.l.iii.
// Step 14.m.iii.
nextSourcePosition = position + matchLength;
}
}
Expand All @@ -436,8 +442,9 @@ function RegExpReplaceSlowPath(rx, S, lengthS, replaceValue,
return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
}

// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
// steps 14.g-k.
// ES 2021 draft 21.2.5.10
// https://tc39.es/ecma262/#sec-regexp.prototype-@@replace
// steps 14.g-l.
// Calculates functional/substitution replacement from match result.
// Used in the following functions:
// * RegExpReplaceSlowPath
Expand All @@ -449,7 +456,7 @@ function RegExpGetComplexReplacement(result, matched, S, position,
var captures = [];
var capturesLength = 0;

// Step 14.j.i (reordered).
// Step 14.k.i (reordered).
_DefineDataProperty(captures, capturesLength++, matched);

// Step 14.g, 14.i, 14.i.iv.
Expand All @@ -466,34 +473,46 @@ function RegExpGetComplexReplacement(result, matched, S, position,
}

// Step 14.j.
var namedCaptures = result.groups;

// Step 14.k.
if (functionalReplace) {
// For `nCaptures` <= 4 case, call `replaceValue` directly, otherwise
// use `std_Function_apply` with all arguments stored in `captures`.
switch (nCaptures) {
case 0:
return ToString(replaceValue(SPREAD(captures, 1), position, S));
case 1:
return ToString(replaceValue(SPREAD(captures, 2), position, S));
case 2:
return ToString(replaceValue(SPREAD(captures, 3), position, S));
case 3:
return ToString(replaceValue(SPREAD(captures, 4), position, S));
case 4:
return ToString(replaceValue(SPREAD(captures, 5), position, S));
default:
// Steps 14.j.ii-v.
_DefineDataProperty(captures, capturesLength++, position);
_DefineDataProperty(captures, capturesLength++, S);
return ToString(callFunction(std_Function_apply, replaceValue, undefined, captures));
if (namedCaptures === undefined) {
switch (nCaptures) {
case 0:
return ToString(replaceValue(SPREAD(captures, 1), position, S));
case 1:
return ToString(replaceValue(SPREAD(captures, 2), position, S));
case 2:
return ToString(replaceValue(SPREAD(captures, 3), position, S));
case 3:
return ToString(replaceValue(SPREAD(captures, 4), position, S));
case 4:
return ToString(replaceValue(SPREAD(captures, 5), position, S));
}
}
// 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.
return RegExpGetSubstitution(captures, S, position, replaceValue, firstDollarIndex);
// Step 14.l.
if (namedCaptures !== undefined) {
namedCaptures = ToObject(namedCaptures);
}
return RegExpGetSubstitution(captures, S, position, replaceValue, firstDollarIndex,
namedCaptures);
}

// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
// steps 14.g-j.
// ES 2021 draft 21.2.5.10
// https://tc39.es/ecma262/#sec-regexp.prototype-@@replace
// steps 14.g-k.
// Calculates functional replacement from match result.
// Used in the following functions:
// * RegExpGlobalReplaceOptFunc
Expand All @@ -505,32 +524,42 @@ function RegExpGetFunctionalReplacement(result, S, position, replaceValue) {
assert(result.length >= 1, "RegExpMatcher doesn't return an empty array");
var nCaptures = result.length - 1;

switch (nCaptures) {
case 0:
return ToString(replaceValue(SPREAD(result, 1), position, S));
case 1:
return ToString(replaceValue(SPREAD(result, 2), position, S));
case 2:
return ToString(replaceValue(SPREAD(result, 3), position, S));
case 3:
return ToString(replaceValue(SPREAD(result, 4), position, S));
case 4:
return ToString(replaceValue(SPREAD(result, 5), position, S));
// Step 14.j (reordered)
var namedCaptures = result.groups;

if (namedCaptures === undefined) {
switch (nCaptures) {
case 0:
return ToString(replaceValue(SPREAD(result, 1), position, S));
case 1:
return ToString(replaceValue(SPREAD(result, 2), position, S));
case 2:
return ToString(replaceValue(SPREAD(result, 3), position, S));
case 3:
return ToString(replaceValue(SPREAD(result, 4), position, S));
case 4:
return ToString(replaceValue(SPREAD(result, 5), position, S));
}
}

// Steps 14.g-i, 14.j.i-ii.
// Steps 14.g-i, 14.k.i-ii.
var captures = [];
for (var n = 0; n <= nCaptures; n++) {
assert(typeof result[n] === "string" || result[n] === undefined,
"RegExpMatcher returns only strings and undefined");
_DefineDataProperty(captures, n, result[n]);
}

// Step 14.j.iii.
// Step 14.k.iii.
_DefineDataProperty(captures, nCaptures + 1, position);
_DefineDataProperty(captures, nCaptures + 2, S);

// Steps 14.j.iv-v.
// Step 14.k.iv.
if (namedCaptures !== undefined) {
_DefineDataProperty(captures, nCaptures + 3, namedCaptures);
}

// Steps 14.k.v-vi.
return ToString(callFunction(std_Function_apply, replaceValue, undefined, captures));
}

Expand Down
15 changes: 11 additions & 4 deletions js/src/builtin/RegExpGlobalReplaceOpt.h.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,19 @@ function FUNC_NAME(rx, S, lengthS, replaceValue, flags
var position = result.index | 0;
lastIndex = position + matchLength;

// Steps g-k.
// Steps g-l.
var replacement;
#if defined(FUNCTIONAL)
replacement = RegExpGetFunctionalReplacement(result, S, position, replaceValue);
#elif defined(SUBSTITUTION)
replacement = RegExpGetSubstitution(result, S, position, replaceValue, firstDollarIndex);
// Step l.i
var namedCaptures = result.groups;
if (namedCaptures !== undefined) {
namedCaptures = ToObject(namedCaptures);
}
// Step l.ii
replacement = RegExpGetSubstitution(result, S, position, replaceValue,
firstDollarIndex, namedCaptures);
#elif defined(ELEMBASE)
if (IsObject(elemBase)) {
var prop = GetStringDataProperty(elemBase, matched);
Expand All @@ -97,11 +104,11 @@ function FUNC_NAME(rx, S, lengthS, replaceValue, flags
replacement = replaceValue;
#endif

// Step 14.l.ii.
// Step 14.m.ii.
accumulatedResult += Substring(S, nextSourcePosition,
position - nextSourcePosition) + replacement;

// Step 14.l.iii.
// Step 14.m.iii.
nextSourcePosition = lastIndex;

// Step 11.c.iii.2.
Expand Down
15 changes: 11 additions & 4 deletions js/src/builtin/RegExpLocalReplaceOpt.h.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ function FUNC_NAME(rx, S, lengthS, replaceValue
// Step 14.e-f.
var position = result.index;

// Step 14.l.iii (reordered)
// Step 14.m.iii (reordered)
// To set rx.lastIndex before RegExpGetFunctionalReplacement.
var nextSourcePosition = position + matchLength;
#else
Expand All @@ -113,16 +113,23 @@ function FUNC_NAME(rx, S, lengthS, replaceValue
rx.lastIndex = nextSourcePosition;

var replacement;
// Steps g-j.
// Steps g-l.
#if defined(FUNCTIONAL)
replacement = RegExpGetFunctionalReplacement(result, S, position, replaceValue);
#elif defined(SUBSTITUTION)
replacement = RegExpGetSubstitution(result, S, position, replaceValue, firstDollarIndex);
// Step l.i
var namedCaptures = result.groups;
if (namedCaptures !== undefined) {
namedCaptures = ToObject(namedCaptures);
}
// Step l.ii
replacement = RegExpGetSubstitution(result, S, position, replaceValue, firstDollarIndex,
namedCaptures);
#else
replacement = replaceValue;
#endif

// Step 14.l.ii.
// Step 14.m.ii.
var accumulatedResult = Substring(S, 0, position) + replacement;

// Step 15.
Expand Down

0 comments on commit 7b5df5e

Please sign in to comment.