From 5ab3874559475479efa2b93f53bf508b71890497 Mon Sep 17 00:00:00 2001 From: jgruber Date: Tue, 20 Sep 2016 01:13:21 -0700 Subject: [PATCH] [regexp/string] Merge ExpandReplacement and GetSubstitution R=littledan@chromium.org BUG=v8:5339 Review-Url: https://codereview.chromium.org/2332333002 Cr-Commit-Position: refs/heads/master@{#39528} --- src/js/regexp.js | 49 ++++++++++++++++++---- src/js/string.js | 106 ++++------------------------------------------- 2 files changed, 48 insertions(+), 107 deletions(-) diff --git a/src/js/regexp.js b/src/js/regexp.js index 6c9699cbc3..396b694ea6 100644 --- a/src/js/regexp.js +++ b/src/js/regexp.js @@ -11,7 +11,6 @@ // ------------------------------------------------------------------- // Imports -var ExpandReplacement; var GlobalArray = global.Array; var GlobalObject = global.Object; var GlobalRegExp = global.RegExp; @@ -28,7 +27,6 @@ var splitSymbol = utils.ImportNow("split_symbol"); var SpeciesConstructor; utils.Import(function(from) { - ExpandReplacement = from.ExpandReplacement; MaxSimple = from.MaxSimple; MinSimple = from.MinSimple; SpeciesConstructor = from.SpeciesConstructor; @@ -565,6 +563,31 @@ function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) { return result + %_SubString(subject, endOfMatch, subject.length); } +// Wraps access to matchInfo's captures into a format understood by +// GetSubstitution. +function MatchInfoCaptureWrapper(matches, subject) { + this.length = NUMBER_OF_CAPTURES(matches) >> 1; + this.match = matches; + this.subject = subject; +} + +MatchInfoCaptureWrapper.prototype.at = function(ix) { + const match = this.match; + const start = match[CAPTURE(ix << 1)]; + if (start < 0) return UNDEFINED; + return %_SubString(this.subject, start, match[CAPTURE((ix << 1) + 1)]); +}; +%SetForceInlineFlag(MatchInfoCaptureWrapper.prototype.at); + +function ArrayCaptureWrapper(array) { + this.length = array.length; + this.array = array; +} + +ArrayCaptureWrapper.prototype.at = function(ix) { + return this.array[ix]; +}; +%SetForceInlineFlag(ArrayCaptureWrapper.prototype.at); function RegExpReplace(string, replace) { if (!IS_REGEXP(this)) { @@ -588,9 +611,17 @@ function RegExpReplace(string, replace) { return %_SubString(subject, 0, match[CAPTURE0]) + %_SubString(subject, match[CAPTURE1], subject.length) } - return ExpandReplacement(replace, subject, RegExpLastMatchInfo, - %_SubString(subject, 0, match[CAPTURE0])) + - %_SubString(subject, match[CAPTURE1], subject.length); + const captures = new MatchInfoCaptureWrapper(match, subject); + const start = match[CAPTURE0]; + const end = match[CAPTURE1]; + + const prefix = %_SubString(subject, 0, start); + const matched = %_SubString(subject, start, end); + const suffix = %_SubString(subject, end, subject.length); + + return prefix + + GetSubstitution(matched, subject, start, captures, replace) + + suffix; } // Global regexp search, string replace. @@ -612,8 +643,6 @@ function RegExpReplace(string, replace) { // GetSubstitution(matched, str, position, captures, replacement) // Expand the $-expressions in the string and return a new string with // the result. -// TODO(littledan): Call this function from String.prototype.replace instead -// of the very similar ExpandReplacement in src/js/string.js function GetSubstitution(matched, string, position, captures, replacement) { var matchLength = matched.length; var stringLength = string.length; @@ -662,7 +691,7 @@ function GetSubstitution(matched, string, position, captures, replacement) { } } if (scaledIndex != 0 && scaledIndex < capturesLength) { - var capture = captures[scaledIndex]; + var capture = captures.at(scaledIndex); if (!IS_UNDEFINED(capture)) result += capture; pos += advance; } else { @@ -786,7 +815,8 @@ function RegExpSubclassReplace(string, replace) { replacement = %reflect_apply(replace, UNDEFINED, parameters, 0, parameters.length); } else { - replacement = GetSubstitution(matched, string, position, captures, + const capturesWrapper = new ArrayCaptureWrapper(captures); + replacement = GetSubstitution(matched, string, position, capturesWrapper, replace); } if (position >= nextSourcePosition) { @@ -1095,6 +1125,7 @@ function InternalRegExpReplace(regexp, subject, replacement) { // Exports utils.Export(function(to) { + to.GetSubstitution = GetSubstitution; to.InternalRegExpMatch = InternalRegExpMatch; to.InternalRegExpReplace = InternalRegExpReplace; to.IsRegExp = IsRegExp; diff --git a/src/js/string.js b/src/js/string.js index b8ab5d4ac5..a6dc2f082d 100644 --- a/src/js/string.js +++ b/src/js/string.js @@ -10,6 +10,7 @@ // Imports var ArrayJoin; +var GetSubstitution; var GlobalRegExp = global.RegExp; var GlobalString = global.String; var IsRegExp; @@ -23,6 +24,7 @@ var splitSymbol = utils.ImportNow("split_symbol"); utils.Import(function(from) { ArrayJoin = from.ArrayJoin; + GetSubstitution = from.GetSubstitution; IsRegExp = from.IsRegExp; MaxSimple = from.MaxSimple; MinSimple = from.MinSimple; @@ -79,14 +81,6 @@ function StringMatchJS(pattern) { } -// This has the same size as the RegExpLastMatchInfo array, and can be used -// for functions that expect that structure to be returned. It is used when -// the needle is a string rather than a regexp. In this case we can't update -// lastMatchArray without erroneously affecting the properties on the global -// RegExp object. -var reusableMatchInfo = [2, "", "", -1, -1]; - - // ES6, section 21.1.3.14 function StringReplace(search, replace) { CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace"); @@ -138,101 +132,18 @@ function StringReplace(search, replace) { if (IS_CALLABLE(replace)) { result += replace(search, start, subject); } else { - reusableMatchInfo[CAPTURE0] = start; - reusableMatchInfo[CAPTURE1] = end; - result = ExpandReplacement(TO_STRING(replace), - subject, - reusableMatchInfo, - result); + // In this case, we don't have any capture groups and can get away with + // faking the captures object by simply setting its length to 1. + const captures = { length: 1 }; + const matched = %_SubString(subject, start, end); + result += GetSubstitution(matched, subject, start, captures, + TO_STRING(replace)); } return result + %_SubString(subject, end, subject.length); } -// Expand the $-expressions in the string and return a new string with -// the result. -function ExpandReplacement(string, subject, matchInfo, result) { - var length = string.length; - var next = %StringIndexOf(string, '$', 0); - if (next < 0) { - if (length > 0) result += string; - return result; - } - - if (next > 0) result += %_SubString(string, 0, next); - - while (true) { - var expansion = '$'; - var position = next + 1; - if (position < length) { - var peek = %_StringCharCodeAt(string, position); - if (peek == 36) { // $$ - ++position; - result += '$'; - } else if (peek == 38) { // $& - match - ++position; - result += - %_SubString(subject, matchInfo[CAPTURE0], matchInfo[CAPTURE1]); - } else if (peek == 96) { // $` - prefix - ++position; - result += %_SubString(subject, 0, matchInfo[CAPTURE0]); - } else if (peek == 39) { // $' - suffix - ++position; - result += %_SubString(subject, matchInfo[CAPTURE1], subject.length); - } else if (peek >= 48 && peek <= 57) { - // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99 - var scaled_index = (peek - 48) << 1; - var advance = 1; - var number_of_captures = NUMBER_OF_CAPTURES(matchInfo); - if (position + 1 < string.length) { - var next = %_StringCharCodeAt(string, position + 1); - if (next >= 48 && next <= 57) { - var new_scaled_index = scaled_index * 10 + ((next - 48) << 1); - if (new_scaled_index < number_of_captures) { - scaled_index = new_scaled_index; - advance = 2; - } - } - } - if (scaled_index != 0 && scaled_index < number_of_captures) { - var start = matchInfo[CAPTURE(scaled_index)]; - if (start >= 0) { - result += - %_SubString(subject, start, matchInfo[CAPTURE(scaled_index + 1)]); - } - position += advance; - } else { - result += '$'; - } - } else { - result += '$'; - } - } else { - result += '$'; - } - - // Go the the next $ in the string. - next = %StringIndexOf(string, '$', position); - - // Return if there are no more $ characters in the string. If we - // haven't reached the end, we need to append the suffix. - if (next < 0) { - if (position < length) { - result += %_SubString(string, position, length); - } - return result; - } - - // Append substring between the previous and the next $ character. - if (next > position) { - result += %_SubString(string, position, next); - } - } - return result; -} - - // ES6 21.1.3.15. function StringSearch(pattern) { CHECK_OBJECT_COERCIBLE(this, "String.prototype.search"); @@ -707,7 +618,6 @@ utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [ // Exports utils.Export(function(to) { - to.ExpandReplacement = ExpandReplacement; to.StringIndexOf = StringIndexOf; to.StringMatch = StringMatchJS; to.StringReplace = StringReplace;