Index: src/js/regexp.js |
diff --git a/src/js/regexp.js b/src/js/regexp.js |
index b9d0f50b244345c52e73e1cb33fdbe09d7c3c6f6..584d4cdf41bdbe58c76cb3bd9ee22249f34f494a 100644 |
--- a/src/js/regexp.js |
+++ b/src/js/regexp.js |
@@ -162,6 +162,46 @@ |
%FunctionRemovePrototype(RegExpSubclassExecJS); |
+// Legacy implementation of RegExp.prototype.exec |
+function RegExpExecJS(string) { |
+ if (!IS_REGEXP(this)) { |
+ throw %make_type_error(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 updateLastIndex = REGEXP_GLOBAL(this) || REGEXP_STICKY(this); |
+ if (updateLastIndex) { |
+ if (i < 0 || i > string.length) { |
+ this.lastIndex = 0; |
+ return null; |
+ } |
+ } else { |
+ i = 0; |
+ } |
+ |
+ // matchIndices is either null or the RegExpLastMatchInfo array. |
+ 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); |
+} |
+ |
+ |
// ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S ) |
// Also takes an optional exec method in case our caller |
// has already fetched exec. |
@@ -176,9 +216,68 @@ |
} |
return result; |
} |
- return %_Call(RegExpSubclassExecJS, regexp, string); |
+ return %_Call(RegExpExecJS, regexp, string); |
} |
%SetForceInlineFlag(RegExpSubclassExec); |
+ |
+ |
+// One-element cache for the simplified test regexp. |
+var regexp_key; |
+var regexp_val; |
+ |
+// Legacy implementation of RegExp.prototype.test |
+// Section 15.10.6.3 doesn't actually make sense, but the intention seems to be |
+// that test is defined in terms of String.prototype.exec. However, it probably |
+// means the original value of String.prototype.exec, which is what everybody |
+// else implements. |
+function RegExpTest(string) { |
+ if (!IS_REGEXP(this)) { |
+ throw %make_type_error(kIncompatibleMethodReceiver, |
+ 'RegExp.prototype.test', 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); |
+ |
+ if (REGEXP_GLOBAL(this) || REGEXP_STICKY(this)) { |
+ if (i < 0 || i > string.length) { |
+ this.lastIndex = 0; |
+ return false; |
+ } |
+ // matchIndices is either null or the RegExpLastMatchInfo array. |
+ var matchIndices = %_RegExpExec(this, string, i, RegExpLastMatchInfo); |
+ if (IS_NULL(matchIndices)) { |
+ this.lastIndex = 0; |
+ return false; |
+ } |
+ this.lastIndex = RegExpLastMatchInfo[CAPTURE1]; |
+ return true; |
+ } else { |
+ // Non-global, non-sticky regexp. |
+ // Remove irrelevant preceeding '.*' in a test regexp. The expression |
+ // checks whether this.source starts with '.*' and that the third char is |
+ // not a '?'. But see https://code.google.com/p/v8/issues/detail?id=3560 |
+ var regexp = this; |
+ var source = REGEXP_SOURCE(regexp); |
+ if (source.length >= 3 && |
+ %_StringCharCodeAt(source, 0) == 46 && // '.' |
+ %_StringCharCodeAt(source, 1) == 42 && // '*' |
+ %_StringCharCodeAt(source, 2) != 63) { // '?' |
+ regexp = TrimRegExp(regexp); |
+ } |
+ // matchIndices is either null or the RegExpLastMatchInfo array. |
+ var matchIndices = %_RegExpExec(regexp, string, 0, RegExpLastMatchInfo); |
+ if (IS_NULL(matchIndices)) { |
+ this.lastIndex = 0; |
+ return false; |
+ } |
+ return true; |
+ } |
+} |
// ES#sec-regexp.prototype.test RegExp.prototype.test ( S ) |
@@ -192,6 +291,18 @@ |
return !IS_NULL(match); |
} |
%FunctionRemovePrototype(RegExpSubclassTest); |
+ |
+function TrimRegExp(regexp) { |
+ if (regexp_key !== regexp) { |
+ regexp_key = regexp; |
+ regexp_val = |
+ new GlobalRegExp( |
+ %_SubString(REGEXP_SOURCE(regexp), 2, REGEXP_SOURCE(regexp).length), |
+ (REGEXP_IGNORE_CASE(regexp) ? REGEXP_MULTILINE(regexp) ? "im" : "i" |
+ : REGEXP_MULTILINE(regexp) ? "m" : "")); |
+ } |
+ return regexp_val; |
+} |
function AtSurrogatePair(subject, index) { |
@@ -816,7 +927,10 @@ |
to.InternalRegExpMatch = InternalRegExpMatch; |
to.InternalRegExpReplace = InternalRegExpReplace; |
to.IsRegExp = IsRegExp; |
+ to.RegExpExec = DoRegExpExec; |
to.RegExpInitialize = RegExpInitialize; |
+ to.RegExpLastMatchInfo = RegExpLastMatchInfo; |
+ to.RegExpTest = RegExpTest; |
}); |
}) |