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