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