Index: src/regexp.js |
diff --git a/src/regexp.js b/src/regexp.js |
index 59c40407fbc20ddc4406d0ef2661d7d9bcf39099..f87a5ee18ff8c9000dc4bed15e6d1db4bded574a 100644 |
--- a/src/regexp.js |
+++ b/src/regexp.js |
@@ -126,11 +126,11 @@ function RegExpCache() { |
this.regExp = 0; |
this.subject = 0; |
this.replaceString = 0; |
- this.lastIndex = 0; // Also used for splitLimit when type is "split" |
this.answer = 0; |
// answerSaved marks whether the contents of answer is valid for a cache |
// hit in RegExpExec, StringMatch and StringSplit. |
this.answerSaved = false; |
+ this.splitLimit = 0; // Used only when type is "split". |
} |
@@ -181,22 +181,30 @@ function RegExpExec(string) { |
var cache = regExpCache; |
var saveAnswer = false; |
+ var lastIndex = this.lastIndex; |
+ |
+ // Since cache.subject is always a string, a matching input can not |
+ // cause visible side-effects when converted to a string, so we can omit |
+ // the conversion required by the specification. |
+ // Likewise, the regexp.lastIndex and regexp.global properties are value |
+ // properties that are not configurable, so reading them can also not cause |
+ // any side effects (converting lastIndex to a number can, though). |
if (%_ObjectEquals(cache.type, 'exec') && |
- %_ObjectEquals(cache.lastIndex, this.lastIndex) && |
+ %_ObjectEquals(0, lastIndex) && |
%_IsRegExpEquivalent(cache.regExp, this) && |
%_ObjectEquals(cache.subject, string)) { |
if (cache.answerSaved) { |
- // If this regexp is global, cache.lastIndex is zero, so we only get |
- // here if this.lastIndex is zero, and resulting this.lastIndex |
- // must be zero too, so no change is necessary. |
- if (!this.global) this.lastIndex = lastMatchInfo[CAPTURE1]; |
+ // The regexp.lastIndex value must be 0 for non-global RegExps, and for |
+ // global RegExps we only cache negative results, which gives a lastIndex |
+ // of zero as well. |
+ this.lastIndex = 0; |
return %_RegExpCloneResult(cache.answer); |
} else { |
saveAnswer = true; |
} |
} |
- if (%_ArgumentsLength() == 0) { |
+ if (%_ArgumentsLength() === 0) { |
var regExpInput = LAST_INPUT(lastMatchInfo); |
if (IS_UNDEFINED(regExpInput)) { |
throw MakeError('no_input_to_regexp', [this]); |
@@ -209,41 +217,48 @@ function RegExpExec(string) { |
} else { |
s = ToString(string); |
} |
- var lastIndex = this.lastIndex; |
- |
- var i = this.global ? TO_INTEGER(lastIndex) : 0; |
+ var global = this.global; |
- if (i < 0 || i > s.length) { |
- this.lastIndex = 0; |
- return null; |
+ // Conversion is required by the ES5 specification (RegExp.prototype.exec |
+ // algorithm, step 5) even if the value is discarded for non-global RegExps. |
+ var i = TO_INTEGER(lastIndex); |
+ if (global) { |
+ if (i < 0 || i > s.length) { |
+ this.lastIndex = 0; |
+ return null; |
+ } |
+ } else { |
+ i = 0; |
} |
%_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, s, lastIndex]); |
// matchIndices is either null or the lastMatchInfo array. |
var matchIndices = %_RegExpExec(this, s, i, lastMatchInfo); |
- if (matchIndices == null) { |
- if (this.global) { |
+ if (matchIndices === null) { |
+ if (global) { |
+ // Cache negative result only if initial lastIndex was zero. |
this.lastIndex = 0; |
- if (lastIndex != 0) return matchIndices; |
+ if (lastIndex !== 0) return matchIndices; |
} |
- cache.lastIndex = lastIndex; |
cache.regExp = this; |
- cache.subject = s; |
- cache.answer = matchIndices; // Null. |
+ cache.subject = s; // Always a string. |
+ cache.answer = null; |
cache.answerSaved = true; // Safe since no cloning is needed. |
cache.type = 'exec'; |
return matchIndices; // No match. |
} |
+ |
+ // Successful match. |
lastMatchInfoOverride = null; |
var result = BuildResultFromMatchInfo(matchIndices, s); |
- if (this.global) { |
+ if (global) { |
+ // Don't cache positive results for global regexps. |
this.lastIndex = lastMatchInfo[CAPTURE1]; |
} else { |
cache.regExp = this; |
cache.subject = s; |
- cache.lastIndex = lastIndex; |
if (saveAnswer) cache.answer = %_RegExpCloneResult(result); |
cache.answerSaved = saveAnswer; |
cache.type = 'exec'; |
@@ -273,32 +288,49 @@ function RegExpTest(string) { |
} |
string = regExpInput; |
} |
- var s; |
- if (IS_STRING(string)) { |
- s = string; |
- } else { |
- s = ToString(string); |
- } |
var lastIndex = this.lastIndex; |
+ |
var cache = regExpCache; |
if (%_ObjectEquals(cache.type, 'test') && |
%_IsRegExpEquivalent(cache.regExp, this) && |
%_ObjectEquals(cache.subject, string) && |
- %_ObjectEquals(cache.lastIndex, lastIndex)) { |
- // If this regexp is not global, cache.lastIndex is zero, so we only get |
- // here if this.lastIndex is zero, and resulting this.lastIndex |
- // must be zero too, so no change is necessary. |
- if (this.global) this.lastIndex = lastMatchInfo[CAPTURE1]; |
+ %_ObjectEquals(0, lastIndex)) { |
+ // The regexp.lastIndex value must be 0 for non-global RegExps, and for |
+ // global RegExps we only cache negative results, which gives a resulting |
+ // lastIndex of zero as well. |
+ if (global) this.lastIndex = 0; |
return cache.answer; |
} |
+ var s; |
+ if (IS_STRING(string)) { |
+ s = string; |
+ } else { |
+ s = ToString(string); |
+ } |
+ var length = s.length; |
+ |
+ // Conversion is required by the ES5 specification (RegExp.prototype.exec |
+ // algorithm, step 5) even if the value is discarded for non-global RegExps. |
+ var i = TO_INTEGER(lastIndex); |
+ if (global) { |
+ if (i < 0 || i > length) { |
+ this.lastIndex = 0; |
+ return false; |
+ } |
+ } else { |
+ i = 0; |
+ } |
+ |
+ var global = this.global; |
+ |
// Remove irrelevant preceeding '.*' in a test regexp. The expression |
// checks whether this.source starts with '.*' and that the third |
// char is not a '?' |
- if (%_StringCharCodeAt(this.source,0) == 46 && // '.' |
- %_StringCharCodeAt(this.source,1) == 42 && // '*' |
- %_StringCharCodeAt(this.source,2) != 63) { // '?' |
+ if (%_StringCharCodeAt(this.source, 0) == 46 && // '.' |
+ %_StringCharCodeAt(this.source, 1) == 42 && // '*' |
+ %_StringCharCodeAt(this.source, 2) != 63) { // '?' |
if (!%_ObjectEquals(regexp_key, this)) { |
regexp_key = this; |
regexp_val = new $RegExp(this.source.substring(2, this.source.length), |
@@ -309,33 +341,28 @@ function RegExpTest(string) { |
if (!regexp_val.test(s)) return false; |
} |
- var length = s.length; |
- var i = this.global ? TO_INTEGER(lastIndex) : 0; |
- |
- cache.type = 'test'; |
- cache.regExp = this; |
- cache.subject = s; |
- cache.lastIndex = i; |
- |
- if (i < 0 || i > length) { |
- this.lastIndex = 0; |
- cache.answer = false; |
- return false; |
- } |
- |
%_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, s, lastIndex]); |
// matchIndices is either null or the lastMatchInfo array. |
var matchIndices = %_RegExpExec(this, s, i, lastMatchInfo); |
- if (matchIndices == null) { |
- if (this.global) this.lastIndex = 0; |
- cache.answer = false; |
- return false; |
+ var result = (matchIndices !== null); |
+ if (result) { |
+ lastMatchInfoOverride = null; |
} |
- lastMatchInfoOverride = null; |
- if (this.global) this.lastIndex = lastMatchInfo[CAPTURE1]; |
- cache.answer = true; |
- return true; |
+ if (global) { |
+ if (result) { |
+ this.lastIndex = lastMatchInfo[CAPTURE1]; |
+ return true; |
+ } else { |
+ this.lastIndex = 0; |
+ if (lastIndex !== 0) return false; |
+ } |
+ } |
+ cache.type = 'test'; |
+ cache.regExp = this; |
+ cache.subject = s; |
+ cache.answer = result; |
+ return result; |
} |
@@ -345,12 +372,9 @@ function RegExpToString() { |
// ecma_2/RegExp/properties-001.js. |
var src = this.source ? this.source : '(?:)'; |
var result = '/' + src + '/'; |
- if (this.global) |
- result += 'g'; |
- if (this.ignoreCase) |
- result += 'i'; |
- if (this.multiline) |
- result += 'm'; |
+ if (this.global) result += 'g'; |
+ if (this.ignoreCase) result += 'i'; |
+ if (this.multiline) result += 'm'; |
return result; |
} |