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 (function(global, utils) { | 5 (function(global, utils) { |
6 | 6 |
7 %CheckIsBootstrapping(); | 7 %CheckIsBootstrapping(); |
8 | 8 |
9 // ------------------------------------------------------------------- | 9 // ------------------------------------------------------------------- |
10 // Imports | 10 // Imports |
11 | 11 |
| 12 var ExpandReplacement; |
12 var FLAG_harmony_tolength; | 13 var FLAG_harmony_tolength; |
13 var GlobalObject = global.Object; | 14 var GlobalObject = global.Object; |
14 var GlobalRegExp = global.RegExp; | 15 var GlobalRegExp = global.RegExp; |
15 var GlobalRegExpPrototype; | 16 var GlobalRegExpPrototype; |
16 var InternalArray = utils.InternalArray; | 17 var InternalArray = utils.InternalArray; |
17 var InternalPackedArray = utils.InternalPackedArray; | 18 var InternalPackedArray = utils.InternalPackedArray; |
18 var MakeTypeError; | 19 var MakeTypeError; |
19 var matchSymbol = utils.ImportNow("match_symbol"); | 20 var matchSymbol = utils.ImportNow("match_symbol"); |
| 21 var replaceSymbol = utils.ImportNow("replace_symbol"); |
20 var searchSymbol = utils.ImportNow("search_symbol"); | 22 var searchSymbol = utils.ImportNow("search_symbol"); |
21 var splitSymbol = utils.ImportNow("split_symbol"); | 23 var splitSymbol = utils.ImportNow("split_symbol"); |
22 | 24 |
23 utils.ImportFromExperimental(function(from) { | 25 utils.ImportFromExperimental(function(from) { |
24 FLAG_harmony_tolength = from.FLAG_harmony_tolength; | 26 FLAG_harmony_tolength = from.FLAG_harmony_tolength; |
25 }); | 27 }); |
26 | 28 |
27 utils.Import(function(from) { | 29 utils.Import(function(from) { |
| 30 ExpandReplacement = from.ExpandReplacement; |
28 MakeTypeError = from.MakeTypeError; | 31 MakeTypeError = from.MakeTypeError; |
29 }); | 32 }); |
30 | 33 |
31 // ------------------------------------------------------------------- | 34 // ------------------------------------------------------------------- |
32 | 35 |
33 // Property of the builtins object for recording the result of the last | 36 // Property of the builtins object for recording the result of the last |
34 // regexp match. The property RegExpLastMatchInfo includes the matchIndices | 37 // regexp match. The property RegExpLastMatchInfo includes the matchIndices |
35 // array of the last successful regexp match (an array of start/end index | 38 // array of the last successful regexp match (an array of start/end index |
36 // pairs for the match and all the captured substrings), the invariant is | 39 // pairs for the match and all the captured substrings), the invariant is |
37 // that there are at least two capture indeces. The array also contains | 40 // that there are at least two capture indeces. The array also contains |
(...skipping 334 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
372 } | 375 } |
373 var subject = TO_STRING(string); | 376 var subject = TO_STRING(string); |
374 | 377 |
375 if (!REGEXP_GLOBAL(this)) return RegExpExecNoTests(this, subject, 0); | 378 if (!REGEXP_GLOBAL(this)) return RegExpExecNoTests(this, subject, 0); |
376 this.lastIndex = 0; | 379 this.lastIndex = 0; |
377 var result = %StringMatch(subject, this, RegExpLastMatchInfo); | 380 var result = %StringMatch(subject, this, RegExpLastMatchInfo); |
378 return result; | 381 return result; |
379 } | 382 } |
380 | 383 |
381 | 384 |
| 385 // ES6 21.2.5.8. |
| 386 |
| 387 // TODO(lrn): This array will survive indefinitely if replace is never |
| 388 // called again. However, it will be empty, since the contents are cleared |
| 389 // in the finally block. |
| 390 var reusableReplaceArray = new InternalArray(4); |
| 391 |
| 392 // Helper function for replacing regular expressions with the result of a |
| 393 // function application in String.prototype.replace. |
| 394 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) { |
| 395 var resultArray = reusableReplaceArray; |
| 396 if (resultArray) { |
| 397 reusableReplaceArray = null; |
| 398 } else { |
| 399 // Inside a nested replace (replace called from the replacement function |
| 400 // of another replace) or we have failed to set the reusable array |
| 401 // back due to an exception in a replacement function. Create a new |
| 402 // array to use in the future, or until the original is written back. |
| 403 resultArray = new InternalArray(16); |
| 404 } |
| 405 var res = %RegExpExecMultiple(regexp, |
| 406 subject, |
| 407 RegExpLastMatchInfo, |
| 408 resultArray); |
| 409 regexp.lastIndex = 0; |
| 410 if (IS_NULL(res)) { |
| 411 // No matches at all. |
| 412 reusableReplaceArray = resultArray; |
| 413 return subject; |
| 414 } |
| 415 var len = res.length; |
| 416 if (NUMBER_OF_CAPTURES(RegExpLastMatchInfo) == 2) { |
| 417 // If the number of captures is two then there are no explicit captures in |
| 418 // the regexp, just the implicit capture that captures the whole match. In |
| 419 // this case we can simplify quite a bit and end up with something faster. |
| 420 // The builder will consist of some integers that indicate slices of the |
| 421 // input string and some replacements that were returned from the replace |
| 422 // function. |
| 423 var match_start = 0; |
| 424 for (var i = 0; i < len; i++) { |
| 425 var elem = res[i]; |
| 426 if (%_IsSmi(elem)) { |
| 427 // Integers represent slices of the original string. |
| 428 if (elem > 0) { |
| 429 match_start = (elem >> 11) + (elem & 0x7ff); |
| 430 } else { |
| 431 match_start = res[++i] - elem; |
| 432 } |
| 433 } else { |
| 434 var func_result = replace(elem, match_start, subject); |
| 435 // Overwrite the i'th element in the results with the string we got |
| 436 // back from the callback function. |
| 437 res[i] = TO_STRING(func_result); |
| 438 match_start += elem.length; |
| 439 } |
| 440 } |
| 441 } else { |
| 442 for (var i = 0; i < len; i++) { |
| 443 var elem = res[i]; |
| 444 if (!%_IsSmi(elem)) { |
| 445 // elem must be an Array. |
| 446 // Use the apply argument as backing for global RegExp properties. |
| 447 var func_result = %Apply(replace, UNDEFINED, elem, 0, elem.length); |
| 448 // Overwrite the i'th element in the results with the string we got |
| 449 // back from the callback function. |
| 450 res[i] = TO_STRING(func_result); |
| 451 } |
| 452 } |
| 453 } |
| 454 var result = %StringBuilderConcat(res, len, subject); |
| 455 resultArray.length = 0; |
| 456 reusableReplaceArray = resultArray; |
| 457 return result; |
| 458 } |
| 459 |
| 460 |
| 461 // Compute the string of a given regular expression capture. |
| 462 function CaptureString(string, lastCaptureInfo, index) { |
| 463 // Scale the index. |
| 464 var scaled = index << 1; |
| 465 // Compute start and end. |
| 466 var start = lastCaptureInfo[CAPTURE(scaled)]; |
| 467 // If start isn't valid, return undefined. |
| 468 if (start < 0) return; |
| 469 var end = lastCaptureInfo[CAPTURE(scaled + 1)]; |
| 470 return %_SubString(string, start, end); |
| 471 } |
| 472 |
| 473 |
| 474 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) { |
| 475 var matchInfo = DoRegExpExec(regexp, subject, 0); |
| 476 if (IS_NULL(matchInfo)) { |
| 477 regexp.lastIndex = 0; |
| 478 return subject; |
| 479 } |
| 480 var index = matchInfo[CAPTURE0]; |
| 481 var result = %_SubString(subject, 0, index); |
| 482 var endOfMatch = matchInfo[CAPTURE1]; |
| 483 // Compute the parameter list consisting of the match, captures, index, |
| 484 // and subject for the replace function invocation. |
| 485 // The number of captures plus one for the match. |
| 486 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; |
| 487 var replacement; |
| 488 if (m == 1) { |
| 489 // No captures, only the match, which is always valid. |
| 490 var s = %_SubString(subject, index, endOfMatch); |
| 491 // Don't call directly to avoid exposing the built-in global object. |
| 492 replacement = replace(s, index, subject); |
| 493 } else { |
| 494 var parameters = new InternalArray(m + 2); |
| 495 for (var j = 0; j < m; j++) { |
| 496 parameters[j] = CaptureString(subject, matchInfo, j); |
| 497 } |
| 498 parameters[j] = index; |
| 499 parameters[j + 1] = subject; |
| 500 |
| 501 replacement = %Apply(replace, UNDEFINED, parameters, 0, j + 2); |
| 502 } |
| 503 |
| 504 result += replacement; // The add method converts to string if necessary. |
| 505 // Can't use matchInfo any more from here, since the function could |
| 506 // overwrite it. |
| 507 return result + %_SubString(subject, endOfMatch, subject.length); |
| 508 } |
| 509 |
| 510 |
| 511 function RegExpReplace(string, replace) { |
| 512 // TODO(littledan): allow non-regexp receivers. |
| 513 if (!IS_REGEXP(this)) { |
| 514 throw MakeTypeError(kIncompatibleMethodReceiver, |
| 515 "RegExp.prototype.@@replace", this); |
| 516 } |
| 517 var subject = TO_STRING(string); |
| 518 var search = this; |
| 519 |
| 520 if (!IS_CALLABLE(replace)) { |
| 521 replace = TO_STRING(replace); |
| 522 |
| 523 if (!REGEXP_GLOBAL(search)) { |
| 524 // Non-global regexp search, string replace. |
| 525 var match = DoRegExpExec(search, subject, 0); |
| 526 if (match == null) { |
| 527 search.lastIndex = 0 |
| 528 return subject; |
| 529 } |
| 530 if (replace.length == 0) { |
| 531 return %_SubString(subject, 0, match[CAPTURE0]) + |
| 532 %_SubString(subject, match[CAPTURE1], subject.length) |
| 533 } |
| 534 return ExpandReplacement(replace, subject, RegExpLastMatchInfo, |
| 535 %_SubString(subject, 0, match[CAPTURE0])) + |
| 536 %_SubString(subject, match[CAPTURE1], subject.length); |
| 537 } |
| 538 |
| 539 // Global regexp search, string replace. |
| 540 search.lastIndex = 0; |
| 541 return %StringReplaceGlobalRegExpWithString( |
| 542 subject, search, replace, RegExpLastMatchInfo); |
| 543 } |
| 544 |
| 545 if (REGEXP_GLOBAL(search)) { |
| 546 // Global regexp search, function replace. |
| 547 return StringReplaceGlobalRegExpWithFunction(subject, search, replace); |
| 548 } |
| 549 // Non-global regexp search, function replace. |
| 550 return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace); |
| 551 } |
| 552 |
| 553 |
382 // ES6 21.2.5.9. | 554 // ES6 21.2.5.9. |
383 function RegExpSearch(string) { | 555 function RegExpSearch(string) { |
384 // TODO(yangguo): allow non-regexp receivers. | 556 // TODO(yangguo): allow non-regexp receivers. |
385 if (!IS_REGEXP(this)) { | 557 if (!IS_REGEXP(this)) { |
386 throw MakeTypeError(kIncompatibleMethodReceiver, | 558 throw MakeTypeError(kIncompatibleMethodReceiver, |
387 "RegExp.prototype.@@search", this); | 559 "RegExp.prototype.@@search", this); |
388 } | 560 } |
389 var match = DoRegExpExec(this, TO_STRING(string), 0); | 561 var match = DoRegExpExec(this, TO_STRING(string), 0); |
390 if (match) return match[CAPTURE0]; | 562 if (match) return match[CAPTURE0]; |
391 return -1; | 563 return -1; |
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
504 %AddNamedProperty( | 676 %AddNamedProperty( |
505 GlobalRegExp.prototype, 'constructor', GlobalRegExp, DONT_ENUM); | 677 GlobalRegExp.prototype, 'constructor', GlobalRegExp, DONT_ENUM); |
506 %SetCode(GlobalRegExp, RegExpConstructor); | 678 %SetCode(GlobalRegExp, RegExpConstructor); |
507 | 679 |
508 utils.InstallFunctions(GlobalRegExp.prototype, DONT_ENUM, [ | 680 utils.InstallFunctions(GlobalRegExp.prototype, DONT_ENUM, [ |
509 "exec", RegExpExecJS, | 681 "exec", RegExpExecJS, |
510 "test", RegExpTest, | 682 "test", RegExpTest, |
511 "toString", RegExpToString, | 683 "toString", RegExpToString, |
512 "compile", RegExpCompileJS, | 684 "compile", RegExpCompileJS, |
513 matchSymbol, RegExpMatch, | 685 matchSymbol, RegExpMatch, |
| 686 replaceSymbol, RegExpReplace, |
514 searchSymbol, RegExpSearch, | 687 searchSymbol, RegExpSearch, |
515 splitSymbol, RegExpSplit, | 688 splitSymbol, RegExpSplit, |
516 ]); | 689 ]); |
517 | 690 |
518 utils.InstallGetter(GlobalRegExp.prototype, 'global', RegExpGetGlobal); | 691 utils.InstallGetter(GlobalRegExp.prototype, 'global', RegExpGetGlobal); |
519 utils.InstallGetter(GlobalRegExp.prototype, 'ignoreCase', RegExpGetIgnoreCase); | 692 utils.InstallGetter(GlobalRegExp.prototype, 'ignoreCase', RegExpGetIgnoreCase); |
520 utils.InstallGetter(GlobalRegExp.prototype, 'multiline', RegExpGetMultiline); | 693 utils.InstallGetter(GlobalRegExp.prototype, 'multiline', RegExpGetMultiline); |
521 utils.InstallGetter(GlobalRegExp.prototype, 'source', RegExpGetSource); | 694 utils.InstallGetter(GlobalRegExp.prototype, 'source', RegExpGetSource); |
522 | 695 |
523 // The length of compile is 1 in SpiderMonkey. | 696 // The length of compile is 1 in SpiderMonkey. |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
572 // Exports | 745 // Exports |
573 | 746 |
574 utils.Export(function(to) { | 747 utils.Export(function(to) { |
575 to.RegExpExec = DoRegExpExec; | 748 to.RegExpExec = DoRegExpExec; |
576 to.RegExpExecNoTests = RegExpExecNoTests; | 749 to.RegExpExecNoTests = RegExpExecNoTests; |
577 to.RegExpLastMatchInfo = RegExpLastMatchInfo; | 750 to.RegExpLastMatchInfo = RegExpLastMatchInfo; |
578 to.RegExpTest = RegExpTest; | 751 to.RegExpTest = RegExpTest; |
579 }); | 752 }); |
580 | 753 |
581 }) | 754 }) |
OLD | NEW |