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; |
}); |