1 // Copyright 2012 the V8 project authors. All rights reserved. 2 // Redistribution and use in source and binary forms, with or without 3 // modification, are permitted provided that the following conditions are 4 // met: 5 // 6 // * Redistributions of source code must retain the above copyright 7 // notice, this list of conditions and the following disclaimer. 8 // * Redistributions in binary form must reproduce the above 9 // copyright notice, this list of conditions and the following 10 // disclaimer in the documentation and/or other materials provided 11 // with the distribution. 12 // * Neither the name of Google Inc. nor the names of its 13 // contributors may be used to endorse or promote products derived 14 // from this software without specific prior written permission. 15 // 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 // This file relies on the fact that the following declaration has been made 29 // in runtime.js: 30 // var $String = global.String; 31 32 // ------------------------------------------------------------------- 33 34 function StringConstructor(x) { 35 var value = %_ArgumentsLength() == 0 ? '' : TO_STRING_INLINE(x); 36 if (%_IsConstructCall()) { 37 %_SetValueOf(this, value); 38 } else { 39 return value; 40 } 41 } 42 43 44 // ECMA-262 section 15.5.4.2 45 function StringToString() { 46 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) { 47 throw new $TypeError('String.prototype.toString is not generic'); 48 } 49 return %_ValueOf(this); 50 } 51 52 53 // ECMA-262 section 15.5.4.3 54 function StringValueOf() { 55 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) { 56 throw new $TypeError('String.prototype.valueOf is not generic'); 57 } 58 return %_ValueOf(this); 59 } 60 61 62 // ECMA-262, section 15.5.4.4 63 function StringCharAt(pos) { 64 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 65 throw MakeTypeError("called_on_null_or_undefined", 66 ["String.prototype.charAt"]); 67 } 68 var result = %_StringCharAt(this, pos); 69 if (%_IsSmi(result)) { 70 result = %_StringCharAt(TO_STRING_INLINE(this), TO_INTEGER(pos)); 71 } 72 return result; 73 } 74 75 76 // ECMA-262 section 15.5.4.5 77 function StringCharCodeAt(pos) { 78 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 79 throw MakeTypeError("called_on_null_or_undefined", 80 ["String.prototype.charCodeAt"]); 81 } 82 var result = %_StringCharCodeAt(this, pos); 83 if (!%_IsSmi(result)) { 84 result = %_StringCharCodeAt(TO_STRING_INLINE(this), TO_INTEGER(pos)); 85 } 86 return result; 87 } 88 89 90 // ECMA-262, section 15.5.4.6 91 function StringConcat() { 92 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 93 throw MakeTypeError("called_on_null_or_undefined", 94 ["String.prototype.concat"]); 95 } 96 var len = %_ArgumentsLength(); 97 var this_as_string = TO_STRING_INLINE(this); 98 if (len === 1) { 99 return this_as_string + %_Arguments(0); 100 } 101 var parts = new InternalArray(len + 1); 102 parts[0] = this_as_string; 103 for (var i = 0; i < len; i++) { 104 var part = %_Arguments(i); 105 parts[i + 1] = TO_STRING_INLINE(part); 106 } 107 return %StringBuilderConcat(parts, len + 1, ""); 108 } 109 110 // Match ES3 and Safari 111 %FunctionSetLength(StringConcat, 1); 112 113 114 // ECMA-262 section 15.5.4.7 115 function StringIndexOf(pattern /* position */) { // length == 1 116 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 117 throw MakeTypeError("called_on_null_or_undefined", 118 ["String.prototype.indexOf"]); 119 } 120 var subject = TO_STRING_INLINE(this); 121 pattern = TO_STRING_INLINE(pattern); 122 var index = 0; 123 if (%_ArgumentsLength() > 1) { 124 index = %_Arguments(1); // position 125 index = TO_INTEGER(index); 126 if (index < 0) index = 0; 127 if (index > subject.length) index = subject.length; 128 } 129 return %StringIndexOf(subject, pattern, index); 130 } 131 132 133 // ECMA-262 section 15.5.4.8 134 function StringLastIndexOf(pat /* position */) { // length == 1 135 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 136 throw MakeTypeError("called_on_null_or_undefined", 137 ["String.prototype.lastIndexOf"]); 138 } 139 var sub = TO_STRING_INLINE(this); 140 var subLength = sub.length; 141 var pat = TO_STRING_INLINE(pat); 142 var patLength = pat.length; 143 var index = subLength - patLength; 144 if (%_ArgumentsLength() > 1) { 145 var position = ToNumber(%_Arguments(1)); 146 if (!NUMBER_IS_NAN(position)) { 147 position = TO_INTEGER(position); 148 if (position < 0) { 149 position = 0; 150 } 151 if (position + patLength < subLength) { 152 index = position; 153 } 154 } 155 } 156 if (index < 0) { 157 return -1; 158 } 159 return %StringLastIndexOf(sub, pat, index); 160 } 161 162 163 // ECMA-262 section 15.5.4.9 164 // 165 // This function is implementation specific. For now, we do not 166 // do anything locale specific. 167 function StringLocaleCompare(other) { 168 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 169 throw MakeTypeError("called_on_null_or_undefined", 170 ["String.prototype.localeCompare"]); 171 } 172 return %StringLocaleCompare(TO_STRING_INLINE(this), 173 TO_STRING_INLINE(other)); 174 } 175 176 177 // ECMA-262 section 15.5.4.10 178 function StringMatch(regexp) { 179 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 180 throw MakeTypeError("called_on_null_or_undefined", 181 ["String.prototype.match"]); 182 } 183 var subject = TO_STRING_INLINE(this); 184 if (IS_REGEXP(regexp)) { 185 // Emulate RegExp.prototype.exec's side effect in step 5, even though 186 // value is discarded. 187 var lastIndex = regexp.lastIndex; 188 TO_INTEGER_FOR_SIDE_EFFECT(lastIndex); 189 if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0); 190 %_Log('regexp', 'regexp-match,%0S,%1r', [subject, regexp]); 191 // lastMatchInfo is defined in regexp.js. 192 var result = %StringMatch(subject, regexp, lastMatchInfo); 193 if (result !== null) lastMatchInfoOverride = null; 194 regexp.lastIndex = 0; 195 return result; 196 } 197 // Non-regexp argument. 198 regexp = new $RegExp(regexp); 199 return RegExpExecNoTests(regexp, subject, 0); 200 } 201 202 203 // This has the same size as the lastMatchInfo array, and can be used for 204 // functions that expect that structure to be returned. It is used when the 205 // needle is a string rather than a regexp. In this case we can't update 206 // lastMatchArray without erroneously affecting the properties on the global 207 // RegExp object. 208 var reusableMatchInfo = [2, "", "", -1, -1]; 209 210 211 // ECMA-262, section 15.5.4.11 212 function StringReplace(search, replace) { 213 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 214 throw MakeTypeError("called_on_null_or_undefined", 215 ["String.prototype.replace"]); 216 } 217 var subject = TO_STRING_INLINE(this); 218 219 // Decision tree for dispatch 220 // .. regexp search 221 // .... string replace 222 // ...... non-global search 223 // ........ empty string replace 224 // ........ non-empty string replace (with $-expansion) 225 // ...... global search 226 // ........ no need to circumvent last match info override 227 // ........ need to circument last match info override 228 // .... function replace 229 // ...... global search 230 // ...... non-global search 231 // .. string search 232 // .... special case that replaces with one single character 233 // ...... function replace 234 // ...... string replace (with $-expansion) 235 236 if (IS_REGEXP(search)) { 237 // Emulate RegExp.prototype.exec's side effect in step 5, even if 238 // value is discarded. 239 var lastIndex = search.lastIndex; 240 TO_INTEGER_FOR_SIDE_EFFECT(lastIndex); 241 %_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]); 242 243 if (!IS_SPEC_FUNCTION(replace)) { 244 replace = TO_STRING_INLINE(replace); 245 246 if (!search.global) { 247 // Non-global regexp search, string replace. 248 var match = DoRegExpExec(search, subject, 0); 249 if (match == null) { 250 search.lastIndex = 0 251 return subject; 252 } 253 if (replace.length == 0) { 254 return %_SubString(subject, 0, match[CAPTURE0]) + 255 %_SubString(subject, match[CAPTURE1], subject.length) 256 } 257 return ExpandReplacement(replace, subject, lastMatchInfo, 258 %_SubString(subject, 0, match[CAPTURE0])) + 259 %_SubString(subject, match[CAPTURE1], subject.length); 260 } 261 262 // Global regexp search, string replace. 263 search.lastIndex = 0; 264 if (lastMatchInfoOverride == null) { 265 return %StringReplaceGlobalRegExpWithString( 266 subject, search, replace, lastMatchInfo); 267 } else { 268 // We use this hack to detect whether StringReplaceRegExpWithString 269 // found at least one hit. In that case we need to remove any 270 // override. 271 var saved_subject = lastMatchInfo[LAST_SUBJECT_INDEX]; 272 lastMatchInfo[LAST_SUBJECT_INDEX] = 0; 273 var answer = %StringReplaceGlobalRegExpWithString( 274 subject, search, replace, lastMatchInfo); 275 if (%_IsSmi(lastMatchInfo[LAST_SUBJECT_INDEX])) { 276 lastMatchInfo[LAST_SUBJECT_INDEX] = saved_subject; 277 } else { 278 lastMatchInfoOverride = null; 279 } 280 return answer; 281 } 282 } 283 284 if (search.global) { 285 // Global regexp search, function replace. 286 return StringReplaceGlobalRegExpWithFunction(subject, search, replace); 287 } 288 // Non-global regexp search, function replace. 289 return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace); 290 } 291 292 search = TO_STRING_INLINE(search); 293 294 if (search.length == 1 && 295 subject.length > 0xFF && 296 IS_STRING(replace) && 297 %StringIndexOf(replace, '$', 0) < 0) { 298 // Searching by traversing a cons string tree and replace with cons of 299 // slices works only when the replaced string is a single character, being 300 // replaced by a simple string and only pays off for long strings. 301 return %StringReplaceOneCharWithString(subject, search, replace); 302 } 303 var start = %StringIndexOf(subject, search, 0); 304 if (start < 0) return subject; 305 var end = start + search.length; 306 307 var result = %_SubString(subject, 0, start); 308 309 // Compute the string to replace with. 310 if (IS_SPEC_FUNCTION(replace)) { 311 var receiver = %GetDefaultReceiver(replace); 312 result += %_CallFunction(receiver, search, start, subject, replace); 313 } else { 314 reusableMatchInfo[CAPTURE0] = start; 315 reusableMatchInfo[CAPTURE1] = end; 316 result = ExpandReplacement(TO_STRING_INLINE(replace), 317 subject, 318 reusableMatchInfo, 319 result); 320 } 321 322 return result + %_SubString(subject, end, subject.length); 323 } 324 325 326 // Expand the $-expressions in the string and return a new string with 327 // the result. 328 function ExpandReplacement(string, subject, matchInfo, result) { 329 var length = string.length; 330 var next = %StringIndexOf(string, '$', 0); 331 if (next < 0) { 332 if (length > 0) result += string; 333 return result; 334 } 335 336 if (next > 0) result += %_SubString(string, 0, next); 337 338 while (true) { 339 var expansion = '$'; 340 var position = next + 1; 341 if (position < length) { 342 var peek = %_StringCharCodeAt(string, position); 343 if (peek == 36) { // $$ 344 ++position; 345 result += '$'; 346 } else if (peek == 38) { // $& - match 347 ++position; 348 result += 349 %_SubString(subject, matchInfo[CAPTURE0], matchInfo[CAPTURE1]); 350 } else if (peek == 96) { // $` - prefix 351 ++position; 352 result += %_SubString(subject, 0, matchInfo[CAPTURE0]); 353 } else if (peek == 39) { // $' - suffix 354 ++position; 355 result += %_SubString(subject, matchInfo[CAPTURE1], subject.length); 356 } else if (peek >= 48 && peek <= 57) { 357 // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99 358 var scaled_index = (peek - 48) << 1; 359 var advance = 1; 360 var number_of_captures = NUMBER_OF_CAPTURES(matchInfo); 361 if (position + 1 < string.length) { 362 var next = %_StringCharCodeAt(string, position + 1); 363 if (next >= 48 && next <= 57) { 364 var new_scaled_index = scaled_index * 10 + ((next - 48) << 1); 365 if (new_scaled_index < number_of_captures) { 366 scaled_index = new_scaled_index; 367 advance = 2; 368 } 369 } 370 } 371 if (scaled_index != 0 && scaled_index < number_of_captures) { 372 var start = matchInfo[CAPTURE(scaled_index)]; 373 if (start >= 0) { 374 result += 375 %_SubString(subject, start, matchInfo[CAPTURE(scaled_index + 1)]); 376 } 377 position += advance; 378 } else { 379 result += '$'; 380 } 381 } else { 382 result += '$'; 383 } 384 } else { 385 result += '$'; 386 } 387 388 // Go the the next $ in the string. 389 next = %StringIndexOf(string, '$', position); 390 391 // Return if there are no more $ characters in the string. If we 392 // haven't reached the end, we need to append the suffix. 393 if (next < 0) { 394 if (position < length) { 395 result += %_SubString(string, position, length); 396 } 397 return result; 398 } 399 400 // Append substring between the previous and the next $ character. 401 if (next > position) { 402 result += %_SubString(string, position, next); 403 } 404 } 405 return result; 406 } 407 408 409 // Compute the string of a given regular expression capture. 410 function CaptureString(string, lastCaptureInfo, index) { 411 // Scale the index. 412 var scaled = index << 1; 413 // Compute start and end. 414 var start = lastCaptureInfo[CAPTURE(scaled)]; 415 // If start isn't valid, return undefined. 416 if (start < 0) return; 417 var end = lastCaptureInfo[CAPTURE(scaled + 1)]; 418 return %_SubString(string, start, end); 419 } 420 421 422 // TODO(lrn): This array will survive indefinitely if replace is never 423 // called again. However, it will be empty, since the contents are cleared 424 // in the finally block. 425 var reusableReplaceArray = new InternalArray(16); 426 427 // Helper function for replacing regular expressions with the result of a 428 // function application in String.prototype.replace. 429 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) { 430 var resultArray = reusableReplaceArray; 431 if (resultArray) { 432 reusableReplaceArray = null; 433 } else { 434 // Inside a nested replace (replace called from the replacement function 435 // of another replace) or we have failed to set the reusable array 436 // back due to an exception in a replacement function. Create a new 437 // array to use in the future, or until the original is written back. 438 resultArray = new InternalArray(16); 439 } 440 var res = %RegExpExecMultiple(regexp, 441 subject, 442 lastMatchInfo, 443 resultArray); 444 regexp.lastIndex = 0; 445 if (IS_NULL(res)) { 446 // No matches at all. 447 reusableReplaceArray = resultArray; 448 return subject; 449 } 450 var len = res.length; 451 if (NUMBER_OF_CAPTURES(lastMatchInfo) == 2) { 452 // If the number of captures is two then there are no explicit captures in 453 // the regexp, just the implicit capture that captures the whole match. In 454 // this case we can simplify quite a bit and end up with something faster. 455 // The builder will consist of some integers that indicate slices of the 456 // input string and some replacements that were returned from the replace 457 // function. 458 var match_start = 0; 459 var override = new InternalPackedArray(null, 0, subject); 460 var receiver = %GetDefaultReceiver(replace); 461 for (var i = 0; i < len; i++) { 462 var elem = res[i]; 463 if (%_IsSmi(elem)) { 464 // Integers represent slices of the original string. Use these to 465 // get the offsets we need for the override array (so things like 466 // RegExp.leftContext work during the callback function. 467 if (elem > 0) { 468 match_start = (elem >> 11) + (elem & 0x7ff); 469 } else { 470 match_start = res[++i] - elem; 471 } 472 } else { 473 override[0] = elem; 474 override[1] = match_start; 475 lastMatchInfoOverride = override; 476 var func_result = 477 %_CallFunction(receiver, elem, match_start, subject, replace); 478 // Overwrite the i'th element in the results with the string we got 479 // back from the callback function. 480 res[i] = TO_STRING_INLINE(func_result); 481 match_start += elem.length; 482 } 483 } 484 } else { 485 var receiver = %GetDefaultReceiver(replace); 486 for (var i = 0; i < len; i++) { 487 var elem = res[i]; 488 if (!%_IsSmi(elem)) { 489 // elem must be an Array. 490 // Use the apply argument as backing for global RegExp properties. 491 lastMatchInfoOverride = elem; 492 var func_result = %Apply(replace, receiver, elem, 0, elem.length); 493 // Overwrite the i'th element in the results with the string we got 494 // back from the callback function. 495 res[i] = TO_STRING_INLINE(func_result); 496 } 497 } 498 } 499 var result = %StringBuilderConcat(res, res.length, subject); 500 resultArray.length = 0; 501 reusableReplaceArray = resultArray; 502 return result; 503 } 504 505 506 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) { 507 var matchInfo = DoRegExpExec(regexp, subject, 0); 508 if (IS_NULL(matchInfo)) { 509 regexp.lastIndex = 0; 510 return subject; 511 } 512 var index = matchInfo[CAPTURE0]; 513 var result = %_SubString(subject, 0, index); 514 var endOfMatch = matchInfo[CAPTURE1]; 515 // Compute the parameter list consisting of the match, captures, index, 516 // and subject for the replace function invocation. 517 // The number of captures plus one for the match. 518 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; 519 var replacement; 520 var receiver = %GetDefaultReceiver(replace); 521 if (m == 1) { 522 // No captures, only the match, which is always valid. 523 var s = %_SubString(subject, index, endOfMatch); 524 // Don't call directly to avoid exposing the built-in global object. 525 replacement = %_CallFunction(receiver, s, index, subject, replace); 526 } else { 527 var parameters = new InternalArray(m + 2); 528 for (var j = 0; j < m; j++) { 529 parameters[j] = CaptureString(subject, matchInfo, j); 530 } 531 parameters[j] = index; 532 parameters[j + 1] = subject; 533 534 replacement = %Apply(replace, receiver, parameters, 0, j + 2); 535 } 536 537 result += replacement; // The add method converts to string if necessary. 538 // Can't use matchInfo any more from here, since the function could 539 // overwrite it. 540 return result + %_SubString(subject, endOfMatch, subject.length); 541 } 542 543 544 // ECMA-262 section 15.5.4.12 545 function StringSearch(re) { 546 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 547 throw MakeTypeError("called_on_null_or_undefined", 548 ["String.prototype.search"]); 549 } 550 var regexp; 551 if (IS_STRING(re)) { 552 regexp = %_GetFromCache(STRING_TO_REGEXP_CACHE_ID, re); 553 } else if (IS_REGEXP(re)) { 554 regexp = re; 555 } else { 556 regexp = new $RegExp(re); 557 } 558 var match = DoRegExpExec(regexp, TO_STRING_INLINE(this), 0); 559 if (match) { 560 return match[CAPTURE0]; 561 } 562 return -1; 563 } 564 565 566 // ECMA-262 section 15.5.4.13 567 function StringSlice(start, end) { 568 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 569 throw MakeTypeError("called_on_null_or_undefined", 570 ["String.prototype.slice"]); 571 } 572 var s = TO_STRING_INLINE(this); 573 var s_len = s.length; 574 var start_i = TO_INTEGER(start); 575 var end_i = s_len; 576 if (!IS_UNDEFINED(end)) { 577 end_i = TO_INTEGER(end); 578 } 579 580 if (start_i < 0) { 581 start_i += s_len; 582 if (start_i < 0) { 583 start_i = 0; 584 } 585 } else { 586 if (start_i > s_len) { 587 return ''; 588 } 589 } 590 591 if (end_i < 0) { 592 end_i += s_len; 593 if (end_i < 0) { 594 return ''; 595 } 596 } else { 597 if (end_i > s_len) { 598 end_i = s_len; 599 } 600 } 601 602 if (end_i <= start_i) { 603 return ''; 604 } 605 606 return %_SubString(s, start_i, end_i); 607 } 608 609 610 // ECMA-262 section 15.5.4.14 611 function StringSplit(separator, limit) { 612 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 613 throw MakeTypeError("called_on_null_or_undefined", 614 ["String.prototype.split"]); 615 } 616 var subject = TO_STRING_INLINE(this); 617 limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit); 618 619 // ECMA-262 says that if separator is undefined, the result should 620 // be an array of size 1 containing the entire string. 621 if (IS_UNDEFINED(separator)) { 622 return [subject]; 623 } 624 625 var length = subject.length; 626 if (!IS_REGEXP(separator)) { 627 separator = TO_STRING_INLINE(separator); 628 629 if (limit === 0) return []; 630 631 var separator_length = separator.length; 632 633 // If the separator string is empty then return the elements in the subject. 634 if (separator_length === 0) return %StringToArray(subject, limit); 635 636 var result = %StringSplit(subject, separator, limit); 637 638 return result; 639 } 640 641 if (limit === 0) return []; 642 643 // Separator is a regular expression. 644 return StringSplitOnRegExp(subject, separator, limit, length); 645 } 646 647 648 var ArrayPushBuiltin = $Array.prototype.push; 649 650 function StringSplitOnRegExp(subject, separator, limit, length) { 651 %_Log('regexp', 'regexp-split,%0S,%1r', [subject, separator]); 652 653 if (length === 0) { 654 if (DoRegExpExec(separator, subject, 0, 0) != null) { 655 return []; 656 } 657 return [subject]; 658 } 659 660 var currentIndex = 0; 661 var startIndex = 0; 662 var startMatch = 0; 663 var result = []; 664 665 outer_loop: 666 while (true) { 667 668 if (startIndex === length) { 669 %_CallFunction(result, %_SubString(subject, currentIndex, length), 670 ArrayPushBuiltin); 671 break; 672 } 673 674 var matchInfo = DoRegExpExec(separator, subject, startIndex); 675 if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) { 676 %_CallFunction(result, %_SubString(subject, currentIndex, length), 677 ArrayPushBuiltin); 678 break; 679 } 680 var endIndex = matchInfo[CAPTURE1]; 681 682 // We ignore a zero-length match at the currentIndex. 683 if (startIndex === endIndex && endIndex === currentIndex) { 684 startIndex++; 685 continue; 686 } 687 688 %_CallFunction(result, %_SubString(subject, currentIndex, startMatch), 689 ArrayPushBuiltin); 690 691 if (result.length === limit) break; 692 693 var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE; 694 for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) { 695 var start = matchInfo[i++]; 696 var end = matchInfo[i++]; 697 if (end != -1) { 698 %_CallFunction(result, %_SubString(subject, start, end), 699 ArrayPushBuiltin); 700 } else { 701 %_CallFunction(result, UNDEFINED, ArrayPushBuiltin); 702 } 703 if (result.length === limit) break outer_loop; 704 } 705 706 startIndex = currentIndex = endIndex; 707 } 708 return result; 709 } 710 711 712 // ECMA-262 section 15.5.4.15 713 function StringSubstring(start, end) { 714 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 715 throw MakeTypeError("called_on_null_or_undefined", 716 ["String.prototype.subString"]); 717 } 718 var s = TO_STRING_INLINE(this); 719 var s_len = s.length; 720 721 var start_i = TO_INTEGER(start); 722 if (start_i < 0) { 723 start_i = 0; 724 } else if (start_i > s_len) { 725 start_i = s_len; 726 } 727 728 var end_i = s_len; 729 if (!IS_UNDEFINED(end)) { 730 end_i = TO_INTEGER(end); 731 if (end_i > s_len) { 732 end_i = s_len; 733 } else { 734 if (end_i < 0) end_i = 0; 735 if (start_i > end_i) { 736 var tmp = end_i; 737 end_i = start_i; 738 start_i = tmp; 739 } 740 } 741 } 742 743 return %_SubString(s, start_i, end_i); 744 } 745 746 747 // This is not a part of ECMA-262. 748 function StringSubstr(start, n) { 749 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 750 throw MakeTypeError("called_on_null_or_undefined", 751 ["String.prototype.substr"]); 752 } 753 var s = TO_STRING_INLINE(this); 754 var len; 755 756 // Correct n: If not given, set to string length; if explicitly 757 // set to undefined, zero, or negative, returns empty string. 758 if (IS_UNDEFINED(n)) { 759 len = s.length; 760 } else { 761 len = TO_INTEGER(n); 762 if (len <= 0) return ''; 763 } 764 765 // Correct start: If not given (or undefined), set to zero; otherwise 766 // convert to integer and handle negative case. 767 if (IS_UNDEFINED(start)) { 768 start = 0; 769 } else { 770 start = TO_INTEGER(start); 771 // If positive, and greater than or equal to the string length, 772 // return empty string. 773 if (start >= s.length) return ''; 774 // If negative and absolute value is larger than the string length, 775 // use zero. 776 if (start < 0) { 777 start += s.length; 778 if (start < 0) start = 0; 779 } 780 } 781 782 var end = start + len; 783 if (end > s.length) end = s.length; 784 785 return %_SubString(s, start, end); 786 } 787 788 789 // ECMA-262, 15.5.4.16 790 function StringToLowerCase() { 791 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 792 throw MakeTypeError("called_on_null_or_undefined", 793 ["String.prototype.toLowerCase"]); 794 } 795 return %StringToLowerCase(TO_STRING_INLINE(this)); 796 } 797 798 799 // ECMA-262, 15.5.4.17 800 function StringToLocaleLowerCase() { 801 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 802 throw MakeTypeError("called_on_null_or_undefined", 803 ["String.prototype.toLocaleLowerCase"]); 804 } 805 return %StringToLowerCase(TO_STRING_INLINE(this)); 806 } 807 808 809 // ECMA-262, 15.5.4.18 810 function StringToUpperCase() { 811 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 812 throw MakeTypeError("called_on_null_or_undefined", 813 ["String.prototype.toUpperCase"]); 814 } 815 return %StringToUpperCase(TO_STRING_INLINE(this)); 816 } 817 818 819 // ECMA-262, 15.5.4.19 820 function StringToLocaleUpperCase() { 821 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 822 throw MakeTypeError("called_on_null_or_undefined", 823 ["String.prototype.toLocaleUpperCase"]); 824 } 825 return %StringToUpperCase(TO_STRING_INLINE(this)); 826 } 827 828 // ES5, 15.5.4.20 829 function StringTrim() { 830 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 831 throw MakeTypeError("called_on_null_or_undefined", 832 ["String.prototype.trim"]); 833 } 834 return %StringTrim(TO_STRING_INLINE(this), true, true); 835 } 836 837 function StringTrimLeft() { 838 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 839 throw MakeTypeError("called_on_null_or_undefined", 840 ["String.prototype.trimLeft"]); 841 } 842 return %StringTrim(TO_STRING_INLINE(this), true, false); 843 } 844 845 function StringTrimRight() { 846 if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { 847 throw MakeTypeError("called_on_null_or_undefined", 848 ["String.prototype.trimRight"]); 849 } 850 return %StringTrim(TO_STRING_INLINE(this), false, true); 851 } 852 853 854 // ECMA-262, section 15.5.3.2 855 function StringFromCharCode(code) { 856 var n = %_ArgumentsLength(); 857 if (n == 1) { 858 if (!%_IsSmi(code)) code = ToNumber(code); 859 return %_StringCharFromCode(code & 0xffff); 860 } 861 862 var one_byte = %NewString(n, NEW_ONE_BYTE_STRING); 863 var i; 864 for (i = 0; i < n; i++) { 865 var code = %_Arguments(i); 866 if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff; 867 if (code < 0) code = code & 0xffff; 868 if (code > 0xff) break; 869 %_OneByteSeqStringSetChar(one_byte, i, code); 870 } 871 if (i == n) return one_byte; 872 one_byte = %TruncateString(one_byte, i); 873 874 var two_byte = %NewString(n - i, NEW_TWO_BYTE_STRING); 875 for (var j = 0; i < n; i++, j++) { 876 var code = %_Arguments(i); 877 if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff; 878 %_TwoByteSeqStringSetChar(two_byte, j, code); 879 } 880 return one_byte + two_byte; 881 } 882 883 884 // Helper function for very basic XSS protection. 885 function HtmlEscape(str) { 886 return TO_STRING_INLINE(str).replace(/</g, "<") 887 .replace(/>/g, ">") 888 .replace(/"/g, """) 889 .replace(/'/g, "'"); 890 } 891 892 893 // Compatibility support for KJS. 894 // Tested by mozilla/js/tests/js1_5/Regress/regress-276103.js. 895 function StringLink(s) { 896 return "<a href=\"" + HtmlEscape(s) + "\">" + this + "</a>"; 897 } 898 899 900 function StringAnchor(name) { 901 return "<a name=\"" + HtmlEscape(name) + "\">" + this + "</a>"; 902 } 903 904 905 function StringFontcolor(color) { 906 return "<font color=\"" + HtmlEscape(color) + "\">" + this + "</font>"; 907 } 908 909 910 function StringFontsize(size) { 911 return "<font size=\"" + HtmlEscape(size) + "\">" + this + "</font>"; 912 } 913 914 915 function StringBig() { 916 return "<big>" + this + "</big>"; 917 } 918 919 920 function StringBlink() { 921 return "<blink>" + this + "</blink>"; 922 } 923 924 925 function StringBold() { 926 return "<b>" + this + "</b>"; 927 } 928 929 930 function StringFixed() { 931 return "<tt>" + this + "</tt>"; 932 } 933 934 935 function StringItalics() { 936 return "<i>" + this + "</i>"; 937 } 938 939 940 function StringSmall() { 941 return "<small>" + this + "</small>"; 942 } 943 944 945 function StringStrike() { 946 return "<strike>" + this + "</strike>"; 947 } 948 949 950 function StringSub() { 951 return "<sub>" + this + "</sub>"; 952 } 953 954 955 function StringSup() { 956 return "<sup>" + this + "</sup>"; 957 } 958 959 // ------------------------------------------------------------------- 960 961 function SetUpString() { 962 %CheckIsBootstrapping(); 963 964 // Set the String function and constructor. 965 %SetCode($String, StringConstructor); 966 %FunctionSetPrototype($String, new $String()); 967 968 // Set up the constructor property on the String prototype object. 969 %SetProperty($String.prototype, "constructor", $String, DONT_ENUM); 970 971 // Set up the non-enumerable functions on the String object. 972 InstallFunctions($String, DONT_ENUM, $Array( 973 "fromCharCode", StringFromCharCode 974 )); 975 976 // Set up the non-enumerable functions on the String prototype object. 977 InstallFunctions($String.prototype, DONT_ENUM, $Array( 978 "valueOf", StringValueOf, 979 "toString", StringToString, 980 "charAt", StringCharAt, 981 "charCodeAt", StringCharCodeAt, 982 "concat", StringConcat, 983 "indexOf", StringIndexOf, 984 "lastIndexOf", StringLastIndexOf, 985 "localeCompare", StringLocaleCompare, 986 "match", StringMatch, 987 "replace", StringReplace, 988 "search", StringSearch, 989 "slice", StringSlice, 990 "split", StringSplit, 991 "substring", StringSubstring, 992 "substr", StringSubstr, 993 "toLowerCase", StringToLowerCase, 994 "toLocaleLowerCase", StringToLocaleLowerCase, 995 "toUpperCase", StringToUpperCase, 996 "toLocaleUpperCase", StringToLocaleUpperCase, 997 "trim", StringTrim, 998 "trimLeft", StringTrimLeft, 999 "trimRight", StringTrimRight, 1000 "link", StringLink, 1001 "anchor", StringAnchor, 1002 "fontcolor", StringFontcolor, 1003 "fontsize", StringFontsize, 1004 "big", StringBig, 1005 "blink", StringBlink, 1006 "bold", StringBold, 1007 "fixed", StringFixed, 1008 "italics", StringItalics, 1009 "small", StringSmall, 1010 "strike", StringStrike, 1011 "sub", StringSub, 1012 "sup", StringSup 1013 )); 1014 } 1015 1016 SetUpString(); 1017