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