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