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