Chromium Code Reviews| Index: src/js/regexp.js |
| diff --git a/src/js/regexp.js b/src/js/regexp.js |
| index 830fc75b30f7f3d999e99b7971cf08d3a41243f8..dde7ac7599fea0b890948e65248b68593edb9836 100644 |
| --- a/src/js/regexp.js |
| +++ b/src/js/regexp.js |
| @@ -11,21 +11,28 @@ |
| // ------------------------------------------------------------------- |
| // Imports |
| +var AddIndexedProperty; |
| var ExpandReplacement; |
| +var GlobalArray = global.Array; |
| var GlobalObject = global.Object; |
| var GlobalRegExp = global.RegExp; |
| var GlobalRegExpPrototype; |
| var InternalArray = utils.InternalArray; |
| var InternalPackedArray = utils.InternalPackedArray; |
| var MakeTypeError; |
| +var MathMax = global.Math.max; |
| +var MathMin = global.Math.min; |
| var matchSymbol = utils.ImportNow("match_symbol"); |
| var replaceSymbol = utils.ImportNow("replace_symbol"); |
| var searchSymbol = utils.ImportNow("search_symbol"); |
| var splitSymbol = utils.ImportNow("split_symbol"); |
| +var SpeciesConstructor; |
| utils.Import(function(from) { |
| + AddIndexedProperty = from.AddIndexedProperty; |
| ExpandReplacement = from.ExpandReplacement; |
| MakeTypeError = from.MakeTypeError; |
| + SpeciesConstructor = from.SpeciesConstructor; |
| }); |
| // ------------------------------------------------------------------- |
| @@ -46,6 +53,7 @@ var RegExpLastMatchInfo = new InternalPackedArray( |
| // ------------------------------------------------------------------- |
| +// ES6 7.2.8 |
| function IsRegExp(o) { |
| if (!IS_RECEIVER(o)) return false; |
| var is_regexp = o[matchSymbol]; |
| @@ -165,6 +173,51 @@ function RegExpExecNoTests(regexp, string, start) { |
| } |
| +function RegExpSubclassExecJS(string) { |
| + if (!IS_REGEXP(this)) { |
| + throw MakeTypeError(kIncompatibleMethodReceiver, |
| + 'RegExp.prototype.exec', this); |
| + } |
| + |
| + string = TO_STRING(string); |
| + var lastIndex = this.lastIndex; |
| + |
| + // Conversion is required by the ES2015 specification (RegExpBuiltinExec |
| + // algorithm, step 4) even if the value is discarded for non-global RegExps. |
| + var i = TO_LENGTH(lastIndex); |
| + |
| + var global = TO_BOOLEAN(this.global); |
| + var sticky = TO_BOOLEAN(this.sticky); |
| + var updateLastIndex = global || sticky; |
| + if (updateLastIndex) { |
| + if (i < 0 || i > string.length) { |
| + this.lastIndex = 0; |
| + return null; |
| + } |
| + } else { |
| + i = 0; |
| + } |
| + |
| + // matchIndices is either null or the RegExpLastMatchInfo array. |
| + // TODO(littledan): Whether a RegExp is sticky is compiled into the RegExp |
| + // itself, but ES2015 allows monkey-patching this property to differ from |
| + // the internal flags. If it differs, recompile a different RegExp? |
| + var matchIndices = %_RegExpExec(this, string, i, RegExpLastMatchInfo); |
| + |
| + if (IS_NULL(matchIndices)) { |
| + this.lastIndex = 0; |
| + return null; |
| + } |
| + |
| + // Successful match. |
| + if (updateLastIndex) { |
| + this.lastIndex = RegExpLastMatchInfo[CAPTURE1]; |
| + } |
| + RETURN_NEW_RESULT_FROM_MATCH_INFO(matchIndices, string); |
| +} |
| +%FunctionRemovePrototype(RegExpSubclassExecJS); |
|
Yang
2016/03/22 12:49:38
Like previously noted, let's always do this in Ove
Dan Ehrenberg
2016/03/22 18:26:31
See v8:4567 for the bug that this is working aroun
|
| + |
| + |
| function RegExpExecJS(string) { |
| if (!IS_REGEXP(this)) { |
| throw MakeTypeError(kIncompatibleMethodReceiver, |
| @@ -204,6 +257,19 @@ function RegExpExecJS(string) { |
| } |
| +function RegExpSubclassExec(regexp, string) { |
| + var exec = regexp.exec; |
| + if (IS_CALLABLE(exec)) { |
| + var result = %_Call(exec, regexp, string); |
| + if (!IS_OBJECT(result) && !IS_NULL(result)) { |
| + throw MakeTypeError(kInvalidRegExpExecResult); |
| + } |
| + return result; |
| + } |
| + return %_Call(RegExpExecJS, regexp, string); |
| +} |
| + |
| + |
| // One-element cache for the simplified test regexp. |
| var regexp_key; |
| var regexp_val; |
| @@ -261,6 +327,17 @@ function RegExpTest(string) { |
| } |
| } |
| +function RegExpSubclassTest(string) { |
| + if (!IS_OBJECT(this)) { |
| + throw MakeTypeError(kIncompatibleMethodReceiver, |
| + 'RegExp.prototype.test', this); |
| + } |
| + string = TO_STRING(string); |
| + var match = RegExpSubclassExec(this, string); |
| + return !IS_NULL(match); |
| +} |
| +%FunctionRemovePrototype(RegExpSubclassTest); |
| + |
| function TrimRegExp(regexp) { |
| if (regexp_key !== regexp) { |
| regexp_key = regexp; |
| @@ -382,9 +459,68 @@ function RegExpSplit(string, limit) { |
| } |
| +function RegExpSubclassSplit(string, limit) { |
| + if (!IS_RECEIVER(this)) { |
| + throw MakeTypeError(kIncompatibleMethodReceiver, |
| + "RegExp.prototype.@@split", this); |
| + } |
| + string = TO_STRING(string); |
| + var constructor = SpeciesConstructor(this, GlobalRegExp); |
| + var flags = TO_STRING(this.flags); |
| + var unicode = %StringIndexOf(flags, 'u', 0) >= 0; |
| + var sticky = %StringIndexOf(flags, 'y', 0) >= 0; |
| + var new_flags = sticky ? flags : flags + "y"; |
| + var splitter = new constructor(this, new_flags); |
| + var array = new GlobalArray(); |
| + var array_index = 0; |
| + var lim = (IS_UNDEFINED(limit)) ? kMaxUint32 : TO_UINT32(limit); |
| + var size = string.length; |
| + var prev_string_index = 0; |
| + if (lim === 0) return array; |
| + var result; |
| + if (size === 0) { |
| + result = RegExpSubclassExec(splitter, string); |
| + if (IS_NULL(result)) AddIndexedProperty(array, 0, string); |
| + return array; |
| + } |
| + var string_index = prev_string_index; |
| + while (string_index < size) { |
| + splitter.lastIndex = string_index; |
| + result = RegExpSubclassExec(splitter, string); |
| + if (IS_NULL(result)) { |
| + string_index += GetUnicodeAdvancedIncrement(string, string_index, |
| + unicode); |
| + } else { |
| + var end = MathMin(splitter.lastIndex, size); |
| + if (end === prev_string_index) { |
| + string_index += GetUnicodeAdvancedIncrement(string, string_index, |
| + unicode); |
| + } else { |
| + AddIndexedProperty( |
| + array, array_index, |
| + %_SubString(string, prev_string_index, string_index)); |
| + array_index++; |
| + if (array_index === lim) return array; |
| + prev_string_index = end; |
| + var number_of_captures = MathMax(TO_LENGTH(result.length), 0); |
| + for (var i = 1; i < number_of_captures; i++) { |
| + AddIndexedProperty(array, array_index, result[i]); |
| + array_index++; |
| + if (array_index === lim) return array; |
| + } |
| + string_index = prev_string_index; |
| + } |
| + } |
| + } |
| + AddIndexedProperty(array, array_index, |
| + %_SubString(string, prev_string_index, size)); |
| + return array; |
| +} |
| +%FunctionRemovePrototype(RegExpSubclassSplit); |
| + |
| + |
| // ES6 21.2.5.6. |
| function RegExpMatch(string) { |
| - // TODO(yangguo): allow non-regexp receivers. |
| if (!IS_REGEXP(this)) { |
| throw MakeTypeError(kIncompatibleMethodReceiver, |
| "RegExp.prototype.@@match", this); |
| @@ -398,6 +534,34 @@ function RegExpMatch(string) { |
| } |
| +function RegExpSubclassMatch(string) { |
| + if (!IS_OBJECT(this)) { |
| + throw MakeTypeError(kIncompatibleMethodReceiver, |
| + "RegExp.prototype.@@match", this); |
| + } |
| + string = TO_STRING(string); |
| + var global = this.global; |
| + if (!global) return RegExpSubclassExec(this, string); |
| + var unicode = this.unicode; |
| + this.lastIndex = 0; |
| + var array = []; |
| + var n = 0; |
| + var result; |
| + while (true) { |
| + result = RegExpSubclassExec(this, string); |
| + if (IS_NULL(result)) { |
| + if (n === 0) return null; |
| + return array; |
| + } |
| + var matchStr = TO_STRING(result[0]); |
| + %AddElement(array, n, matchStr); |
| + if (matchStr === "") AdvanceStringIndex(this, string, unicode); |
| + n++; |
| + } |
| +} |
| +%FunctionRemovePrototype(RegExpSubclassMatch); |
| + |
| + |
| // ES6 21.2.5.8. |
| // TODO(lrn): This array will survive indefinitely if replace is never |
| @@ -525,7 +689,6 @@ function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) { |
| function RegExpReplace(string, replace) { |
| - // TODO(littledan): allow non-regexp receivers. |
| if (!IS_REGEXP(this)) { |
| throw MakeTypeError(kIncompatibleMethodReceiver, |
| "RegExp.prototype.@@replace", this); |
| @@ -567,9 +730,179 @@ function RegExpReplace(string, replace) { |
| } |
| +// Expand the $-expressions in the string and return a new string with |
| +// the result. |
| +function GetCaptures(matched, string, position, captures, replacement) { |
| + var match_length = matched.length; |
| + var string_length = string.length; |
| + var captures_length = captures.length; |
| + var tail_pos = position + match_length; |
| + var result = ""; |
| + var pos, expansion, peek, next, scaled_index, advance, new_scaled_index; |
| + |
| + var next = %StringIndexOf(replacement, '$', 0); |
| + if (next < 0) { |
| + result += replacement; |
| + return result; |
| + } |
| + |
| + if (next > 0) result += %_SubString(replacement, 0, next); |
| + |
| + while (true) { |
| + expansion = '$'; |
| + pos = next + 1; |
| + if (pos < replacement.length) { |
| + peek = %_StringCharCodeAt(replacement, pos); |
| + if (peek == 36) { // $$ |
| + ++pos; |
| + result += '$'; |
| + } else if (peek == 38) { // $& - match |
| + ++pos; |
| + result += matched; |
| + } else if (peek == 96) { // $` - prefix |
| + ++pos; |
| + result += %_SubString(string, 0, position); |
| + } else if (peek == 39) { // $' - suffix |
| + ++pos; |
| + result += %_SubString(string, tail_pos, string_length); |
| + } else if (peek >= 48 && peek <= 57) { |
| + // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99 |
| + scaled_index = (peek - 48); |
| + advance = 1; |
| + if (pos + 1 < replacement.length) { |
| + next = %_StringCharCodeAt(replacement, pos + 1); |
| + if (next >= 48 && next <= 57) { |
| + new_scaled_index = scaled_index * 10 + ((next - 48)); |
| + if (new_scaled_index < captures_length) { |
| + scaled_index = new_scaled_index; |
| + advance = 2; |
| + } |
| + } |
| + } |
| + if (scaled_index != 0 && scaled_index < captures_length) { |
| + var capture = captures[scaled_index]; |
| + if (!IS_UNDEFINED(capture)) result += capture; |
| + pos += advance; |
| + } else { |
| + result += '$'; |
| + } |
| + } else { |
| + result += '$'; |
| + } |
| + } else { |
| + result += '$'; |
| + } |
| + |
| + // Go the the next $ in the replacement. |
| + next = %StringIndexOf(replacement, '$', pos); |
| + |
| + // Return if there are no more $ characters in the replacement. If we |
| + // haven't reached the end, we need to append the suffix. |
| + if (next < 0) { |
| + if (pos < replacement.length) { |
| + result += %_SubString(replacement, pos, replacement.length); |
| + } |
| + return result; |
| + } |
| + |
| + // Append substring between the previous and the next $ character. |
| + if (next > pos) { |
| + result += %_SubString(replacement, pos, next); |
| + } |
| + } |
| + return result; |
| +} |
| + |
| + |
| +function GetUnicodeAdvancedIncrement(string, index, unicode) { |
| + var increment = 1; |
| + if (unicode) { |
| + var first = %_StringCharCodeAt(string, index); |
| + if (first >= 0xD800 && first <= 0xDBFF && string.length > index + 1) { |
| + var second = %_StringCharCodeAt(string, index + 1); |
| + if (second >= 0xDC00 && second <= 0xDFFF) { |
| + increment = 2; |
| + } |
| + } |
| + } |
| + return increment; |
| +} |
| + |
| + |
| +function AdvanceStringIndex(regexp, string, unicode) { |
| + var last_index = regexp.lastIndex; |
| + regexp.lastIndex = last_index + |
| + GetUnicodeAdvancedIncrement(string, last_index, unicode); |
| +} |
| + |
| + |
| +function RegExpSubclassReplace(string, replace) { |
| + if (!IS_OBJECT(this)) { |
| + throw MakeTypeError(kIncompatibleMethodReceiver, |
| + "RegExp.prototype.@@replace", this); |
| + } |
| + string = TO_STRING(string); |
| + var length = string.length; |
| + var functional_replace = IS_CALLABLE(replace); |
| + if (!functional_replace) replace = TO_STRING(replace); |
| + var global = this.global; |
| + if (global) { |
| + var unicode = this.unicode; |
| + this.lastIndex = 0; |
| + } |
| + var results = new InternalArray(); |
| + var result, replacement; |
| + while (true) { |
| + result = RegExpSubclassExec(this, string); |
| + if (IS_NULL(result)) { |
| + break; |
| + } else { |
| + results.push(result); |
| + if (!global) break; |
| + var match_str = TO_STRING(result[0]); |
| + if (match_str === "") AdvanceStringIndex(this, string, unicode); |
| + } |
| + } |
| + var accumulated_result = ""; |
| + var next_source_position = 0; |
| + for (var i = 0; i < results.length; i++) { |
| + result = results[i]; |
| + var captures_length = MathMax(TO_LENGTH(result.length), 0); |
| + var matched = TO_STRING(result[0]); |
| + var matched_length = matched.length; |
| + var position = MathMax(MathMin(TO_INTEGER(result.index), length), 0); |
| + var captures = new InternalArray(); |
| + for (var n = 0; n < captures_length; n++) { |
| + var capture = result[n]; |
| + if (!IS_UNDEFINED(capture)) capture = TO_STRING(capture); |
| + captures[n] = capture; |
| + } |
| + if (functional_replace) { |
| + var parameters = new InternalArray(captures_length + 2); |
| + for (var j = 0; j < captures_length; j++) { |
| + parameters[j] = captures[j]; |
| + } |
| + parameters[j] = position; |
| + parameters[j + 1] = string; |
| + replacement = %reflect_apply(replace, UNDEFINED, parameters, 0, |
| + parameters.length); |
| + } else { |
| + replacement = GetCaptures(matched, string, position, captures, replace); |
| + } |
| + if (position >= next_source_position) { |
| + accumulated_result += |
| + %_SubString(string, next_source_position, position) + replacement; |
| + next_source_position = position + matched_length; |
| + } |
| + } |
| + if (next_source_position >= length) return accumulated_result; |
| + return accumulated_result + %_SubString(string, next_source_position, length); |
| +} |
| +%FunctionRemovePrototype(RegExpSubclassReplace); |
| + |
| + |
| // ES6 21.2.5.9. |
| function RegExpSearch(string) { |
| - // TODO(yangguo): allow non-regexp receivers. |
| if (!IS_REGEXP(this)) { |
| throw MakeTypeError(kIncompatibleMethodReceiver, |
| "RegExp.prototype.@@search", this); |
| @@ -580,6 +913,22 @@ function RegExpSearch(string) { |
| } |
| +function RegExpSubclassSearch(string) { |
| + if (!IS_OBJECT(this)) { |
| + throw MakeTypeError(kIncompatibleMethodReceiver, |
| + "RegExp.prototype.@@search", this); |
| + } |
| + string = TO_STRING(string); |
| + var previousLastIndex = this.lastIndex; |
| + this.lastIndex = 0; |
| + var result = RegExpSubclassExec(this, string); |
| + this.lastIndex = previousLastIndex; |
| + if (IS_NULL(result)) return -1; |
| + return result.index; |
| +} |
| +%FunctionRemovePrototype(RegExpSubclassSearch); |
| + |
| + |
| // Getters for the static properties lastMatch, lastParen, leftContext, and |
| // rightContext of the RegExp constructor. The properties are computed based |
| // on the captures array of the last successful match and the subject string |
| @@ -704,6 +1053,7 @@ function RegExpGetSource() { |
| %FunctionSetName(RegExpGetSource, "RegExp.prototype.source"); |
| %SetNativeFlag(RegExpGetSource); |
| + |
|
Yang
2016/03/22 12:49:38
stray edit?
Dan Ehrenberg
2016/03/22 18:26:31
Reverted
|
| // ------------------------------------------------------------------- |
| %FunctionSetInstanceClassName(GlobalRegExp, 'RegExp'); |
| @@ -781,6 +1131,12 @@ utils.Export(function(to) { |
| to.RegExpExec = DoRegExpExec; |
| to.RegExpExecNoTests = RegExpExecNoTests; |
| to.RegExpLastMatchInfo = RegExpLastMatchInfo; |
| + to.RegExpSubclassExecJS = RegExpSubclassExecJS; |
| + to.RegExpSubclassMatch = RegExpSubclassMatch; |
| + to.RegExpSubclassReplace = RegExpSubclassReplace; |
| + to.RegExpSubclassSearch = RegExpSubclassSearch; |
| + to.RegExpSubclassSplit = RegExpSubclassSplit; |
| + to.RegExpSubclassTest = RegExpSubclassTest; |
| to.RegExpTest = RegExpTest; |
| to.IsRegExp = IsRegExp; |
| }); |