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 |