| 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 ArrayIndexOf; | 12 var ArrayIndexOf; |
| 13 var ArrayJoin; | 13 var ArrayJoin; |
| 14 var GlobalRegExp = global.RegExp; | 14 var GlobalRegExp = global.RegExp; |
| 15 var GlobalString = global.String; | 15 var GlobalString = global.String; |
| 16 var InternalArray = utils.InternalArray; | 16 var InternalArray = utils.InternalArray; |
| 17 var InternalPackedArray = utils.InternalPackedArray; | 17 var InternalPackedArray = utils.InternalPackedArray; |
| 18 var MakeRangeError; | 18 var MakeRangeError; |
| 19 var MakeTypeError; | 19 var MakeTypeError; |
| 20 var MathMax; | 20 var MathMax; |
| 21 var MathMin; | 21 var MathMin; |
| 22 var matchSymbol = utils.ImportNow("match_symbol"); | 22 var matchSymbol = utils.ImportNow("match_symbol"); |
| 23 var RegExpExec; | |
| 24 var RegExpExecNoTests; | 23 var RegExpExecNoTests; |
| 25 var RegExpLastMatchInfo; | 24 var replaceSymbol = utils.ImportNow("replace_symbol"); |
| 26 var searchSymbol = utils.ImportNow("search_symbol"); | 25 var searchSymbol = utils.ImportNow("search_symbol"); |
| 27 var splitSymbol = utils.ImportNow("split_symbol"); | 26 var splitSymbol = utils.ImportNow("split_symbol"); |
| 28 | 27 |
| 29 utils.Import(function(from) { | 28 utils.Import(function(from) { |
| 30 ArrayIndexOf = from.ArrayIndexOf; | 29 ArrayIndexOf = from.ArrayIndexOf; |
| 31 ArrayJoin = from.ArrayJoin; | 30 ArrayJoin = from.ArrayJoin; |
| 32 MakeRangeError = from.MakeRangeError; | 31 MakeRangeError = from.MakeRangeError; |
| 33 MakeTypeError = from.MakeTypeError; | 32 MakeTypeError = from.MakeTypeError; |
| 34 MathMax = from.MathMax; | 33 MathMax = from.MathMax; |
| 35 MathMin = from.MathMin; | 34 MathMin = from.MathMin; |
| 36 RegExpExec = from.RegExpExec; | |
| 37 RegExpExecNoTests = from.RegExpExecNoTests; | 35 RegExpExecNoTests = from.RegExpExecNoTests; |
| 38 RegExpLastMatchInfo = from.RegExpLastMatchInfo; | |
| 39 }); | 36 }); |
| 40 | 37 |
| 41 //------------------------------------------------------------------- | 38 //------------------------------------------------------------------- |
| 42 | 39 |
| 43 // ECMA-262 section 15.5.4.2 | 40 // ECMA-262 section 15.5.4.2 |
| 44 function StringToString() { | 41 function StringToString() { |
| 45 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) { | 42 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) { |
| 46 throw MakeTypeError(kNotGeneric, 'String.prototype.toString'); | 43 throw MakeTypeError(kNotGeneric, 'String.prototype.toString'); |
| 47 } | 44 } |
| 48 return %_ValueOf(this); | 45 return %_ValueOf(this); |
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 199 | 196 |
| 200 | 197 |
| 201 // This has the same size as the RegExpLastMatchInfo array, and can be used | 198 // This has the same size as the RegExpLastMatchInfo array, and can be used |
| 202 // for functions that expect that structure to be returned. It is used when | 199 // for functions that expect that structure to be returned. It is used when |
| 203 // the needle is a string rather than a regexp. In this case we can't update | 200 // the needle is a string rather than a regexp. In this case we can't update |
| 204 // lastMatchArray without erroneously affecting the properties on the global | 201 // lastMatchArray without erroneously affecting the properties on the global |
| 205 // RegExp object. | 202 // RegExp object. |
| 206 var reusableMatchInfo = [2, "", "", -1, -1]; | 203 var reusableMatchInfo = [2, "", "", -1, -1]; |
| 207 | 204 |
| 208 | 205 |
| 209 // ECMA-262, section 15.5.4.11 | 206 // ES6, section 21.1.3.14 |
| 210 function StringReplace(search, replace) { | 207 function StringReplace(search, replace) { |
| 211 CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace"); | 208 CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace"); |
| 212 | 209 |
| 213 var subject = TO_STRING(this); | |
| 214 | |
| 215 // Decision tree for dispatch | 210 // Decision tree for dispatch |
| 216 // .. regexp search | 211 // .. regexp search (in src/js/regexp.js, RegExpReplace) |
| 217 // .... string replace | 212 // .... string replace |
| 218 // ...... non-global search | 213 // ...... non-global search |
| 219 // ........ empty string replace | 214 // ........ empty string replace |
| 220 // ........ non-empty string replace (with $-expansion) | 215 // ........ non-empty string replace (with $-expansion) |
| 221 // ...... global search | 216 // ...... global search |
| 222 // ........ no need to circumvent last match info override | 217 // ........ no need to circumvent last match info override |
| 223 // ........ need to circument last match info override | 218 // ........ need to circument last match info override |
| 224 // .... function replace | 219 // .... function replace |
| 225 // ...... global search | 220 // ...... global search |
| 226 // ...... non-global search | 221 // ...... non-global search |
| 227 // .. string search | 222 // .. string search |
| 228 // .... special case that replaces with one single character | 223 // .... special case that replaces with one single character |
| 229 // ...... function replace | 224 // ...... function replace |
| 230 // ...... string replace (with $-expansion) | 225 // ...... string replace (with $-expansion) |
| 231 | 226 |
| 232 if (IS_REGEXP(search)) { | 227 if (!IS_NULL_OR_UNDEFINED(search)) { |
| 233 if (!IS_CALLABLE(replace)) { | 228 var replacer = search[replaceSymbol]; |
| 234 replace = TO_STRING(replace); | 229 if (!IS_UNDEFINED(replacer)) { |
| 230 return %_Call(replacer, search, this, replace); |
| 231 } |
| 232 } |
| 235 | 233 |
| 236 if (!REGEXP_GLOBAL(search)) { | 234 var subject = TO_STRING(this); |
| 237 // Non-global regexp search, string replace. | |
| 238 var match = RegExpExec(search, subject, 0); | |
| 239 if (match == null) { | |
| 240 search.lastIndex = 0 | |
| 241 return subject; | |
| 242 } | |
| 243 if (replace.length == 0) { | |
| 244 return %_SubString(subject, 0, match[CAPTURE0]) + | |
| 245 %_SubString(subject, match[CAPTURE1], subject.length) | |
| 246 } | |
| 247 return ExpandReplacement(replace, subject, RegExpLastMatchInfo, | |
| 248 %_SubString(subject, 0, match[CAPTURE0])) + | |
| 249 %_SubString(subject, match[CAPTURE1], subject.length); | |
| 250 } | |
| 251 | |
| 252 // Global regexp search, string replace. | |
| 253 search.lastIndex = 0; | |
| 254 return %StringReplaceGlobalRegExpWithString( | |
| 255 subject, search, replace, RegExpLastMatchInfo); | |
| 256 } | |
| 257 | |
| 258 if (REGEXP_GLOBAL(search)) { | |
| 259 // Global regexp search, function replace. | |
| 260 return StringReplaceGlobalRegExpWithFunction(subject, search, replace); | |
| 261 } | |
| 262 // Non-global regexp search, function replace. | |
| 263 return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace); | |
| 264 } | |
| 265 | 235 |
| 266 search = TO_STRING(search); | 236 search = TO_STRING(search); |
| 267 | 237 |
| 268 if (search.length == 1 && | 238 if (search.length == 1 && |
| 269 subject.length > 0xFF && | 239 subject.length > 0xFF && |
| 270 IS_STRING(replace) && | 240 IS_STRING(replace) && |
| 271 %StringIndexOf(replace, '$', 0) < 0) { | 241 %StringIndexOf(replace, '$', 0) < 0) { |
| 272 // Searching by traversing a cons string tree and replace with cons of | 242 // Searching by traversing a cons string tree and replace with cons of |
| 273 // slices works only when the replaced string is a single character, being | 243 // slices works only when the replaced string is a single character, being |
| 274 // replaced by a simple string and only pays off for long strings. | 244 // replaced by a simple string and only pays off for long strings. |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 372 | 342 |
| 373 // Append substring between the previous and the next $ character. | 343 // Append substring between the previous and the next $ character. |
| 374 if (next > position) { | 344 if (next > position) { |
| 375 result += %_SubString(string, position, next); | 345 result += %_SubString(string, position, next); |
| 376 } | 346 } |
| 377 } | 347 } |
| 378 return result; | 348 return result; |
| 379 } | 349 } |
| 380 | 350 |
| 381 | 351 |
| 382 // Compute the string of a given regular expression capture. | |
| 383 function CaptureString(string, lastCaptureInfo, index) { | |
| 384 // Scale the index. | |
| 385 var scaled = index << 1; | |
| 386 // Compute start and end. | |
| 387 var start = lastCaptureInfo[CAPTURE(scaled)]; | |
| 388 // If start isn't valid, return undefined. | |
| 389 if (start < 0) return; | |
| 390 var end = lastCaptureInfo[CAPTURE(scaled + 1)]; | |
| 391 return %_SubString(string, start, end); | |
| 392 } | |
| 393 | |
| 394 | |
| 395 // TODO(lrn): This array will survive indefinitely if replace is never | |
| 396 // called again. However, it will be empty, since the contents are cleared | |
| 397 // in the finally block. | |
| 398 var reusableReplaceArray = new InternalArray(4); | |
| 399 | |
| 400 // Helper function for replacing regular expressions with the result of a | |
| 401 // function application in String.prototype.replace. | |
| 402 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) { | |
| 403 var resultArray = reusableReplaceArray; | |
| 404 if (resultArray) { | |
| 405 reusableReplaceArray = null; | |
| 406 } else { | |
| 407 // Inside a nested replace (replace called from the replacement function | |
| 408 // of another replace) or we have failed to set the reusable array | |
| 409 // back due to an exception in a replacement function. Create a new | |
| 410 // array to use in the future, or until the original is written back. | |
| 411 resultArray = new InternalArray(16); | |
| 412 } | |
| 413 var res = %RegExpExecMultiple(regexp, | |
| 414 subject, | |
| 415 RegExpLastMatchInfo, | |
| 416 resultArray); | |
| 417 regexp.lastIndex = 0; | |
| 418 if (IS_NULL(res)) { | |
| 419 // No matches at all. | |
| 420 reusableReplaceArray = resultArray; | |
| 421 return subject; | |
| 422 } | |
| 423 var len = res.length; | |
| 424 if (NUMBER_OF_CAPTURES(RegExpLastMatchInfo) == 2) { | |
| 425 // If the number of captures is two then there are no explicit captures in | |
| 426 // the regexp, just the implicit capture that captures the whole match. In | |
| 427 // this case we can simplify quite a bit and end up with something faster. | |
| 428 // The builder will consist of some integers that indicate slices of the | |
| 429 // input string and some replacements that were returned from the replace | |
| 430 // function. | |
| 431 var match_start = 0; | |
| 432 for (var i = 0; i < len; i++) { | |
| 433 var elem = res[i]; | |
| 434 if (%_IsSmi(elem)) { | |
| 435 // Integers represent slices of the original string. | |
| 436 if (elem > 0) { | |
| 437 match_start = (elem >> 11) + (elem & 0x7ff); | |
| 438 } else { | |
| 439 match_start = res[++i] - elem; | |
| 440 } | |
| 441 } else { | |
| 442 var func_result = replace(elem, match_start, subject); | |
| 443 // Overwrite the i'th element in the results with the string we got | |
| 444 // back from the callback function. | |
| 445 res[i] = TO_STRING(func_result); | |
| 446 match_start += elem.length; | |
| 447 } | |
| 448 } | |
| 449 } else { | |
| 450 for (var i = 0; i < len; i++) { | |
| 451 var elem = res[i]; | |
| 452 if (!%_IsSmi(elem)) { | |
| 453 // elem must be an Array. | |
| 454 // Use the apply argument as backing for global RegExp properties. | |
| 455 var func_result = %Apply(replace, UNDEFINED, elem, 0, elem.length); | |
| 456 // Overwrite the i'th element in the results with the string we got | |
| 457 // back from the callback function. | |
| 458 res[i] = TO_STRING(func_result); | |
| 459 } | |
| 460 } | |
| 461 } | |
| 462 var result = %StringBuilderConcat(res, len, subject); | |
| 463 resultArray.length = 0; | |
| 464 reusableReplaceArray = resultArray; | |
| 465 return result; | |
| 466 } | |
| 467 | |
| 468 | |
| 469 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) { | |
| 470 var matchInfo = RegExpExec(regexp, subject, 0); | |
| 471 if (IS_NULL(matchInfo)) { | |
| 472 regexp.lastIndex = 0; | |
| 473 return subject; | |
| 474 } | |
| 475 var index = matchInfo[CAPTURE0]; | |
| 476 var result = %_SubString(subject, 0, index); | |
| 477 var endOfMatch = matchInfo[CAPTURE1]; | |
| 478 // Compute the parameter list consisting of the match, captures, index, | |
| 479 // and subject for the replace function invocation. | |
| 480 // The number of captures plus one for the match. | |
| 481 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; | |
| 482 var replacement; | |
| 483 if (m == 1) { | |
| 484 // No captures, only the match, which is always valid. | |
| 485 var s = %_SubString(subject, index, endOfMatch); | |
| 486 // Don't call directly to avoid exposing the built-in global object. | |
| 487 replacement = replace(s, index, subject); | |
| 488 } else { | |
| 489 var parameters = new InternalArray(m + 2); | |
| 490 for (var j = 0; j < m; j++) { | |
| 491 parameters[j] = CaptureString(subject, matchInfo, j); | |
| 492 } | |
| 493 parameters[j] = index; | |
| 494 parameters[j + 1] = subject; | |
| 495 | |
| 496 replacement = %Apply(replace, UNDEFINED, parameters, 0, j + 2); | |
| 497 } | |
| 498 | |
| 499 result += replacement; // The add method converts to string if necessary. | |
| 500 // Can't use matchInfo any more from here, since the function could | |
| 501 // overwrite it. | |
| 502 return result + %_SubString(subject, endOfMatch, subject.length); | |
| 503 } | |
| 504 | |
| 505 | |
| 506 // ES6 21.1.3.15. | 352 // ES6 21.1.3.15. |
| 507 function StringSearch(pattern) { | 353 function StringSearch(pattern) { |
| 508 CHECK_OBJECT_COERCIBLE(this, "String.prototype.search"); | 354 CHECK_OBJECT_COERCIBLE(this, "String.prototype.search"); |
| 509 | 355 |
| 510 if (!IS_NULL_OR_UNDEFINED(pattern)) { | 356 if (!IS_NULL_OR_UNDEFINED(pattern)) { |
| 511 var searcher = pattern[searchSymbol]; | 357 var searcher = pattern[searchSymbol]; |
| 512 if (!IS_UNDEFINED(searcher)) { | 358 if (!IS_UNDEFINED(searcher)) { |
| 513 return %_Call(searcher, pattern, this); | 359 return %_Call(searcher, pattern, this); |
| 514 } | 360 } |
| 515 } | 361 } |
| (...skipping 575 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1091 "small", StringSmall, | 937 "small", StringSmall, |
| 1092 "strike", StringStrike, | 938 "strike", StringStrike, |
| 1093 "sub", StringSub, | 939 "sub", StringSub, |
| 1094 "sup", StringSup | 940 "sup", StringSup |
| 1095 ]); | 941 ]); |
| 1096 | 942 |
| 1097 // ------------------------------------------------------------------- | 943 // ------------------------------------------------------------------- |
| 1098 // Exports | 944 // Exports |
| 1099 | 945 |
| 1100 utils.Export(function(to) { | 946 utils.Export(function(to) { |
| 947 to.ExpandReplacement = ExpandReplacement; |
| 1101 to.StringCharAt = StringCharAtJS; | 948 to.StringCharAt = StringCharAtJS; |
| 1102 to.StringIndexOf = StringIndexOfJS; | 949 to.StringIndexOf = StringIndexOfJS; |
| 1103 to.StringLastIndexOf = StringLastIndexOfJS; | 950 to.StringLastIndexOf = StringLastIndexOfJS; |
| 1104 to.StringMatch = StringMatchJS; | 951 to.StringMatch = StringMatchJS; |
| 1105 to.StringReplace = StringReplace; | 952 to.StringReplace = StringReplace; |
| 1106 to.StringSlice = StringSlice; | 953 to.StringSlice = StringSlice; |
| 1107 to.StringSplit = StringSplitJS; | 954 to.StringSplit = StringSplitJS; |
| 1108 to.StringSubstr = StringSubstr; | 955 to.StringSubstr = StringSubstr; |
| 1109 to.StringSubstring = StringSubstring; | 956 to.StringSubstring = StringSubstring; |
| 1110 }); | 957 }); |
| 1111 | 958 |
| 1112 }) | 959 }) |
| OLD | NEW |