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