| OLD | NEW |
| 1 // Copyright 2012 the V8 project authors. All rights reserved. | 1 // Copyright 2012 the V8 project authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // This file relies on the fact that the following declaration has been made | 5 // This file relies on the fact that the following declaration has been made |
| 6 // in runtime.js: | 6 // in runtime.js: |
| 7 // var $Object = global.Object; | 7 // var $Object = global.Object; |
| 8 // var $Array = global.Array; | 8 // var $Array = global.Array; |
| 9 | 9 |
| 10 var $RegExp = global.RegExp; | 10 var $RegExp = global.RegExp; |
| 11 | 11 |
| 12 // ------------------------------------------------------------------- | 12 // ------------------------------------------------------------------- |
| 13 | 13 |
| 14 // A recursive descent parser for Patterns according to the grammar of | 14 // A recursive descent parser for Patterns according to the grammar of |
| 15 // ECMA-262 15.10.1, with deviations noted below. | 15 // ECMA-262 15.10.1, with deviations noted below. |
| 16 function DoConstructRegExp(object, pattern, flags) { | 16 function DoConstructRegExp(object, pattern, flags) { |
| 17 // RegExp : Called as constructor; see ECMA-262, section 15.10.4. | 17 // RegExp : Called as constructor; see ECMA-262, section 15.10.4. |
| 18 if (IS_REGEXP(pattern)) { | 18 if (IS_REGEXP(pattern)) { |
| 19 if (!IS_UNDEFINED(flags)) { | 19 if (!IS_UNDEFINED(flags)) { |
| 20 throw MakeTypeError('regexp_flags', []); | 20 throw MakeTypeError('regexp_flags', []); |
| 21 } | 21 } |
| 22 flags = (pattern.global ? 'g' : '') | 22 flags = (pattern.global ? 'g' : '') |
| 23 + (pattern.ignoreCase ? 'i' : '') | 23 + (pattern.ignoreCase ? 'i' : '') |
| 24 + (pattern.multiline ? 'm' : ''); | 24 + (pattern.multiline ? 'm' : ''); |
| 25 if (harmony_regexps) |
| 26 flags += (pattern.sticky ? 'y' : ''); |
| 25 pattern = pattern.source; | 27 pattern = pattern.source; |
| 26 } | 28 } |
| 27 | 29 |
| 28 pattern = IS_UNDEFINED(pattern) ? '' : ToString(pattern); | 30 pattern = IS_UNDEFINED(pattern) ? '' : ToString(pattern); |
| 29 flags = IS_UNDEFINED(flags) ? '' : ToString(flags); | 31 flags = IS_UNDEFINED(flags) ? '' : ToString(flags); |
| 30 | 32 |
| 31 var global = false; | 33 var global = false; |
| 32 var ignoreCase = false; | 34 var ignoreCase = false; |
| 33 var multiline = false; | 35 var multiline = false; |
| 36 var sticky = false; |
| 34 for (var i = 0; i < flags.length; i++) { | 37 for (var i = 0; i < flags.length; i++) { |
| 35 var c = %_CallFunction(flags, i, StringCharAt); | 38 var c = %_CallFunction(flags, i, StringCharAt); |
| 36 switch (c) { | 39 switch (c) { |
| 37 case 'g': | 40 case 'g': |
| 38 if (global) { | 41 if (global) { |
| 39 throw MakeSyntaxError("invalid_regexp_flags", [flags]); | 42 throw MakeSyntaxError("invalid_regexp_flags", [flags]); |
| 40 } | 43 } |
| 41 global = true; | 44 global = true; |
| 42 break; | 45 break; |
| 43 case 'i': | 46 case 'i': |
| 44 if (ignoreCase) { | 47 if (ignoreCase) { |
| 45 throw MakeSyntaxError("invalid_regexp_flags", [flags]); | 48 throw MakeSyntaxError("invalid_regexp_flags", [flags]); |
| 46 } | 49 } |
| 47 ignoreCase = true; | 50 ignoreCase = true; |
| 48 break; | 51 break; |
| 49 case 'm': | 52 case 'm': |
| 50 if (multiline) { | 53 if (multiline) { |
| 51 throw MakeSyntaxError("invalid_regexp_flags", [flags]); | 54 throw MakeSyntaxError("invalid_regexp_flags", [flags]); |
| 52 } | 55 } |
| 53 multiline = true; | 56 multiline = true; |
| 54 break; | 57 break; |
| 58 case 'y': |
| 59 if (!harmony_regexps || sticky) { |
| 60 throw MakeSyntaxError("invalid_regexp_flags", [flags]); |
| 61 } |
| 62 sticky = true; |
| 63 break; |
| 55 default: | 64 default: |
| 56 throw MakeSyntaxError("invalid_regexp_flags", [flags]); | 65 throw MakeSyntaxError("invalid_regexp_flags", [flags]); |
| 57 } | 66 } |
| 58 } | 67 } |
| 59 | 68 |
| 60 %RegExpInitializeObject(object, pattern, global, ignoreCase, multiline); | 69 %RegExpInitializeObject(object, pattern, global, ignoreCase, multiline, sticky
); |
| 61 | 70 |
| 62 // Call internal function to compile the pattern. | 71 // Call internal function to compile the pattern. |
| 63 %RegExpCompile(object, pattern, flags); | 72 %RegExpCompile(object, pattern, flags); |
| 64 } | 73 } |
| 65 | 74 |
| 66 | 75 |
| 67 function RegExpConstructor(pattern, flags) { | 76 function RegExpConstructor(pattern, flags) { |
| 68 if (%_IsConstructCall()) { | 77 if (%_IsConstructCall()) { |
| 69 DoConstructRegExp(this, pattern, flags); | 78 DoConstructRegExp(this, pattern, flags); |
| 70 } else { | 79 } else { |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 152 ['RegExp.prototype.exec', this]); | 161 ['RegExp.prototype.exec', this]); |
| 153 } | 162 } |
| 154 | 163 |
| 155 string = TO_STRING_INLINE(string); | 164 string = TO_STRING_INLINE(string); |
| 156 var lastIndex = this.lastIndex; | 165 var lastIndex = this.lastIndex; |
| 157 | 166 |
| 158 // Conversion is required by the ES5 specification (RegExp.prototype.exec | 167 // Conversion is required by the ES5 specification (RegExp.prototype.exec |
| 159 // algorithm, step 5) even if the value is discarded for non-global RegExps. | 168 // algorithm, step 5) even if the value is discarded for non-global RegExps. |
| 160 var i = TO_INTEGER(lastIndex); | 169 var i = TO_INTEGER(lastIndex); |
| 161 | 170 |
| 162 var global = this.global; | 171 var updateLastIndex = this.global || (harmony_regexps && this.sticky); |
| 163 if (global) { | 172 if (updateLastIndex) { |
| 164 if (i < 0 || i > string.length) { | 173 if (i < 0 || i > string.length) { |
| 165 this.lastIndex = 0; | 174 this.lastIndex = 0; |
| 166 return null; | 175 return null; |
| 167 } | 176 } |
| 168 } else { | 177 } else { |
| 169 i = 0; | 178 i = 0; |
| 170 } | 179 } |
| 171 | 180 |
| 172 // matchIndices is either null or the lastMatchInfo array. | 181 // matchIndices is either null or the lastMatchInfo array. |
| 173 var matchIndices = %_RegExpExec(this, string, i, lastMatchInfo); | 182 var matchIndices = %_RegExpExec(this, string, i, lastMatchInfo); |
| 174 | 183 |
| 175 if (IS_NULL(matchIndices)) { | 184 if (IS_NULL(matchIndices)) { |
| 176 this.lastIndex = 0; | 185 this.lastIndex = 0; |
| 177 return null; | 186 return null; |
| 178 } | 187 } |
| 179 | 188 |
| 180 // Successful match. | 189 // Successful match. |
| 181 lastMatchInfoOverride = null; | 190 lastMatchInfoOverride = null; |
| 182 if (global) { | 191 if (updateLastIndex) { |
| 183 this.lastIndex = lastMatchInfo[CAPTURE1]; | 192 this.lastIndex = lastMatchInfo[CAPTURE1]; |
| 184 } | 193 } |
| 185 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchIndices, string); | 194 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchIndices, string); |
| 186 } | 195 } |
| 187 | 196 |
| 188 | 197 |
| 189 // One-element cache for the simplified test regexp. | 198 // One-element cache for the simplified test regexp. |
| 190 var regexp_key; | 199 var regexp_key; |
| 191 var regexp_val; | 200 var regexp_val; |
| 192 | 201 |
| 193 // Section 15.10.6.3 doesn't actually make sense, but the intention seems to be | 202 // Section 15.10.6.3 doesn't actually make sense, but the intention seems to be |
| 194 // that test is defined in terms of String.prototype.exec. However, it probably | 203 // that test is defined in terms of String.prototype.exec. However, it probably |
| 195 // means the original value of String.prototype.exec, which is what everybody | 204 // means the original value of String.prototype.exec, which is what everybody |
| 196 // else implements. | 205 // else implements. |
| 197 function RegExpTest(string) { | 206 function RegExpTest(string) { |
| 198 if (!IS_REGEXP(this)) { | 207 if (!IS_REGEXP(this)) { |
| 199 throw MakeTypeError('incompatible_method_receiver', | 208 throw MakeTypeError('incompatible_method_receiver', |
| 200 ['RegExp.prototype.test', this]); | 209 ['RegExp.prototype.test', this]); |
| 201 } | 210 } |
| 202 string = TO_STRING_INLINE(string); | 211 string = TO_STRING_INLINE(string); |
| 203 | 212 |
| 204 var lastIndex = this.lastIndex; | 213 var lastIndex = this.lastIndex; |
| 205 | 214 |
| 206 // Conversion is required by the ES5 specification (RegExp.prototype.exec | 215 // Conversion is required by the ES5 specification (RegExp.prototype.exec |
| 207 // algorithm, step 5) even if the value is discarded for non-global RegExps. | 216 // algorithm, step 5) even if the value is discarded for non-global RegExps. |
| 208 var i = TO_INTEGER(lastIndex); | 217 var i = TO_INTEGER(lastIndex); |
| 209 | 218 |
| 210 if (this.global) { | 219 if (this.global || (harmony_regexps && this.sticky)) { |
| 211 if (i < 0 || i > string.length) { | 220 if (i < 0 || i > string.length) { |
| 212 this.lastIndex = 0; | 221 this.lastIndex = 0; |
| 213 return false; | 222 return false; |
| 214 } | 223 } |
| 215 // matchIndices is either null or the lastMatchInfo array. | 224 // matchIndices is either null or the lastMatchInfo array. |
| 216 var matchIndices = %_RegExpExec(this, string, i, lastMatchInfo); | 225 var matchIndices = %_RegExpExec(this, string, i, lastMatchInfo); |
| 217 if (IS_NULL(matchIndices)) { | 226 if (IS_NULL(matchIndices)) { |
| 218 this.lastIndex = 0; | 227 this.lastIndex = 0; |
| 219 return false; | 228 return false; |
| 220 } | 229 } |
| 221 lastMatchInfoOverride = null; | 230 lastMatchInfoOverride = null; |
| 222 this.lastIndex = lastMatchInfo[CAPTURE1]; | 231 this.lastIndex = lastMatchInfo[CAPTURE1]; |
| 223 return true; | 232 return true; |
| 224 } else { | 233 } else { |
| 225 // Non-global regexp. | 234 // Non-global, non-sticky regexp. |
| 226 // Remove irrelevant preceeding '.*' in a non-global test regexp. | 235 // Remove irrelevant preceeding '.*' in a test regexp. The expression |
| 227 // The expression checks whether this.source starts with '.*' and | 236 // checks whether this.source starts with '.*' and that the third char is |
| 228 // that the third char is not a '?'. | 237 // not a '?'. But see https://code.google.com/p/v8/issues/detail?id=3560 |
| 229 var regexp = this; | 238 var regexp = this; |
| 230 if (%_StringCharCodeAt(regexp.source, 0) == 46 && // '.' | 239 if (regexp.source.length >= 3 && |
| 240 %_StringCharCodeAt(regexp.source, 0) == 46 && // '.' |
| 231 %_StringCharCodeAt(regexp.source, 1) == 42 && // '*' | 241 %_StringCharCodeAt(regexp.source, 1) == 42 && // '*' |
| 232 %_StringCharCodeAt(regexp.source, 2) != 63) { // '?' | 242 %_StringCharCodeAt(regexp.source, 2) != 63) { // '?' |
| 233 regexp = TrimRegExp(regexp); | 243 regexp = TrimRegExp(regexp); |
| 234 } | 244 } |
| 235 // matchIndices is either null or the lastMatchInfo array. | 245 // matchIndices is either null or the lastMatchInfo array. |
| 236 var matchIndices = %_RegExpExec(regexp, string, 0, lastMatchInfo); | 246 var matchIndices = %_RegExpExec(regexp, string, 0, lastMatchInfo); |
| 237 if (IS_NULL(matchIndices)) { | 247 if (IS_NULL(matchIndices)) { |
| 238 this.lastIndex = 0; | 248 this.lastIndex = 0; |
| 239 return false; | 249 return false; |
| 240 } | 250 } |
| (...skipping 16 matching lines...) Expand all Loading... |
| 257 | 267 |
| 258 function RegExpToString() { | 268 function RegExpToString() { |
| 259 if (!IS_REGEXP(this)) { | 269 if (!IS_REGEXP(this)) { |
| 260 throw MakeTypeError('incompatible_method_receiver', | 270 throw MakeTypeError('incompatible_method_receiver', |
| 261 ['RegExp.prototype.toString', this]); | 271 ['RegExp.prototype.toString', this]); |
| 262 } | 272 } |
| 263 var result = '/' + this.source + '/'; | 273 var result = '/' + this.source + '/'; |
| 264 if (this.global) result += 'g'; | 274 if (this.global) result += 'g'; |
| 265 if (this.ignoreCase) result += 'i'; | 275 if (this.ignoreCase) result += 'i'; |
| 266 if (this.multiline) result += 'm'; | 276 if (this.multiline) result += 'm'; |
| 277 if (harmony_regexps && this.sticky) result += 'y'; |
| 267 return result; | 278 return result; |
| 268 } | 279 } |
| 269 | 280 |
| 270 | 281 |
| 271 // Getters for the static properties lastMatch, lastParen, leftContext, and | 282 // Getters for the static properties lastMatch, lastParen, leftContext, and |
| 272 // rightContext of the RegExp constructor. The properties are computed based | 283 // rightContext of the RegExp constructor. The properties are computed based |
| 273 // on the captures array of the last successful match and the subject string | 284 // on the captures array of the last successful match and the subject string |
| 274 // of the last successful match. | 285 // of the last successful match. |
| 275 function RegExpGetLastMatch() { | 286 function RegExpGetLastMatch() { |
| 276 if (lastMatchInfoOverride !== null) { | 287 if (lastMatchInfoOverride !== null) { |
| (...skipping 178 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 455 | 466 |
| 456 for (var i = 1; i < 10; ++i) { | 467 for (var i = 1; i < 10; ++i) { |
| 457 %DefineAccessorPropertyUnchecked($RegExp, '$' + i, | 468 %DefineAccessorPropertyUnchecked($RegExp, '$' + i, |
| 458 RegExpMakeCaptureGetter(i), NoOpSetter, | 469 RegExpMakeCaptureGetter(i), NoOpSetter, |
| 459 DONT_DELETE); | 470 DONT_DELETE); |
| 460 } | 471 } |
| 461 %ToFastProperties($RegExp); | 472 %ToFastProperties($RegExp); |
| 462 } | 473 } |
| 463 | 474 |
| 464 SetUpRegExp(); | 475 SetUpRegExp(); |
| OLD | NEW |