| 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 var $regexpLastMatchInfoOverride; | |
| 6 | |
| 7 (function(global, utils) { | 5 (function(global, utils) { |
| 8 | 6 |
| 9 %CheckIsBootstrapping(); | 7 %CheckIsBootstrapping(); |
| 10 | 8 |
| 11 // ------------------------------------------------------------------- | 9 // ------------------------------------------------------------------- |
| 12 // Imports | 10 // Imports |
| 13 | 11 |
| 14 var FLAG_harmony_regexps; | 12 var FLAG_harmony_regexps; |
| 15 var FLAG_harmony_tolength; | 13 var FLAG_harmony_tolength; |
| 16 var FLAG_harmony_unicode_regexps; | 14 var FLAG_harmony_unicode_regexps; |
| (...skipping 20 matching lines...) Expand all Loading... |
| 37 // that there are at least two capture indeces. The array also contains | 35 // that there are at least two capture indeces. The array also contains |
| 38 // the subject string for the last successful match. | 36 // the subject string for the last successful match. |
| 39 var RegExpLastMatchInfo = new InternalPackedArray( | 37 var RegExpLastMatchInfo = new InternalPackedArray( |
| 40 2, // REGEXP_NUMBER_OF_CAPTURES | 38 2, // REGEXP_NUMBER_OF_CAPTURES |
| 41 "", // Last subject. | 39 "", // Last subject. |
| 42 UNDEFINED, // Last input - settable with RegExpSetInput. | 40 UNDEFINED, // Last input - settable with RegExpSetInput. |
| 43 0, // REGEXP_FIRST_CAPTURE + 0 | 41 0, // REGEXP_FIRST_CAPTURE + 0 |
| 44 0 // REGEXP_FIRST_CAPTURE + 1 | 42 0 // REGEXP_FIRST_CAPTURE + 1 |
| 45 ); | 43 ); |
| 46 | 44 |
| 47 // Override last match info with an array of actual substrings. | |
| 48 // Used internally by replace regexp with function. | |
| 49 // The array has the format of an "apply" argument for a replacement | |
| 50 // function. | |
| 51 $regexpLastMatchInfoOverride = null; | |
| 52 | |
| 53 // ------------------------------------------------------------------- | 45 // ------------------------------------------------------------------- |
| 54 | 46 |
| 55 // A recursive descent parser for Patterns according to the grammar of | 47 // A recursive descent parser for Patterns according to the grammar of |
| 56 // ECMA-262 15.10.1, with deviations noted below. | 48 // ECMA-262 15.10.1, with deviations noted below. |
| 57 function DoConstructRegExp(object, pattern, flags) { | 49 function DoConstructRegExp(object, pattern, flags) { |
| 58 // RegExp : Called as constructor; see ECMA-262, section 15.10.4. | 50 // RegExp : Called as constructor; see ECMA-262, section 15.10.4. |
| 59 if (IS_REGEXP(pattern)) { | 51 if (IS_REGEXP(pattern)) { |
| 60 if (!IS_UNDEFINED(flags)) throw MakeTypeError(kRegExpFlags); | 52 if (!IS_UNDEFINED(flags)) throw MakeTypeError(kRegExpFlags); |
| 61 flags = (pattern.global ? 'g' : '') | 53 flags = (pattern.global ? 'g' : '') |
| 62 + (pattern.ignoreCase ? 'i' : '') | 54 + (pattern.ignoreCase ? 'i' : '') |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 107 if (IS_UNDEFINED(pattern) && %_ArgumentsLength() != 0) { | 99 if (IS_UNDEFINED(pattern) && %_ArgumentsLength() != 0) { |
| 108 DoConstructRegExp(this, 'undefined', flags); | 100 DoConstructRegExp(this, 'undefined', flags); |
| 109 } else { | 101 } else { |
| 110 DoConstructRegExp(this, pattern, flags); | 102 DoConstructRegExp(this, pattern, flags); |
| 111 } | 103 } |
| 112 } | 104 } |
| 113 | 105 |
| 114 | 106 |
| 115 function DoRegExpExec(regexp, string, index) { | 107 function DoRegExpExec(regexp, string, index) { |
| 116 var result = %_RegExpExec(regexp, string, index, RegExpLastMatchInfo); | 108 var result = %_RegExpExec(regexp, string, index, RegExpLastMatchInfo); |
| 117 if (result !== null) $regexpLastMatchInfoOverride = null; | |
| 118 return result; | 109 return result; |
| 119 } | 110 } |
| 120 | 111 |
| 121 | 112 |
| 122 // This is kind of performance sensitive, so we want to avoid unnecessary | 113 // This is kind of performance sensitive, so we want to avoid unnecessary |
| 123 // type checks on inputs. But we also don't want to inline it several times | 114 // type checks on inputs. But we also don't want to inline it several times |
| 124 // manually, so we use a macro :-) | 115 // manually, so we use a macro :-) |
| 125 macro RETURN_NEW_RESULT_FROM_MATCH_INFO(MATCHINFO, STRING) | 116 macro RETURN_NEW_RESULT_FROM_MATCH_INFO(MATCHINFO, STRING) |
| 126 var numResults = NUMBER_OF_CAPTURES(MATCHINFO) >> 1; | 117 var numResults = NUMBER_OF_CAPTURES(MATCHINFO) >> 1; |
| 127 var start = MATCHINFO[CAPTURE0]; | 118 var start = MATCHINFO[CAPTURE0]; |
| (...skipping 14 matching lines...) Expand all Loading... |
| 142 j++; | 133 j++; |
| 143 } | 134 } |
| 144 return result; | 135 return result; |
| 145 endmacro | 136 endmacro |
| 146 | 137 |
| 147 | 138 |
| 148 function RegExpExecNoTests(regexp, string, start) { | 139 function RegExpExecNoTests(regexp, string, start) { |
| 149 // Must be called with RegExp, string and positive integer as arguments. | 140 // Must be called with RegExp, string and positive integer as arguments. |
| 150 var matchInfo = %_RegExpExec(regexp, string, start, RegExpLastMatchInfo); | 141 var matchInfo = %_RegExpExec(regexp, string, start, RegExpLastMatchInfo); |
| 151 if (matchInfo !== null) { | 142 if (matchInfo !== null) { |
| 152 $regexpLastMatchInfoOverride = null; | |
| 153 // ES6 21.2.5.2.2 step 18. | 143 // ES6 21.2.5.2.2 step 18. |
| 154 if (FLAG_harmony_regexps && regexp.sticky) { | 144 if (FLAG_harmony_regexps && regexp.sticky) { |
| 155 regexp.lastIndex = matchInfo[CAPTURE1]; | 145 regexp.lastIndex = matchInfo[CAPTURE1]; |
| 156 } | 146 } |
| 157 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchInfo, string); | 147 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchInfo, string); |
| 158 } | 148 } |
| 159 regexp.lastIndex = 0; | 149 regexp.lastIndex = 0; |
| 160 return null; | 150 return null; |
| 161 } | 151 } |
| 162 | 152 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 186 | 176 |
| 187 // matchIndices is either null or the RegExpLastMatchInfo array. | 177 // matchIndices is either null or the RegExpLastMatchInfo array. |
| 188 var matchIndices = %_RegExpExec(this, string, i, RegExpLastMatchInfo); | 178 var matchIndices = %_RegExpExec(this, string, i, RegExpLastMatchInfo); |
| 189 | 179 |
| 190 if (IS_NULL(matchIndices)) { | 180 if (IS_NULL(matchIndices)) { |
| 191 this.lastIndex = 0; | 181 this.lastIndex = 0; |
| 192 return null; | 182 return null; |
| 193 } | 183 } |
| 194 | 184 |
| 195 // Successful match. | 185 // Successful match. |
| 196 $regexpLastMatchInfoOverride = null; | |
| 197 if (updateLastIndex) { | 186 if (updateLastIndex) { |
| 198 this.lastIndex = RegExpLastMatchInfo[CAPTURE1]; | 187 this.lastIndex = RegExpLastMatchInfo[CAPTURE1]; |
| 199 } | 188 } |
| 200 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchIndices, string); | 189 RETURN_NEW_RESULT_FROM_MATCH_INFO(matchIndices, string); |
| 201 } | 190 } |
| 202 | 191 |
| 203 | 192 |
| 204 // One-element cache for the simplified test regexp. | 193 // One-element cache for the simplified test regexp. |
| 205 var regexp_key; | 194 var regexp_key; |
| 206 var regexp_val; | 195 var regexp_val; |
| (...skipping 19 matching lines...) Expand all Loading... |
| 226 if (i < 0 || i > string.length) { | 215 if (i < 0 || i > string.length) { |
| 227 this.lastIndex = 0; | 216 this.lastIndex = 0; |
| 228 return false; | 217 return false; |
| 229 } | 218 } |
| 230 // matchIndices is either null or the RegExpLastMatchInfo array. | 219 // matchIndices is either null or the RegExpLastMatchInfo array. |
| 231 var matchIndices = %_RegExpExec(this, string, i, RegExpLastMatchInfo); | 220 var matchIndices = %_RegExpExec(this, string, i, RegExpLastMatchInfo); |
| 232 if (IS_NULL(matchIndices)) { | 221 if (IS_NULL(matchIndices)) { |
| 233 this.lastIndex = 0; | 222 this.lastIndex = 0; |
| 234 return false; | 223 return false; |
| 235 } | 224 } |
| 236 $regexpLastMatchInfoOverride = null; | |
| 237 this.lastIndex = RegExpLastMatchInfo[CAPTURE1]; | 225 this.lastIndex = RegExpLastMatchInfo[CAPTURE1]; |
| 238 return true; | 226 return true; |
| 239 } else { | 227 } else { |
| 240 // Non-global, non-sticky regexp. | 228 // Non-global, non-sticky regexp. |
| 241 // Remove irrelevant preceeding '.*' in a test regexp. The expression | 229 // Remove irrelevant preceeding '.*' in a test regexp. The expression |
| 242 // checks whether this.source starts with '.*' and that the third char is | 230 // checks whether this.source starts with '.*' and that the third char is |
| 243 // not a '?'. But see https://code.google.com/p/v8/issues/detail?id=3560 | 231 // not a '?'. But see https://code.google.com/p/v8/issues/detail?id=3560 |
| 244 var regexp = this; | 232 var regexp = this; |
| 245 if (regexp.source.length >= 3 && | 233 if (regexp.source.length >= 3 && |
| 246 %_StringCharCodeAt(regexp.source, 0) == 46 && // '.' | 234 %_StringCharCodeAt(regexp.source, 0) == 46 && // '.' |
| 247 %_StringCharCodeAt(regexp.source, 1) == 42 && // '*' | 235 %_StringCharCodeAt(regexp.source, 1) == 42 && // '*' |
| 248 %_StringCharCodeAt(regexp.source, 2) != 63) { // '?' | 236 %_StringCharCodeAt(regexp.source, 2) != 63) { // '?' |
| 249 regexp = TrimRegExp(regexp); | 237 regexp = TrimRegExp(regexp); |
| 250 } | 238 } |
| 251 // matchIndices is either null or the RegExpLastMatchInfo array. | 239 // matchIndices is either null or the RegExpLastMatchInfo array. |
| 252 var matchIndices = %_RegExpExec(regexp, string, 0, RegExpLastMatchInfo); | 240 var matchIndices = %_RegExpExec(regexp, string, 0, RegExpLastMatchInfo); |
| 253 if (IS_NULL(matchIndices)) { | 241 if (IS_NULL(matchIndices)) { |
| 254 this.lastIndex = 0; | 242 this.lastIndex = 0; |
| 255 return false; | 243 return false; |
| 256 } | 244 } |
| 257 $regexpLastMatchInfoOverride = null; | |
| 258 return true; | 245 return true; |
| 259 } | 246 } |
| 260 } | 247 } |
| 261 | 248 |
| 262 function TrimRegExp(regexp) { | 249 function TrimRegExp(regexp) { |
| 263 if (!%_ObjectEquals(regexp_key, regexp)) { | 250 if (!%_ObjectEquals(regexp_key, regexp)) { |
| 264 regexp_key = regexp; | 251 regexp_key = regexp; |
| 265 regexp_val = | 252 regexp_val = |
| 266 new GlobalRegExp(%_SubString(regexp.source, 2, regexp.source.length), | 253 new GlobalRegExp(%_SubString(regexp.source, 2, regexp.source.length), |
| 267 (regexp.ignoreCase ? regexp.multiline ? "im" : "i" | 254 (regexp.ignoreCase ? regexp.multiline ? "im" : "i" |
| (...skipping 16 matching lines...) Expand all Loading... |
| 284 if (FLAG_harmony_regexps && this.sticky) result += 'y'; | 271 if (FLAG_harmony_regexps && this.sticky) result += 'y'; |
| 285 return result; | 272 return result; |
| 286 } | 273 } |
| 287 | 274 |
| 288 | 275 |
| 289 // Getters for the static properties lastMatch, lastParen, leftContext, and | 276 // Getters for the static properties lastMatch, lastParen, leftContext, and |
| 290 // rightContext of the RegExp constructor. The properties are computed based | 277 // rightContext of the RegExp constructor. The properties are computed based |
| 291 // on the captures array of the last successful match and the subject string | 278 // on the captures array of the last successful match and the subject string |
| 292 // of the last successful match. | 279 // of the last successful match. |
| 293 function RegExpGetLastMatch() { | 280 function RegExpGetLastMatch() { |
| 294 if ($regexpLastMatchInfoOverride !== null) { | |
| 295 return OVERRIDE_MATCH($regexpLastMatchInfoOverride); | |
| 296 } | |
| 297 var regExpSubject = LAST_SUBJECT(RegExpLastMatchInfo); | 281 var regExpSubject = LAST_SUBJECT(RegExpLastMatchInfo); |
| 298 return %_SubString(regExpSubject, | 282 return %_SubString(regExpSubject, |
| 299 RegExpLastMatchInfo[CAPTURE0], | 283 RegExpLastMatchInfo[CAPTURE0], |
| 300 RegExpLastMatchInfo[CAPTURE1]); | 284 RegExpLastMatchInfo[CAPTURE1]); |
| 301 } | 285 } |
| 302 | 286 |
| 303 | 287 |
| 304 function RegExpGetLastParen() { | 288 function RegExpGetLastParen() { |
| 305 if ($regexpLastMatchInfoOverride) { | |
| 306 var override = $regexpLastMatchInfoOverride; | |
| 307 if (override.length <= 3) return ''; | |
| 308 return override[override.length - 3]; | |
| 309 } | |
| 310 var length = NUMBER_OF_CAPTURES(RegExpLastMatchInfo); | 289 var length = NUMBER_OF_CAPTURES(RegExpLastMatchInfo); |
| 311 if (length <= 2) return ''; // There were no captures. | 290 if (length <= 2) return ''; // There were no captures. |
| 312 // We match the SpiderMonkey behavior: return the substring defined by the | 291 // We match the SpiderMonkey behavior: return the substring defined by the |
| 313 // last pair (after the first pair) of elements of the capture array even if | 292 // last pair (after the first pair) of elements of the capture array even if |
| 314 // it is empty. | 293 // it is empty. |
| 315 var regExpSubject = LAST_SUBJECT(RegExpLastMatchInfo); | 294 var regExpSubject = LAST_SUBJECT(RegExpLastMatchInfo); |
| 316 var start = RegExpLastMatchInfo[CAPTURE(length - 2)]; | 295 var start = RegExpLastMatchInfo[CAPTURE(length - 2)]; |
| 317 var end = RegExpLastMatchInfo[CAPTURE(length - 1)]; | 296 var end = RegExpLastMatchInfo[CAPTURE(length - 1)]; |
| 318 if (start != -1 && end != -1) { | 297 if (start != -1 && end != -1) { |
| 319 return %_SubString(regExpSubject, start, end); | 298 return %_SubString(regExpSubject, start, end); |
| 320 } | 299 } |
| 321 return ""; | 300 return ""; |
| 322 } | 301 } |
| 323 | 302 |
| 324 | 303 |
| 325 function RegExpGetLeftContext() { | 304 function RegExpGetLeftContext() { |
| 326 var start_index; | 305 var start_index; |
| 327 var subject; | 306 var subject; |
| 328 if (!$regexpLastMatchInfoOverride) { | 307 start_index = RegExpLastMatchInfo[CAPTURE0]; |
| 329 start_index = RegExpLastMatchInfo[CAPTURE0]; | 308 subject = LAST_SUBJECT(RegExpLastMatchInfo); |
| 330 subject = LAST_SUBJECT(RegExpLastMatchInfo); | |
| 331 } else { | |
| 332 var override = $regexpLastMatchInfoOverride; | |
| 333 start_index = OVERRIDE_POS(override); | |
| 334 subject = OVERRIDE_SUBJECT(override); | |
| 335 } | |
| 336 return %_SubString(subject, 0, start_index); | 309 return %_SubString(subject, 0, start_index); |
| 337 } | 310 } |
| 338 | 311 |
| 339 | 312 |
| 340 function RegExpGetRightContext() { | 313 function RegExpGetRightContext() { |
| 341 var start_index; | 314 var start_index; |
| 342 var subject; | 315 var subject; |
| 343 if (!$regexpLastMatchInfoOverride) { | 316 start_index = RegExpLastMatchInfo[CAPTURE1]; |
| 344 start_index = RegExpLastMatchInfo[CAPTURE1]; | 317 subject = LAST_SUBJECT(RegExpLastMatchInfo); |
| 345 subject = LAST_SUBJECT(RegExpLastMatchInfo); | |
| 346 } else { | |
| 347 var override = $regexpLastMatchInfoOverride; | |
| 348 subject = OVERRIDE_SUBJECT(override); | |
| 349 var match = OVERRIDE_MATCH(override); | |
| 350 start_index = OVERRIDE_POS(override) + match.length; | |
| 351 } | |
| 352 return %_SubString(subject, start_index, subject.length); | 318 return %_SubString(subject, start_index, subject.length); |
| 353 } | 319 } |
| 354 | 320 |
| 355 | 321 |
| 356 // The properties $1..$9 are the first nine capturing substrings of the last | 322 // The properties $1..$9 are the first nine capturing substrings of the last |
| 357 // successful match, or ''. The function RegExpMakeCaptureGetter will be | 323 // successful match, or ''. The function RegExpMakeCaptureGetter will be |
| 358 // called with indices from 1 to 9. | 324 // called with indices from 1 to 9. |
| 359 function RegExpMakeCaptureGetter(n) { | 325 function RegExpMakeCaptureGetter(n) { |
| 360 return function foo() { | 326 return function foo() { |
| 361 if ($regexpLastMatchInfoOverride) { | |
| 362 if (n < $regexpLastMatchInfoOverride.length - 2) { | |
| 363 return OVERRIDE_CAPTURE($regexpLastMatchInfoOverride, n); | |
| 364 } | |
| 365 return ''; | |
| 366 } | |
| 367 var index = n * 2; | 327 var index = n * 2; |
| 368 if (index >= NUMBER_OF_CAPTURES(RegExpLastMatchInfo)) return ''; | 328 if (index >= NUMBER_OF_CAPTURES(RegExpLastMatchInfo)) return ''; |
| 369 var matchStart = RegExpLastMatchInfo[CAPTURE(index)]; | 329 var matchStart = RegExpLastMatchInfo[CAPTURE(index)]; |
| 370 var matchEnd = RegExpLastMatchInfo[CAPTURE(index + 1)]; | 330 var matchEnd = RegExpLastMatchInfo[CAPTURE(index + 1)]; |
| 371 if (matchStart == -1 || matchEnd == -1) return ''; | 331 if (matchStart == -1 || matchEnd == -1) return ''; |
| 372 return %_SubString(LAST_SUBJECT(RegExpLastMatchInfo), matchStart, matchEnd); | 332 return %_SubString(LAST_SUBJECT(RegExpLastMatchInfo), matchStart, matchEnd); |
| 373 }; | 333 }; |
| 374 } | 334 } |
| 375 | 335 |
| 376 // ------------------------------------------------------------------- | 336 // ------------------------------------------------------------------- |
| (...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 460 // Exports | 420 // Exports |
| 461 | 421 |
| 462 utils.Export(function(to) { | 422 utils.Export(function(to) { |
| 463 to.RegExpExec = DoRegExpExec; | 423 to.RegExpExec = DoRegExpExec; |
| 464 to.RegExpExecNoTests = RegExpExecNoTests; | 424 to.RegExpExecNoTests = RegExpExecNoTests; |
| 465 to.RegExpLastMatchInfo = RegExpLastMatchInfo; | 425 to.RegExpLastMatchInfo = RegExpLastMatchInfo; |
| 466 to.RegExpTest = RegExpTest; | 426 to.RegExpTest = RegExpTest; |
| 467 }); | 427 }); |
| 468 | 428 |
| 469 }) | 429 }) |
| OLD | NEW |