OLD | NEW |
1 // Copyright 2006-2009 the V8 project authors. All rights reserved. | 1 // Copyright 2006-2009 the V8 project authors. All rights reserved. |
2 // Redistribution and use in source and binary forms, with or without | 2 // Redistribution and use in source and binary forms, with or without |
3 // modification, are permitted provided that the following conditions are | 3 // modification, are permitted provided that the following conditions are |
4 // met: | 4 // met: |
5 // | 5 // |
6 // * Redistributions of source code must retain the above copyright | 6 // * Redistributions of source code must retain the above copyright |
7 // notice, this list of conditions and the following disclaimer. | 7 // notice, this list of conditions and the following disclaimer. |
8 // * Redistributions in binary form must reproduce the above | 8 // * Redistributions in binary form must reproduce the above |
9 // copyright notice, this list of conditions and the following | 9 // copyright notice, this list of conditions and the following |
10 // disclaimer in the documentation and/or other materials provided | 10 // disclaimer in the documentation and/or other materials provided |
(...skipping 223 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
234 | 234 |
235 // ECMA-262, section 15.5.4.11 | 235 // ECMA-262, section 15.5.4.11 |
236 function StringReplace(search, replace) { | 236 function StringReplace(search, replace) { |
237 var subject = TO_STRING_INLINE(this); | 237 var subject = TO_STRING_INLINE(this); |
238 | 238 |
239 // Delegate to one of the regular expression variants if necessary. | 239 // Delegate to one of the regular expression variants if necessary. |
240 if (IS_REGEXP(search)) { | 240 if (IS_REGEXP(search)) { |
241 %_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]); | 241 %_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]); |
242 if (IS_FUNCTION(replace)) { | 242 if (IS_FUNCTION(replace)) { |
243 regExpCache.type = 'none'; | 243 regExpCache.type = 'none'; |
244 return StringReplaceRegExpWithFunction(subject, search, replace); | 244 if (search.global) { |
| 245 return StringReplaceGlobalRegExpWithFunction(subject, search, replace); |
| 246 } else { |
| 247 return StringReplaceNonGlobalRegExpWithFunction(subject, |
| 248 search, |
| 249 replace); |
| 250 } |
245 } else { | 251 } else { |
246 return StringReplaceRegExp(subject, search, replace); | 252 return StringReplaceRegExp(subject, search, replace); |
247 } | 253 } |
248 } | 254 } |
249 | 255 |
250 // Convert the search argument to a string and search for it. | 256 // Convert the search argument to a string and search for it. |
251 search = TO_STRING_INLINE(search); | 257 search = TO_STRING_INLINE(search); |
252 var start = %StringIndexOf(subject, search, 0); | 258 var start = %StringIndexOf(subject, search, 0); |
253 if (start < 0) return subject; | 259 if (start < 0) return subject; |
254 var end = start + search.length; | 260 var end = start + search.length; |
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
389 } | 395 } |
390 }; | 396 }; |
391 | 397 |
392 | 398 |
393 // Compute the string of a given regular expression capture. | 399 // Compute the string of a given regular expression capture. |
394 function CaptureString(string, lastCaptureInfo, index) { | 400 function CaptureString(string, lastCaptureInfo, index) { |
395 // Scale the index. | 401 // Scale the index. |
396 var scaled = index << 1; | 402 var scaled = index << 1; |
397 // Compute start and end. | 403 // Compute start and end. |
398 var start = lastCaptureInfo[CAPTURE(scaled)]; | 404 var start = lastCaptureInfo[CAPTURE(scaled)]; |
| 405 // If start isn't valid, return undefined. |
| 406 if (start < 0) return; |
399 var end = lastCaptureInfo[CAPTURE(scaled + 1)]; | 407 var end = lastCaptureInfo[CAPTURE(scaled + 1)]; |
400 // If either start or end is missing return undefined. | |
401 if (start < 0 || end < 0) return; | |
402 return SubString(string, start, end); | 408 return SubString(string, start, end); |
403 }; | 409 }; |
404 | 410 |
405 | 411 |
406 // Add the string of a given regular expression capture to the | 412 // Add the string of a given regular expression capture to the |
407 // ReplaceResultBuilder | 413 // ReplaceResultBuilder |
408 function addCaptureString(builder, matchInfo, index) { | 414 function addCaptureString(builder, matchInfo, index) { |
409 // Scale the index. | 415 // Scale the index. |
410 var scaled = index << 1; | 416 var scaled = index << 1; |
411 // Compute start and end. | 417 // Compute start and end. |
412 var start = matchInfo[CAPTURE(scaled)]; | 418 var start = matchInfo[CAPTURE(scaled)]; |
| 419 if (start < 0) return; |
413 var end = matchInfo[CAPTURE(scaled + 1)]; | 420 var end = matchInfo[CAPTURE(scaled + 1)]; |
414 // If either start or end is missing return. | |
415 if (start < 0 || end <= start) return; | |
416 builder.addSpecialSlice(start, end); | 421 builder.addSpecialSlice(start, end); |
417 }; | 422 }; |
418 | 423 |
419 // TODO(lrn): This array will survive indefinitely if replace is never | 424 // TODO(lrn): This array will survive indefinitely if replace is never |
420 // called again. However, it will be empty, since the contents are cleared | 425 // called again. However, it will be empty, since the contents are cleared |
421 // in the finally block. | 426 // in the finally block. |
422 var reusableReplaceArray = $Array(16); | 427 var reusableReplaceArray = $Array(16); |
423 | 428 |
424 // Helper function for replacing regular expressions with the result of a | 429 // Helper function for replacing regular expressions with the result of a |
425 // function application in String.prototype.replace. | 430 // function application in String.prototype.replace. |
426 function StringReplaceRegExpWithFunction(subject, regexp, replace) { | 431 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) { |
427 if (regexp.global) { | 432 var resultArray = reusableReplaceArray; |
428 var resultArray = reusableReplaceArray; | 433 if (resultArray) { |
429 if (resultArray) { | 434 reusableReplaceArray = null; |
430 reusableReplaceArray = null; | 435 } else { |
431 } else { | 436 // Inside a nested replace (replace called from the replacement function |
432 // Inside a nested replace (replace called from the replacement function | 437 // of another replace) or we have failed to set the reusable array |
433 // of another replace) or we have failed to set the reusable array | 438 // back due to an exception in a replacement function. Create a new |
434 // back due to an exception in a replacement function. Create a new | 439 // array to use in the future, or until the original is written back. |
435 // array to use in the future, or until the original is written back. | 440 resultArray = $Array(16); |
436 resultArray = $Array(16); | 441 } |
| 442 var res = %RegExpExecMultiple(regexp, |
| 443 subject, |
| 444 lastMatchInfo, |
| 445 resultArray); |
| 446 regexp.lastIndex = 0; |
| 447 if (IS_NULL(res)) { |
| 448 // No matches at all. |
| 449 reusableReplaceArray = resultArray; |
| 450 return subject; |
| 451 } |
| 452 var len = res.length; |
| 453 var i = 0; |
| 454 if (NUMBER_OF_CAPTURES(lastMatchInfo) == 2) { |
| 455 var match_start = 0; |
| 456 var override = [null, 0, subject]; |
| 457 var receiver = %GetGlobalReceiver(); |
| 458 while (i < len) { |
| 459 var elem = res[i]; |
| 460 if (%_IsSmi(elem)) { |
| 461 if (elem > 0) { |
| 462 match_start = (elem >> 11) + (elem & 0x7ff); |
| 463 } else { |
| 464 match_start = res[++i] - elem; |
| 465 } |
| 466 } else { |
| 467 override[0] = elem; |
| 468 override[1] = match_start; |
| 469 lastMatchInfoOverride = override; |
| 470 var func_result = |
| 471 %_CallFunction(receiver, elem, match_start, subject, replace); |
| 472 if (!IS_STRING(func_result)) { |
| 473 func_result = NonStringToString(func_result); |
| 474 } |
| 475 res[i] = func_result; |
| 476 match_start += elem.length; |
| 477 } |
| 478 i++; |
437 } | 479 } |
438 | 480 } else { |
439 var res = %RegExpExecMultiple(regexp, | 481 while (i < len) { |
440 subject, | 482 var elem = res[i]; |
441 lastMatchInfo, | 483 if (!%_IsSmi(elem)) { |
442 resultArray); | 484 // elem must be an Array. |
443 regexp.lastIndex = 0; | 485 // Use the apply argument as backing for global RegExp properties. |
444 if (IS_NULL(res)) { | 486 lastMatchInfoOverride = elem; |
445 // No matches at all. | 487 var func_result = replace.apply(null, elem); |
446 return subject; | 488 if (!IS_STRING(func_result)) { |
| 489 func_result = NonStringToString(func_result); |
| 490 } |
| 491 res[i] = func_result; |
| 492 } |
| 493 i++; |
447 } | 494 } |
448 var len = res.length; | |
449 var i = 0; | |
450 if (NUMBER_OF_CAPTURES(lastMatchInfo) == 2) { | |
451 var match_start = 0; | |
452 var override = [null, 0, subject]; | |
453 while (i < len) { | |
454 var elem = res[i]; | |
455 if (%_IsSmi(elem)) { | |
456 if (elem > 0) { | |
457 match_start = (elem >> 11) + (elem & 0x7ff); | |
458 } else { | |
459 match_start = res[++i] - elem; | |
460 } | |
461 } else { | |
462 override[0] = elem; | |
463 override[1] = match_start; | |
464 lastMatchInfoOverride = override; | |
465 var func_result = replace.call(null, elem, match_start, subject); | |
466 if (!IS_STRING(func_result)) { | |
467 func_result = NonStringToString(func_result); | |
468 } | |
469 res[i] = func_result; | |
470 match_start += elem.length; | |
471 } | |
472 i++; | |
473 } | |
474 } else { | |
475 while (i < len) { | |
476 var elem = res[i]; | |
477 if (!%_IsSmi(elem)) { | |
478 // elem must be an Array. | |
479 // Use the apply argument as backing for global RegExp properties. | |
480 lastMatchInfoOverride = elem; | |
481 var func_result = replace.apply(null, elem); | |
482 if (!IS_STRING(func_result)) { | |
483 func_result = NonStringToString(func_result); | |
484 } | |
485 res[i] = func_result; | |
486 } | |
487 i++; | |
488 } | |
489 } | |
490 var resultBuilder = new ReplaceResultBuilder(subject, res); | |
491 var result = resultBuilder.generate(); | |
492 resultArray.length = 0; | |
493 reusableReplaceArray = resultArray; | |
494 return result; | |
495 } else { // Not a global regexp, no need to loop. | |
496 var matchInfo = DoRegExpExec(regexp, subject, 0); | |
497 if (IS_NULL(matchInfo)) return subject; | |
498 | |
499 var result = new ReplaceResultBuilder(subject); | |
500 result.addSpecialSlice(0, matchInfo[CAPTURE0]); | |
501 var endOfMatch = matchInfo[CAPTURE1]; | |
502 result.add(ApplyReplacementFunction(replace, matchInfo, subject)); | |
503 // Can't use matchInfo any more from here, since the function could | |
504 // overwrite it. | |
505 result.addSpecialSlice(endOfMatch, subject.length); | |
506 return result.generate(); | |
507 } | 495 } |
| 496 var resultBuilder = new ReplaceResultBuilder(subject, res); |
| 497 var result = resultBuilder.generate(); |
| 498 resultArray.length = 0; |
| 499 reusableReplaceArray = resultArray; |
| 500 return result; |
508 } | 501 } |
509 | 502 |
510 | 503 |
511 // Helper function to apply a string replacement function once. | 504 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) { |
512 function ApplyReplacementFunction(replace, matchInfo, subject) { | 505 var matchInfo = DoRegExpExec(regexp, subject, 0); |
| 506 if (IS_NULL(matchInfo)) return subject; |
| 507 var result = new ReplaceResultBuilder(subject); |
| 508 var index = matchInfo[CAPTURE0]; |
| 509 result.addSpecialSlice(0, index); |
| 510 var endOfMatch = matchInfo[CAPTURE1]; |
513 // Compute the parameter list consisting of the match, captures, index, | 511 // Compute the parameter list consisting of the match, captures, index, |
514 // and subject for the replace function invocation. | 512 // and subject for the replace function invocation. |
515 var index = matchInfo[CAPTURE0]; | |
516 // The number of captures plus one for the match. | 513 // The number of captures plus one for the match. |
517 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; | 514 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; |
| 515 var replacement; |
518 if (m == 1) { | 516 if (m == 1) { |
519 var s = CaptureString(subject, matchInfo, 0); | 517 // No captures, only the match, which is always valid. |
| 518 var s = SubString(subject, index, endOfMatch); |
520 // Don't call directly to avoid exposing the built-in global object. | 519 // Don't call directly to avoid exposing the built-in global object. |
521 return replace.call(null, s, index, subject); | 520 replacement = |
| 521 %_CallFunction(%GetGlobalReceiver(), s, index, subject, replace); |
| 522 } else { |
| 523 var parameters = $Array(m + 2); |
| 524 for (var j = 0; j < m; j++) { |
| 525 parameters[j] = CaptureString(subject, matchInfo, j); |
| 526 } |
| 527 parameters[j] = index; |
| 528 parameters[j + 1] = subject; |
| 529 |
| 530 replacement = replace.apply(null, parameters); |
522 } | 531 } |
523 var parameters = $Array(m + 2); | 532 |
524 for (var j = 0; j < m; j++) { | 533 result.add(replacement); // The add method converts to string if necessary. |
525 parameters[j] = CaptureString(subject, matchInfo, j); | 534 // Can't use matchInfo any more from here, since the function could |
526 } | 535 // overwrite it. |
527 parameters[j] = index; | 536 result.addSpecialSlice(endOfMatch, subject.length); |
528 parameters[j + 1] = subject; | 537 return result.generate(); |
529 return replace.apply(null, parameters); | |
530 } | 538 } |
531 | 539 |
| 540 |
532 // ECMA-262 section 15.5.4.12 | 541 // ECMA-262 section 15.5.4.12 |
533 function StringSearch(re) { | 542 function StringSearch(re) { |
534 var regexp; | 543 var regexp; |
535 if (IS_STRING(re)) { | 544 if (IS_STRING(re)) { |
536 regexp = %_GetFromCache(STRING_TO_REGEXP_CACHE_ID, re); | 545 regexp = %_GetFromCache(STRING_TO_REGEXP_CACHE_ID, re); |
537 } else if (IS_REGEXP(re)) { | 546 } else if (IS_REGEXP(re)) { |
538 regexp = re; | 547 regexp = re; |
539 } else { | 548 } else { |
540 regexp = new $RegExp(re); | 549 regexp = new $RegExp(re); |
541 } | 550 } |
(...skipping 461 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1003 "small", StringSmall, | 1012 "small", StringSmall, |
1004 "strike", StringStrike, | 1013 "strike", StringStrike, |
1005 "sub", StringSub, | 1014 "sub", StringSub, |
1006 "sup", StringSup, | 1015 "sup", StringSup, |
1007 "toJSON", StringToJSON | 1016 "toJSON", StringToJSON |
1008 )); | 1017 )); |
1009 } | 1018 } |
1010 | 1019 |
1011 | 1020 |
1012 SetupString(); | 1021 SetupString(); |
OLD | NEW |