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